Разработчики, которые часто пишут сложные отчеты или занимаются доработкой типовых конфигураций (таких как «1С:Зарплата и управление персоналом» или «1С:ERP»), рано или поздно сталкиваются в чужом коде с весьма загадочной конструкцией: ВНУТРЕННЕЕ СОЕДИНЕНИЕ ... ПО ИСТИНА. С первого взгляда такое условие связи кажется лишенным практического смысла. Ведь мы привыкли соединять таблицы по конкретным ключевым полям — идентификаторам, ссылкам, кодам или датам. Условие вида ПО ИСТИНА полностью игнорирует сопоставление полей.
В этой статье мы подробно разберем, какой физический смысл кроется за этой конструкцией, как она работает «под капотом» СУБД, в каких реальных бизнес-сценариях она применяется и почему её использование требует крайней осторожности из-за угроз производительности системы.
Для того чтобы понять, как работает это соединение, вспомним школьный курс математики и теорию множеств. В классическом SQL операция, при которой каждая строка первой таблицы соединяется с каждой строкой второй таблицы, называется декартовым произведением (или CROSS JOIN).
В языке запросов «1С» нет прямого оператора CROSS JOIN. Чтобы получить аналогичный результат, разработчики используют стандартное соединение, но вместо равенства ключевых полей (например, Таблица1.Ссылка = Таблица2.Ссылка) указывают тождественно истинное условие — логическую константу ИСТИНА (или TRUE на уровне СУБД).
Давайте проанализируем, как это работает. При обычном соединении СУБД проверяет каждую строку на соответствие условию. Если условие выполняется, строка попадает в выборку. Когда мы пишем ПО ИСТИНА, это условие выполняется абсолютно для любой пары строк из двух таблиц. В результате каждая запись из первой таблицы будет сопоставлена со всеми без исключения записями из второй таблицы.
Если в Таблице 1 содержится N строк, а в Таблице 2 — M строк, то на выходе мы гарантированно получим таблицу размером N * M строк.
Рассмотрим пример, который наглядно иллюстрирует этот механизм. Допустим, у нас есть две таблицы.
Таблица 1 (Товары)
| Наим | id |
|---|---|
| Товар 1 | 21 |
| Товар 2 | 22 |
| Товар 3 | 23 |
| Товар 1 | 21 |
Таблица 2 (Заказчики)
| Наим | id |
|---|---|
| Заказчик 2 | 22 |
| Заказчик 3 | 23 |
| Заказчик 1 | 21 |
Напишем следующий запрос на языке 1С:
ВЫБРАТЬ
Товары.Наим КАК ТоварНаим,
Товары.id КАК ТоварID,
Заказчики.Наим КАК ЗаказчикНаим,
Заказчики.id КАК ЗаказчикID
ИЗ
ТаблицаТовары КАК Товары
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТаблицаЗаказчики КАК Заказчики
ПО ИСТИНА
Давайте выясним, что произойдет при выполнении этого запроса. Обратите внимание: поля id в условии соединения никак не участвуют! Платформа полностью проигнорирует тот факт, что у нас есть совпадающие или несовпадающие идентификаторы. Каждая строка из таблицы «Товары» соединится с каждой строкой таблицы «Заказчики».
В результате мы получим таблицу из 12 строк (4 строки товаров * 3 строки заказчиков):
| ТоварНаим | ТоварID | ЗаказчикНаим | ЗаказчикID |
|---|---|---|---|
| Товар 1 | 21 | Заказчик 2 | 22 |
| Товар 1 | 21 | Заказчик 3 | 23 |
| Товар 1 | 21 | Заказчик 1 | 21 |
| Товар 2 | 22 | Заказчик 2 | 22 |
| Товар 2 | 22 | Заказчик 3 | 23 |
| Товар 2 | 22 | Заказчик 1 | 21 |
| Товар 3 | 23 | Заказчик 2 | 22 |
| Товар 3 | 23 | Заказчик 3 | 23 |
| Товар 3 | 23 | Заказчик 1 | 21 |
| Товар 1 | 21 | Заказчик 2 | 22 |
| Товар 1 | 21 | Заказчик 3 | 23 |
| Товар 1 | 21 | Заказчик 1 | 21 |
Как мы видим, результатом стала полная сетка возможных комбинаций строк первой и второй таблиц. Повторяющаяся строка «Товар 1» с ID 21 также послушно размножилась трижды.
Очень важно понимать разницу в поведении системы при использовании различных типов соединений с условием ПО ИСТИНА. Особенно ярко эта разница проявляется в ситуациях, когда одна из соединяемых таблиц оказывается пустой. Рассмотрим эти сценарии подробнее.
NULL. Если же в правой таблице есть хотя бы одна строка, то левое соединение отработает точно так же, как и внутреннее, то есть перемножит все строки.
ПО ИСТИНА склеит эти строки в одну широкую строку без риска потерять данные, если какая-то из таблиц вдруг окажется пустой.
На первый взгляд декартово произведение кажется искусственным приемом, который редко нужен в реальной жизни. Однако в типовых конфигурациях «1С:Предприятие» эта конструкция используется регулярно. Рассмотрим основные кейсы.
Представим задачу: компания устанавливает единую цену на определенный перечень номенклатуры для всей сети филиалов, расположенных в разных городах. В базе данных есть список товаров и список городов. Нам нужно быстро сформировать записи для регистра сведений «Цены номенклатуры по регионам».
Вместо того чтобы в цикле на встроенном языке обходить каждый товар и каждую территорию, мы пишем простой запрос, соединяющий временную таблицу товаров с таблицей городов ПО ИСТИНА. В результате в один клик мы получаем готовую плоскую таблицу, где для каждого товара создана строчка под каждый город. Остается лишь записать этот массив в базу данных.
В конфигурации «1С:Зарплата и управление персоналом» часто требуется построить плоский отчёт по сотрудникам за каждый день расчетного периода, даже если по сотруднику в этот день не было никаких кадровых событий или начислений (например, для вывода пустой строки или анализа прогулов). Для этого используется соединение таблицы уникальных сотрудников с производственным календарем (или списком дат периода) ПО ИСТИНА.
В результате мы получаем готовую координатную сетку «Сотрудник — Дата», которую затем левым соединением связываем с фактическими данными о начислениях и отработанном времени. Это позволяет избежать «дыр» в отчете и вывести красивые графики с нулевыми показателями.
Разработчики типовых решений часто прибегают к конструкции ПО ИСТИНА для упрощения алгоритмов динамической сборки текста запроса с помощью функции СтрЗаменить().
Допустим, у нас есть базовый шаблон запроса, в котором условия связи между таблицами могут добавляться или не добавляться в зависимости от установленных пользователем функциональных опций или настроек отчета. Если мы сразу напишем в шаблоне базовое условие:
ИЗ
Таблица1 КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Таблица2 КАК Т2
ПО ИСТИНА
То в коде общего модуля мы сможем очень легко дописывать дополнительные фильтры через простой оператор И, не заботясь о том, является ли это условие первым. Мы просто пишем:
ТекстЗапроса = ТекстЗапроса + " И Т1.Организация = Т2.Организация";
Итоговый текст запроса примет вид ПО ИСТИНА И Т1.Организация = Т2.Организация. С точки зрения синтаксиса и оптимизатора СУБД такой запрос абсолютно валиден и будет выполнен корректно, при этом разработчику не пришлось писать сложные проверки на предмет того, нужно ли вставлять ключевое слово ПО или И.
В шаблонах ограничений доступа на уровне записей (RLS) конструкция ПО (ИСТИНА) встречается постоянно. Она используется для связи основной таблицы данных (например, физических лиц или документов) с плоскими таблицами разрешенных пользователю значений (групп доступа, подразделений). Если пользователю разрешен доступ к определенной группе, условие связи выполняется, и строка становится доступной для чтения.
В языке запросов «1С» декартово произведение можно получить и другим способом — просто перечислив таблицы в секции ИЗ через запятую:
ВЫБРАТЬ
Т1.Поле1,
Т2.Поле2
ИЗ
Таблица1 КАК Т1,
Таблица2 КАК Т2
Этот синтаксис пришел из старых стандартов SQL. Результат выполнения этого запроса будет абсолютно идентичен результату запроса с ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПО ИСТИНА. Однако явное использование оператора соединения ПО ИСТИНА считается более предпочтительным по следующим причинам:
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ... ПО ИСТИНА сразу скажет ему о том, что декартово произведение здесь сделано намеренно. Использование запятой в секции ИЗ часто воспринимается как ошибка («забыл указать условия соединения в секции ГДЕ»).INNER JOIN. Если вам понадобится поведение LEFT JOIN (чтобы не потерять строки левой таблицы при пустой правой), вы сможете реализовать это только через явную конструкцию ЛЕВОЕ СОЕДИНЕНИЕ ПО ИСТИНА.Внимание! Операция декартова произведения — одна из самых ресурсоемких операций в реляционных базах данных. С точки зрения производительности СУБД (будь то MS SQL, PostgreSQL или IBM DB2), соединение по константе ИСТИНА заставляет оптимизатор использовать физический оператор Nested Loops (Вложенные циклы).
При этом алгоритме СУБД берет каждую строку из первой таблицы и полностью сканирует вторую таблицу для поиска совпадений. Так как условием выступает ИСТИНА, СУБД вынуждена прочитать и сопоставить абсолютно все записи.
Проанализируем риски:
ПО ИСТИНА, СУБД придется обработать 5 миллиардов записей.Такой запрос гарантированно приведет к следующим последствиям:
Золотое правило разработчика 1С: Использовать конструкцию ПО ИСТИНА можно только в тех случаях, когда вы гарантированно уверены, что хотя бы одна из соединяемых таблиц содержит минимальное количество строк (оптимально — до 10-50 строк). Никогда не соединяйте таким образом две динамически растущие таблицы базы данных.
Конструкция ВНУТРЕННЕЕ СОЕДИНЕНИЕ ... ПО ИСТИНА — это мощный и удобный инструмент языка запросов 1С для получения декартова произведения (полной сетки комбинаций строк). Она незаменима при автоматическом формировании расписаний, матриц цен, кадровых календарей и при динамической сборке текстов запросов программными методами. Однако этот инструмент требует от разработчика высокой культуры программирования и четкого понимания объемов данных, которые будут обрабатываться в процессе выполнения запроса.