При работе с языком запросов 1С программисты часто сталкиваются с неочевидным поведением стандартных конструкций, особенно когда речь идет об объединении данных из нескольких источников (например, нескольких табличных частей разных документов). В данной статье мы подробно разберем два критически важных вопроса: как оператор ПЕРВЫЕ ведет себя внутри конструкции ОБЪЕДИНИТЬ и как правильно оптимизировать соединения этих данных с регистрами сведений для обеспечения максимальной производительности системы.
Рассмотрим ситуацию, когда нам необходимо выбрать ограниченное количество записей из результата объединения нескольких запросов. Распространенной ошибкой является мнение, что указание ПЕРВЫЕ в начале запроса ограничит общий результат всей выборки. На самом деле, в языке запросов 1С (и в лежащем в его основе T-SQL) оператор ПЕРВЫЕ имеет локальную область видимости.
Проанализируем это на примере. Допустим, у нас есть две табличные части, и в каждой из них более 100 строк. Мы пишем следующий запрос:
ВЫБРАТЬ ПЕРВЫЕ 100
ТЧ1.Ссылка КАК Документ,
ТЧ1.Номенклатура КАК Товар
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК ТЧ1
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ПЕРВЫЕ 100
ТЧ2.Ссылка,
ТЧ2.Номенклатура
ИЗ
Документ.АктВыполненныхРабот.Услуги КАК ТЧ2
В данном случае мы получим не 100 записей, а до 200 записей. Это происходит потому, что инструкция ПЕРВЫЕ 100 отрабатывает независимо для каждой части объединения. Если в первой таблице есть 100 строк, они будут выбраны. Если во второй есть еще 100, они также попадут в результат.
Если же ваша задача — получить строго 100 записей из общего массива данных, нам необходимо использовать механизм вложенных запросов или временных таблиц. Рассмотрим, как это сделать правильно:
ВЫБРАТЬ ПЕРВЫЕ 100
ВложенныйЗапрос.Документ,
ВложенныйЗапрос.Товар
ИЗ
(ВЫБРАТЬ
ТЧ1.Ссылка КАК Документ,
ТЧ1.Номенклатура КАК Товар
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК ТЧ1
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ТЧ2.Ссылка,
ТЧ2.Номенклатура
ИЗ
Документ.АктВыполненныхРабот.Услуги КАК ТЧ2) КАК ВложенныйЗапрос
УПОРЯДОЧИТЬ ПО
ВложенныйЗапрос.Документ
Важное замечание: Использование ПЕРВЫЕ без УПОРЯДОЧИТЬ ПО делает результат запроса недетерминированным. Это означает, что СУБД вернет те записи, которые первыми попали в выборку согласно текущему состоянию индексов и статистики. Для получения предсказуемого результата всегда указывайте сортировку.
Второй важный вопрос касается производительности при соединении объединяемых данных с другими таблицами (например, с Регистром Сведений для получения цен или остатков). Здесь нет единственно верного ответа, так как все зависит от объема данных и типа соединения.
Этот подход предпочтителен в случаях, когда соединение выступает в роли фильтра. Например, если вы используете ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN) или накладываете условие в секции ГДЕ на данные из регистра.
Разберем почему: если мы сначала объединим 10 000 строк из ТЧ документов во временную таблицу, а потом начнем их фильтровать соединением с РС, нам придется записать все 10 000 строк на диск (в базу tempdb). Если же мы сделаем соединение внутри каждой части запроса, то в результат (и во временную таблицу) попадут только те строки, которые прошли фильтр. Это значительно сокращает объем дисковых операций.
ВЫБРАТЬ
ТЧ.Ссылка,
Цены.Цена
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК ТЧ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК Цены
ПО ТЧ.Номенклатура = Цены.Номенклатура
ОБЪЕДИНИТЬ ВСЕ
// ... аналогично для других ТЧ
Этот метод эффективнее, когда вам нужно просто "дотянуть" справочные данные (например, артикул номенклатуры или текущую цену) без фильтрации строк. Рассмотрим алгоритм действий:
ОБЪЕДИНИТЬ ВСЕ и помещаем их во временную таблицу (ВТ).Рассмотрим пример реализации:
// Помещаем данные во временную таблицу
ВЫБРАТЬ
ТЧ1.Номенклатура КАК Номенклатура,
ТЧ1.Количество КАК Количество
ПОМЕСТИТЬ ВТ_Данные
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК ТЧ1
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ТЧ2.Номенклатура,
ТЧ2.Количество
ИЗ
Документ.ПеремещениеТоваров.Товары КАК ТЧ2
;
// Индексируем для быстрого соединения
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
// Выполняем финальное соединение
ВЫБРАТЬ
ВТ.Номенклатура,
ВТ.Количество,
Цены.Цена
ИЗ
ВТ_Данные КАК ВТ
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК Цены
ПО ВТ.Номенклатура = Цены.Номенклатура
Проанализируем ситуацию с виртуальными таблицами. Если вы используете СрезПоследних внутри каждой части объединения, система будет вынуждена вычислять этот срез несколько раз (для каждого запроса в объединении). В этом случае использование временной таблицы с последующим однократным соединением с виртуальной таблицей почти всегда будет работать быстрее.
Также стоит обратить внимание на типизацию полей. При объединении ТЧ разных документов 1С может привести поля к составному типу. Соединение по составному типу (особенно если одна из частей — ссылка на документ, а другая — на справочник) крайне негативно сказывается на производительности. В таких случаях помещение данных в ВТ с явным приведением типа через ВЫРАЗИТЬ является обязательным условием оптимизации.
Выясним причину, по которой ОБЪЕДИНИТЬ ВСЕ лучше, чем просто ОБЪЕДИНИТЬ. Оператор ОБЪЕДИНИТЬ выполняет неявную проверку на уникальность (аналог ВЫБРАТЬ РАЗЛИЧНЫЕ), что требует значительных ресурсов СУБД для сортировки и сравнения строк. В 90% случаев при работе с ТЧ документов дубликаты строк между разными документами невозможны или не критичны, поэтому использование ОБЪЕДИНИТЬ ВСЕ — это "золотой стандарт" разработки.
Подводя итог, при проектировании сложных запросов с объединениями придерживайтесь следующих правил:
ПЕРВЫЕ ограничивает только ту часть ВЫБРАТЬ, в которой написан. Для общего ограничения используйте вложенный запрос.УПОРЯДОЧИТЬ ПО при применении лимитов.ОБЪЕДИНИТЬ.ОБЪЕДИНИТЬ ВСЕ для исключения лишних нагрузок на сервер базы данных.Соблюдение этих принципов позволит вам создавать быстрые и масштабируемые решения даже при работе с очень большими объемами данных в системе 1С:Предприятие.