Перейти к содержимому

Выборка ORM возвращает не то - разбор причин

Запрос через ORM отрабатывает без ошибок, а строк больше нужного, меньше нужного или в них пусто. Разбираем причины по убыванию частоты.

С чего начать

Смотрим собранный запрос целиком:

$query = ElementTable::query()
->setSelect(['ID', 'TITLE'])
->where('TITLE', 'тест');
echo $query->getQuery(); // готовый SQL со всеми объединениями
// фильтр без оператора собирается в поиск подстроки, а не в равенство

Собранный запрос отвечает на большинство вопросов сразу. В нём видно и лишние объединения таблиц, и условие поиска подстроки там, где ожидалось точное сравнение.

Проверяем оператор в условии:

->where('=TITLE', 'тест') // точное совпадение
->where('TITLE', 'тест') // поиск подстроки: вернёт и «тест два»
// оператор указывают явно: значение по умолчанию удивляет чаще всего

Условие без явного оператора ищет вхождение подстроки. Это объясняет лишние строки в выборке, которые выглядят похожими на нужные, но нужными не являются.

Смотрим, что вернулось на самом деле:

$rows = $query->fetchAll();
printf("строк=%d уникальных ID=%d\n", count($rows), count(array_unique(array_column($rows, 'ID'))));
// строк больше, чем уникальных записей - объединение размножило результат

Разница между числом строк и числом уникальных записей выдаёт объединение таблиц. Связь один-ко-многим множит основную запись по числу связанных, и постраничная навигация после этого считает неправильно.

Разделяем запрос со связями:

use Bitrix\Main\ORM\Query\QueryHelper;
$rows = QueryHelper::decompose($query, true); // связи выбираются отдельно
// без этого ограничение считает строки объединения, а не записи сущности

Помощник разбирает запрос на части и собирает результат по записям. Это же снимает произведение строк, когда в одном запросе выбрано сразу несколько отношений.

Проверяем результат записи:

$result = ElementTable::update($id, ['TITLE' => 'новое']);
if (!$result->isSuccess()) {
print_r($result->getErrorMessages());
}
printf("изменено строк: %d\n", $result->getAffectedRowsCount());
// успех операции и факт изменения строки - разные вещи

Проверка результата обязательна на каждой записи. Без неё непрошедшая проверку операция сообщает об ошибке предупреждением уровня PHP, которое на боевом сайте никто не увидит.

Причины

  1. В условии нет явного оператора примерно 30% случаев

    ПризнакВ выборке лишние записи, похожие на нужные по началу строки.

    ПроверкаСмотрим собранный запрос: условие собралось в поиск подстроки вместо равенства.

    Что делатьПишем оператор явно: точное сравнение задаётся отдельным знаком перед именем поля.

  2. Ограничение применяется к строкам объединения примерно 25% случаев

    ПризнакВместо пяти записей приходит одна-две, зато с неполными связанными данными.

    ПроверкаСчитаем строки и уникальные записи в результате, смотрим объединения в запросе.

    Что делатьРазделяем запрос помощником для связей: ограничение должно считать записи, а не строки.

  3. Несколько связей в одном запросе примерно 20% случаев

    ПризнакСтрок в разы больше ожидаемого, запрос съедает память и выполняется долго.

    ПроверкаПеремножаем число связанных записей: результат совпадёт с числом строк.

    Что делатьВыбираем отношения отдельными запросами, а не одним общим с несколькими объединениями.

  4. Поле не запрошено и не подгружается само примерно 15% случаев

    ПризнакОбъект получен, а поле пустое или обращение к нему даёт исключение.

    ПроверкаСмотрим список выбранных полей и то, какие поля читает код дальше.

    Что делатьНазываем нужные поля в выборке или подгружаем их явно: сами они не появятся.

  5. Результат записи не проверен примерно 10% случаев

    ПризнакКод отработал, а данные в базе прежние или изменились не у всех записей.

    ПроверкаПечатаем ошибки результата и число затронутых строк после записи.

    Что делатьПроверяем результат всегда: успех операции и факт изменения строки - разные вещи.

Частые вопросы

Почему выборка возвращает лишние записи?

Условие без оператора ищет подстроку. Для точного совпадения оператор указывают явно, и это первое, что проверяют при лишних строках.

Почему постраничная навигация врёт при связях?

Ограничение применяется к строкам результата, а объединение размножает основную запись. Считать надо записи, а связи разбирать отдельным запросом.

Можно ли брать объекты при группировке?

Нет, при группировке и агрегатах форма результата другая: берут обычные массивы. Объекты рассчитаны на выборку записей сущности, а не на сводные числа.

Удаление записи удаляет связанные данные?

Нет, удаляется только сама запись. Связанные строки убирают явно или обработчиком события удаления.

Как понять, что запись действительно изменилась?

По числу затронутых строк в результате. Успешный результат означает лишь отсутствие ошибок, а не факт изменения данных.

Смежное

Первоисточник