При работе с чужим кодом разработчики часто сталкиваются с конструкциями, которые на первый взгляд кажутся бессмысленными или ошибочными. Одним из таких примеров является сочетание явного соединения с условием ИСТИНА и последующим перечислением таблиц через запятую в секции ИЗ.
В этой статье мы подробно разберем, как платформа 1С интерпретирует подобные запросы, в чем разница между явными и неявными соединениями, используя шпаргалку по языку запросов для понимания основ. Мы проанализируем механику работы SQL-сервера при выполнении таких инструкций и выработаем рекомендации по их безопасному использованию.
Рассмотрим пример запроса, понимание которого часто проверяется во время собеседования или интервью при отборе программиста. У нас есть основная таблица и ряд присоединяемых таблиц, записанных следующим образом:
ВЫБРАТЬ
...
ИЗ
Покупатель КАК Покупатель
ЛЕВОЕ СОЕДИНЕНИЕ КонечныйПокупатель КАК КонечныйПокупатель
ПО (ИСТИНА),
ТочкаДоставки КАК ТочкаДоставки,
Плательщик КАК Плательщик,
Продавец КАК Продавец
Здесь мы видим смешение двух стилей написания запросов. Давайте разделим эту конструкцию на логические блоки и проанализируем каждый из них.
Первая часть конструкции — это явное соединение:
...
Покупатель ЛЕВОЕ СОЕДИНЕНИЕ КонечныйПокупатель ПО (ИСТИНА)
Обычно в секции ПО мы пишем условие связи, например Покупатель.Ссылка = КонечныйПокупатель.Ссылка. Однако использование литерала ИСТИНА заставляет систему соединять каждую строку левой таблицы с каждой строкой правой таблицы безусловно. Если бы это было ВНУТРЕННЕЕ СОЕДИНЕНИЕ, мы бы получили классическое декартово произведение (Cartesian Product).
Но здесь используется ЛЕВОЕ СОЕДИНЕНИЕ. Это дает нам важное преимущество:
КонечныйПокупатель есть записи (например, одна запись с параметрами), она присоединится ко всем строкам таблицы Покупатель.КонечныйПокупатель пуста, результат запроса не исчезнет. Строки из таблицы Покупатель останутся, а поля, относящиеся к КонечныйПокупатель, будут заполнены значением NULL.Вывод: Конструкция ЛЕВОЕ СОЕДИНЕНИЕ ... ПО (ИСТИНА) часто используется для безопасного присоединения таблиц с настройками или параметрами, где мы не уверены, есть ли там данные, но не хотим потерять основную выборку.
Теперь обратим внимание на вторую часть секции ИЗ:
...,
ТочкаДоставки КАК ТочкаДоставки,
Плательщик КАК Плательщик
В языке запросов 1С (как и в старом стандарте SQL-89) запятая между таблицами означает неявное соединение. При отсутствии условий в секции ГДЕ, связывающих эти таблицы, запятая работает как оператор CROSS JOIN (полное декартово произведение).
Это означает, что результат предыдущего соединения (Покупатель + КонечныйПокупатель) будет перемножен на каждую строку таблицы ТочкаДоставки, затем полученный результат перемножен на таблицу Плательщик и так далее.
Самый важный момент, который необходимо понимать программисту 1С, заключается в поведении при пустых таблицах. Давайте сравним поведение двух подходов.
Предположим, у нас есть одна запись в таблице Покупатель.
Если таблица КонечныйПокупатель пуста, запрос вернет:
Выборка сохранена.
Если таблица ТочкаДоставки (перечисленная через запятую) пуста, запрос вернет:
Это происходит потому, что декартово произведение любого множества на пустое множество дает пустое множество (X * 0 = 0). Таким образом, наличие одной пустой таблицы в списке через запятую полностью обнуляет результат всего запроса.
Именно поэтому использование запятой для соединения таблиц считается опасной практикой, если вы не гарантируете наличие данных в этих таблицах на 100%.
Вторая проблема, связанная с синтаксисом перечисления через запятую (или соединением ПО ИСТИНА), — это неконтролируемое размножение строк. Чтобы визуально оценить масштаб проблемы, лучше всего использовать консоль запросов с поддержкой временных таблиц.
Представим ситуацию, когда таблицы содержат следующее количество строк:
Покупатель: 100 строкТочкаДоставки: 5 строкПлательщик: 3 строкиЕсли мы выполним запрос с перечислением через запятую без условий связи, количество строк в итоговой выборке будет равно произведению:
100 * 5 * 3 = 1500 строк
Каждая запись о покупателе продублируется 15 раз. Это нормально, если мы намеренно хотим получить все возможные комбинации, но в контексте обычных учетных задач это чаще всего является логической ошибкой, приводящей к неверным итогам в отчетах.
Несмотря на "странность", рассмотренный запрос может быть вполне рабочим и корректным в определенных ситуациях. Разберем случаи, когда это применимо:
Номенклатура * Склады * Организации), чтобы потом присоединить к этому скелету остатки. В этом случае CROSS JOIN (или запятая) является единственным верным решением.Если вы встретили такой код и хотите его улучшить или обезопасить, упрощая работу разработчика при написании запросов, следуйте этим рекомендациям:
Всегда лучше использовать явный синтаксис JOIN.
Было:
ИЗ
Таблица1, Таблица2
Стало (если нужна полная комбинация):
ИЗ
Таблица1
ПОЛНОЕ СОЕДИНЕНИЕ Таблица2 ПО (ИСТИНА)
Но чаще всего вам нужно именно ЛЕВОЕ СОЕДИНЕНИЕ, чтобы избежать потери данных при пустой второй таблице.
Если вы присоединяете таблицу настроек, которая теоретически может быть пустой, используйте конструкцию:
ИЗ
ДокументыПродажи
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_НастройкиОтчета ПО (ИСТИНА)
Это гарантирует, что список продаж не исчезнет, если ВТ настроек окажется пустой.
Убедитесь, что таблицы, которые вы присоединяете без условий (или по условию ИСТИНА), содержат не более одной записи. В противном случае вы получите дублирование данных, которое исказит суммы в отчетах.
Конструкция из темы форума интерпретируется платформой следующим образом:
ЛЕВОЕ СОЕДИНЕНИЕ первой пары таблиц. Это безопасно для выборки.CROSS JOIN) со всеми таблицами, перечисленными через запятую.Используйте этот инструмент осознанно, понимая риски потери данных или их задвоения.