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

Связи между таблицами ORM - ссылки, выборка, удаление

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

Решение

Объявляем связь в карте сущности:

use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;
new Reference('ORDER', \Bitrix\Sale\Internals\OrderTable::class,
Join::on('this.ORDER_ID', 'ref.ID')),
// this - наша таблица, ref - та, на которую ссылаемся
// связывать можно и со штатными сущностями платформы, не только со своими

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

Выбираем связанные поля одним запросом:

$rows = SyncLogTable::getList([
'select' => ['ID', 'STATUS', 'ORDER_NUM' => 'ORDER.ACCOUNT_NUMBER'],
'filter' => ['=ORDER.PAYED' => 'Y'], // фильтровать тоже можно через точку
'limit' => 50,
])->fetchAll();

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

Добавляем связь прямо в запросе:

$rows = SyncLogTable::getList([
'runtime' => [new Reference('U', \Bitrix\Main\UserTable::class,
Join::on('this.USER_ID', 'ref.ID'))],
'select' => ['ID', 'USER_LOGIN' => 'U.LOGIN'],
])->fetchAll();
// разовая связь для одного отчёта: карту сущности она не трогает

Разовую связь добавляют прямо в запросе, не трогая карту сущности. Так удобно собирать отчёты, не засоряя карту сущности ссылками, которые нужны один раз в квартал.

Убираем записи при удалении родителя:

// платформа не удаляет связанные записи сама
SyncLogTable::deleteByFilter(['=ORDER_ID' => $orderId]);
// то же самое вешают на событие удаления заказа, иначе таблица копит сирот
// удаление пачкой по фильтру дешевле обхода записей по одной

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

Поле, по которому идёт связь, обязано быть проиндексировано. Соединение по полю без указателя превращает быстрый отчёт в полный проход по обеим таблицам сразу.

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

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

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

Типичные проблемы

В выборке дубли строк по одному заказу.

Связь один-ко-многим возвращает строку на каждую связанную запись. Считают группировкой или отдельным запросом.

Поле связанной таблицы приходит пустым.

Оно не перечислено в списке выбираемых полей запроса. Связь сама по себе поля не подтягивает.

Отчёт со связью работает несколько секунд.

У поля связи нет указателя, и база читает обе таблицы целиком. Указатель ставят на поле, по которому идёт соединение.

В таблице записи несуществующих заказов.

Родитель удалён, а связанные записи никто не убрал. Их чистят своим кодом на событии удаления.

Фильтр по связанному полю не срабатывает.

В фильтре указано имя поля без имени связи. Обращаются к нему через точку, как и в выборке.

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

Чем связь лучше соединения руками?

Она описана один раз и работает во всех запросах. Условие соединения не размножается по коду.

Можно ли связаться со штатной таблицей?

Да, ссылка принимает любую сущность платформы. Менять при этом чужую карту не нужно.

Нужны ли внешние ключи в самой базе?

Не обязательны, но помогают не оставить сирот. Платформа на них не полагается.

Как выбрать связанные записи без дублей?

Отдельным запросом по списку номеров или группировкой. Соединение для этого не годится.

Смежное

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