Запросы в 1С, которые выглядят нормально, но работают плохо

04.05.26

Разработка - Рефакторинг и качество кода

Есть запросы, которые сразу вызывают подозрение: десятки соединений, множество временных таблиц, объединения, группировки и длинный список условий. Но чаще проблемы прячутся в другом месте — в запросах, которые выглядят вполне приемлемо. Пара обращений через точку, отбор после виртуальной таблицы, РАЗЛИЧНЫЕ «чтобы убрать дубли», большой список в параметре, реквизит регистратора через составной тип — и вот уже на тестовой базе все летает, а в рабочей базе отчет открывается минуту. Разберу такие случаи из практики: не синтаксические ошибки, а именно запросы, которые формально нормальные, но на больших данных начинают вести себя плохо.

Запросы в 1С, которые выглядят нормально, но работают плохо

Есть запросы, которые сразу вызывают подозрение: десятки соединений, множество временных таблиц, объединения, группировки и длинный список условий. С такими хотя бы понятно — их надо проверять.

Но чаще проблемы приходят не от таких монстров. Гораздо неприятнее запросы, которые выглядят совершенно нормально. Небольшой текст, пара условий, один регистр, один справочник. На тестовой базе выполняется мгновенно. На рабочей базе сначала тоже живет спокойно. А потом данных становится больше, отчет начинают строить не за неделю, а за год — и внезапно этот «обычный» запрос превращается в источник тормозов.

Вот здесь и начинается самое интересное, текст запроса в 1С часто выглядит проще, чем фактическая работа, которую придется выполнить платформе и СУБД. За одной точкой может скрываться соединение. За одним полем составного типа — несколько возможных таблиц. За простым В (&Список) — огромный набор значений. За виртуальной таблицей регистра — достаточно сложная логика получения остатков, оборотов или срезов.

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

 

Разыменование ссылок: когда одна точка превращается в соединение

Начну с классики — обращение через точку к реквизитам ссылочного типа.

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

ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    Продажи.Номенклатура.Артикул КАК Артикул,
    СУММА(Продажи.КоличествоОборот) КАК Количество
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания) КАК Продажи
ГДЕ
    Продажи.Номенклатура.ВидНоменклатуры = &ВидНоменклатуры

СГРУППИРОВАТЬ ПО
    Продажи.Номенклатура,
    Продажи.Номенклатура.Артикул

Формально запрос нормальный, понятный, читается легко. И именно поэтому такие места часто остаются без внимания.

Но здесь кроется главный подвох: Продажи.Номенклатура.Артикул и Продажи.Номенклатура.ВидНоменклатуры — это не поля регистра продаж. Это реквизиты связанного справочника. То есть платформа должна получить данные не только из регистра, но и из таблицы справочника номенклатуры.

Само разыменование ссылок не является ошибкой. Я бы не стал вводить правило «никогда не используйте точку». Это было бы слишком грубо. Проблема начинается тогда, когда разыменованные поля попадают в условия отбора, группировки или соединения на больших объемах данных.

Более верный вариант — написать соединение явно:

ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    Номенклатура.Артикул КАК Артикул,
    СУММА(Продажи.КоличествоОборот) КАК Количество
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания) КАК Продажи
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
        ПО Продажи.Номенклатура = Номенклатура.Ссылка
ГДЕ
    Номенклатура.ВидНоменклатуры = &ВидНоменклатуры

СГРУППИРОВАТЬ ПО
    Продажи.Номенклатура,
    Номенклатура.Артикул

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

Я обычно настораживаюсь не от самой точки в запросе, а от ситуации, когда через эту точку начинают фильтровать большой набор данных. На маленькой базе разницы может не быть. На базе с миллионами движений разница иногда становится очень заметной.

 

Поля составного типа: особенно аккуратно с регистратором

Отдельная история — поля составного типа. В типовых конфигурациях они встречаются постоянно: Регистратор, ДокументОснованиеСубконто1, Субконто2, Субконто3 и другие похожие поля.

На уровне 1С мы видим одно поле. Но внутри в нем могут лежать ссылки на разные таблицы: документы, справочники, планы видов характеристик и так далее.

Поэтому запрос вида:

ВЫБРАТЬ
    Движения.Регистратор КАК Регистратор,
    Движения.Регистратор.Номер КАК НомерРегистратора,
    Движения.Регистратор.Дата КАК ДатаРегистратора,
    Движения.Сумма КАК Сумма
ИЗ
    РегистрНакопления.ВзаиморасчетыСКонтрагентами КАК Движения
ГДЕ
    Движения.Период МЕЖДУ &ДатаНачала И &ДатаОкончания

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

Если у регистра несколько типов регистраторов, обращение к Регистратор.Номер становится не таким простым, как выглядит. Платформе нужно учитывать возможные типы регистратора. В тексте запроса это одна строка, а фактическая работа может оказаться заметно сложнее.

Если нужен конкретный тип регистратора, лучше явно ограничить тип через ССЫЛКА и выполнить приведение через ВЫРАЗИТЬ.

ВЫБРАТЬ
    Движения.Регистратор КАК Регистратор,
    ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Номер КАК НомерРегистратора,
    ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Дата КАК ДатаРегистратора,
    Движения.Сумма КАК Сумма
ИЗ
    РегистрНакопления.ВзаиморасчетыСКонтрагентами КАК Движения
ГДЕ
    Движения.Период МЕЖДУ &ДатаНачала И &ДатаОкончания
    И Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг

Такой запрос длиннее, но он гораздо более предсказуемый. Мы явно говорим платформе: нас интересуют только движения, у которых регистратор — документ РеализацияТоваровУслуг.

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

ВЫБРАТЬ
    Движения.Регистратор КАК Регистратор,
    ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Номер КАК НомерРегистратора,
    "Реализация" КАК ВидРегистратора,
    Движения.Сумма КАК Сумма
ИЗ
    РегистрНакопления.ВзаиморасчетыСКонтрагентами КАК Движения
ГДЕ
    Движения.Период МЕЖДУ &ДатаНачала И &ДатаОкончания
    И Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Движения.Регистратор КАК Регистратор,
    ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.ПоступлениеТоваровУслуг).Номер КАК НомерРегистратора,
    "Поступление" КАК ВидРегистратора,
    Движения.Сумма КАК Сумма
ИЗ
    РегистрНакопления.ВзаиморасчетыСКонтрагентами КАК Движения
ГДЕ
    Движения.Период МЕЖДУ &ДатаНачала И &ДатаОкончания
    И Движения.Регистратор ССЫЛКА Документ.ПоступлениеТоваровУслуг

Да, такой запрос выглядит менее элегантно. Но в высоконагруженной базе чаще выбираю предсказуемость, а не красивую универсальность.

Здесь важно не впадать в крайность. Если отчет небольшой, период короткий, а данных мало — универсальный вариант может быть вполне допустимым. Но если это отчет по движениям за год, который открывают пользователи часто, я бы первым делом посмотрел именно на такие места.

 

Отбор после виртуальной таблицы: поздно вспомнили про условие

Виртуальные таблицы регистров — еще одно место, где запрос может выглядеть нормальным, но работать хуже, чем мог бы.

Например, нужно получить остатки по складу:

ВЫБРАТЬ
    Остатки.Склад КАК Склад,
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.КоличествоОстаток КАК КоличествоОстаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаОстатков) КАК Остатки
ГДЕ
    Остатки.Склад = &Склад

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

Чаще правильнее передать условие внутрь параметров виртуальной таблицы:

ВЫБРАТЬ
    Остатки.Склад КАК Склад,
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.КоличествоОстаток КАК КоличествоОстаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(
        &ДатаОстатков,
        Склад = &Склад
    ) КАК Остатки

Разница здесь не косметическая. Мы не просто перенесли условие выше по тексту. Мы даем платформе возможность учитывать отбор уже при построении виртуальной таблицы.

По моему опыту, если отчет по остаткам начинает тормозить, один из первых вопросов: не получаем ли мы остатки «по всему миру», чтобы потом оставить один склад, одну организацию или пару сотен позиций.

То же самое относится к оборотам и срезам. Если отбор можно передать в параметры виртуальной таблицы — я почти всегда проверяю такой вариант.

 

Большой список в параметре В

Еще один типичный случай:

ВЫБРАТЬ
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.КоличествоОстаток КАК КоличествоОстаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаОстатков) КАК Остатки
ГДЕ
    Остатки.Номенклатура В (&СписокНоменклатуры)

Если в списке 20 элементов — ничего страшного. Если там 50 000 элементов — это уже совсем другая история.

Я не говорю, что оператор В плохой. Он нормальный. Проблема начинается тогда, когда в него бездумно передают огромный список и считают, что база как-нибудь справится. Иногда справится. Иногда нет. И обычно это «нет» всплывает в самый неподходящий вечер пятницы.

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

ВЫБРАТЬ
    ТаблицаНоменклатуры.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Номенклатура
ИЗ
    &ТаблицаНоменклатуры КАК ТаблицаНоменклатуры

ИНДЕКСИРОВАТЬ ПО
    Номенклатура
;

////////////////////////////////////////////////////////////////////////////////

ВЫБРАТЬ
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.КоличествоОстаток КАК КоличествоОстаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(
        &ДатаОстатков,
        Номенклатура В
            (ВЫБРАТЬ
                ВТ_Номенклатура.Номенклатура
             ИЗ
                ВТ_Номенклатура КАК ВТ_Номенклатура)
    ) КАК Остатки

Здесь параметр ТаблицаНоменклатуры — это таблица значений с колонкой Номенклатура. Смысл временной таблицы не в том, чтобы сделать запрос «модным». Смысл в том, чтобы дать платформе нормальный набор данных, с которым можно дальше работать как с таблицей.

 

Временные таблицы: не украшение, а изменение порядка обработки данных

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

Я для себя формулирую проще: временная таблица оправдана, если она уменьшает объем данных или делает порядок обработки более понятным.

Например, нам нужно получить продажи по номенклатуре из определенной группы. Можно сразу соединить обороты регистра со справочником номенклатуры:

ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    СУММА(Продажи.СуммаОборот) КАК Сумма
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания) КАК Продажи
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
        ПО Продажи.Номенклатура = Номенклатура.Ссылка
ГДЕ
    Номенклатура.Родитель = &ГруппаНоменклатуры

СГРУППИРОВАТЬ ПО
    Продажи.Номенклатура

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

ВЫБРАТЬ
    Номенклатура.Ссылка КАК Номенклатура
ПОМЕСТИТЬ ВТ_Номенклатура
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.Родитель = &ГруппаНоменклатуры

ИНДЕКСИРОВАТЬ ПО
    Номенклатура
;

////////////////////////////////////////////////////////////////////////////////

ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    СУММА(Продажи.СуммаОборот) КАК Сумма
ИЗ
    РегистрНакопления.Продажи.Обороты(
        &ДатаНачала,
        &ДатаОкончания,
        ,
        Номенклатура В
            (ВЫБРАТЬ
                ВТ_Номенклатура.Номенклатура
             ИЗ
                ВТ_Номенклатура КАК ВТ_Номенклатура)
    ) КАК Продажи

СГРУППИРОВАТЬ ПО
    Продажи.Номенклатура

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

У меня были случаи, когда такая перестановка давала заметный эффект. И были ситуации, когда разницы почти не было. Поэтому временные таблицы — это не религия. Их нужно применять там, где они реально меняют объем обрабатываемых данных.

 

Индекс временной таблицы: полезно, но не «на всякий случай»

В запросах 1С можно использовать ИНДЕКСИРОВАТЬ ПО для временных таблиц. Это хороший инструмент, но его тоже не стоит применять механически.

Индекс имеет смысл, если временная таблица дальше используется:

  • в соединении;
  • в условии отбора;
  • в подзапросе;
  • несколько раз в последующих пакетах запроса.

Например:

ВЫБРАТЬ
    ТаблицаТоваров.Номенклатура КАК Номенклатура,
    ТаблицаТоваров.Количество КАК Количество
ПОМЕСТИТЬ ВТ_Товары
ИЗ
    &ТаблицаТоваров КАК ТаблицаТоваров

ИНДЕКСИРОВАТЬ ПО
    Номенклатура
;

////////////////////////////////////////////////////////////////////////////////

ВЫБРАТЬ
    ВТ_Товары.Номенклатура КАК Номенклатура,
    ВТ_Товары.Количество КАК Количество,
    ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
ИЗ
    ВТ_Товары КАК ВТ_Товары
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
            &ДатаОстатков,
            Номенклатура В
                (ВЫБРАТЬ
                    ВТ_Товары.Номенклатура
                 ИЗ
                    ВТ_Товары КАК ВТ_Товары)
        ) КАК Остатки
        ПО ВТ_Товары.Номенклатура = Остатки.Номенклатура

Если временная таблица маленькая или читается один раз целиком, индекс может ничего не дать, а даже добавит лишнюю работу на его построение.

Поэтому обычно отношусь к ИНДЕКСИРОВАТЬ ПО практично, не спеша: поставил, замерил, сравнил. Если эффект есть — оставил. Если эффекта нет — убрал, чтобы не создавать видимость оптимизации.

 

Лишняя группировка: когда результат правильный, но слишком дорогой

Группировки в запросах часто разрастаются незаметно. Добавили поле в выборку, платформа попросила добавить его в группировку, потом еще одно, потом еще. В итоге запрос вроде работает, но группирует данные на более детальном уровне, чем реально нужно.

Например:

ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    Продажи.Характеристика КАК Характеристика,
    Продажи.Серия КАК Серия,
    Продажи.Склад КАК Склад,
    Продажи.Организация КАК Организация,
    СУММА(Продажи.КоличествоОборот) КАК Количество
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания) КАК Продажи

СГРУППИРОВАТЬ ПО
    Продажи.Номенклатура,
    Продажи.Характеристика,
    Продажи.Серия,
    Продажи.Склад,
    Продажи.Организация

Если отчету действительно нужны номенклатура, характеристика, серия, склад и организация — вопросов нет.

Но часто пользователь потом смотрит только итог по номенклатуре. Остальные поля попали в запрос «на всякий случай» или остались после предыдущей версии отчета.

Тогда лучше так:

ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    СУММА(Продажи.КоличествоОборот) КАК Количество
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания) КАК Продажи

СГРУППИРОВАТЬ ПО
    Продажи.Номенклатура

Здесь контраст простой: чем больше полей в группировке, тем больше уникальных комбинаций должна обработать база. Иногда одно лишнее поле превращает компактный результат в огромную промежуточную выборку.

Мое правило такое: если поле не нужно пользователю, не нужно для отбора и не нужно для дальнейшей логики — его не должно быть в запросе.

 

Соединение до агрегации: когда строки размножаются раньше времени

Одна из самых неприятных ошибок — соединить большие наборы данных до того, как они приведены к нужной детализации.

Например, хотим получить сумму долга по контрагенту рядом с заказом:

ВЫБРАТЬ
    Заказы.Ссылка КАК Заказ,
    Заказы.Контрагент КАК Контрагент,
    Заказы.СуммаДокумента КАК СуммаДокумента,
    СУММА(Взаиморасчеты.СуммаОстаток) КАК Долг
ИЗ
    Документ.ЗаказПокупателя КАК Заказы
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки(&ДатаОстатков) КАК Взаиморасчеты
        ПО Заказы.Контрагент = Взаиморасчеты.Контрагент

СГРУППИРОВАТЬ ПО
    Заказы.Ссылка,
    Заказы.Контрагент,
    Заказы.СуммаДокумента

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

Правильнее сначала привести взаиморасчеты к нужному уровню детализации, а потом соединять:

ВЫБРАТЬ
    Взаиморасчеты.Контрагент КАК Контрагент,
    СУММА(Взаиморасчеты.СуммаОстаток) КАК Долг
ПОМЕСТИТЬ ВТ_Долги
ИЗ
    РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки(&ДатаОстатков) КАК Взаиморасчеты

СГРУППИРОВАТЬ ПО
    Взаиморасчеты.Контрагент

ИНДЕКСИРОВАТЬ ПО
    Контрагент
;

////////////////////////////////////////////////////////////////////////////////

ВЫБРАТЬ
    Заказы.Ссылка КАК Заказ,
    Заказы.Контрагент КАК Контрагент,
    Заказы.СуммаДокумента КАК СуммаДокумента,
    ЕСТЬNULL(ВТ_Долги.Долг, 0) КАК Долг
ИЗ
    Документ.ЗаказПокупателя КАК Заказы
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Долги КАК ВТ_Долги
        ПО Заказы.Контрагент = ВТ_Долги.Контрагент

Главная мысль: перед соединением нужно понимать кратность. Сколько строк справа соответствует одной строке слева? Одна? Несколько? Потенциально сотни?

Если этого не понимать, запрос может быть одновременно и медленным, и неправильным по результату.

 

ВЫБРАТЬ РАЗЛИЧНЫЕ как пластырь от неправильного соединения

ВЫБРАТЬ РАЗЛИЧНЫЕ иногда используют как быстрый способ убрать дубли. Строки задвоились — добавили РАЗЛИЧНЫЕ, визуально стало красиво.

Например:

ВЫБРАТЬ РАЗЛИЧНЫЕ
    Заказы.Ссылка КАК Заказ,
    Заказы.Контрагент КАК Контрагент
ИЗ
    Документ.ЗаказПокупателя КАК Заказы
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя.Товары КАК Товары
        ПО Заказы.Ссылка = Товары.Ссылка

Если задача — получить заказы, у которых есть строки в табличной части, то соединение с табличной частью размножит заказ на количество строк. Потом РАЗЛИЧНЫЕ уберет дубли.

Но это именно пластырь. База сначала размножила строки, потом была вынуждена убрать дубли. На маленьких данных незаметно, на больших — уже вызывает вопросы.

Часто лучше сначала получить список ссылок из табличной части, а потом соединиться с документами:

ВЫБРАТЬ РАЗЛИЧНЫЕ
    Товары.Ссылка КАК Заказ
ПОМЕСТИТЬ ВТ_ЗаказыСТоварами
ИЗ
    Документ.ЗаказПокупателя.Товары КАК Товары

ИНДЕКСИРОВАТЬ ПО
    Заказ
;

////////////////////////////////////////////////////////////////////////////////

ВЫБРАТЬ
    Заказы.Ссылка КАК Заказ,
    Заказы.Контрагент КАК Контрагент
ИЗ
    ВТ_ЗаказыСТоварами КАК ВТ_ЗаказыСТоварами
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя КАК Заказы
        ПО ВТ_ЗаказыСТоварами.Заказ = Заказы.Ссылка

Здесь РАЗЛИЧНЫЕ тоже есть, но оно применяется осознанно: мы получаем уникальный список заказов из табличной части. А не пытаемся замаскировать последствия случайного размножения строк в основном запросе.

 

Условие по выражению: результат правильный, отбор неудачный

Еще один запрос, который часто выглядит безобидно:

ВЫБРАТЬ
    Заказы.Ссылка КАК Ссылка,
    Заказы.Дата КАК Дата,
    Заказы.Контрагент КАК Контрагент
ИЗ
    Документ.ЗаказПокупателя КАК Заказы
ГДЕ
    НАЧАЛОПЕРИОДА(Заказы.Дата, ДЕНЬ) = &ДатаДокумента

Логика понятная: хотим документы за конкретный день. Но условие построено не по самому полю Дата, а по выражению от этого поля.

Я бы предпочел заранее вычислить границы периода в коде и передать их параметрами:

ВЫБРАТЬ
    Заказы.Ссылка КАК Ссылка,
    Заказы.Дата КАК Дата,
    Заказы.Контрагент КАК Контрагент
ИЗ
    Документ.ЗаказПокупателя КАК Заказы
ГДЕ
    Заказы.Дата >= &НачалоДня
    И Заказы.Дата < &НачалоСледующегоДня

В модуле:

НачалоДняОтбора = НачалоДня(ДатаДокумента);
НачалоСледующегоДня = НачалоДняОтбора + 86400;

Запрос.УстановитьПараметр("НачалоДня", НачалоДняОтбора);
Запрос.УстановитьПараметр("НачалоСледующегоДня", НачалоСледующегоДня);

Запрос стал чуть длиннее, но отбор стал более прямым. В таких мелочах часто и набирается разница между «вроде работает» и «работает нормально на большой базе».

 

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

В отладке часто удобно написать так:

ВЫБРАТЬ
    Номенклатура.*
ИЗ
    Справочник.Номенклатура КАК Номенклатура

Для быстрой проверки — нормально. Для рабочего кода я бы так не оставлял.

Во-первых, запрос тащит больше данных, чем нужно. Во-вторых, через полгода уже непонятно, какие именно поля реально используются дальше. В-третьих, при развитии конфигурации состав реквизитов может меняться, а запрос продолжит забирать все подряд.

Лучше явно указать нужные поля:

ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка,
    Номенклатура.Наименование КАК Наименование,
    Номенклатура.Артикул КАК Артикул
ИЗ
    Справочник.Номенклатура КАК Номенклатура

Это скучнее. Но хороший промышленный код часто и выглядит скучно. В этом его плюс.

 

Динамический текст запроса: параметры вместо склейки значений

В коде встречается сборка текста запроса через конкатенацию значений:

ТекстЗапроса =
"ВЫБРАТЬ
|   Заказы.Ссылка
|ИЗ
|   Документ.ЗаказПокупателя КАК Заказы
|ГДЕ
|   Заказы.Номер = """ + НомерДокумента + """";

Так лучше не делать. Это хуже читается, сложнее сопровождается и легко ломается на кавычках, типах и форматах.

Нормальный вариант — использовать параметры:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|   Заказы.Ссылка КАК Ссылка
|ИЗ
|   Документ.ЗаказПокупателя КАК Заказы
|ГДЕ
|   Заказы.Номер = &НомерДокумента";

Запрос.УстановитьПараметр("НомерДокумента", НомерДокумента);

Результат = Запрос.Выполнить();

Если условий много и часть из них включается динамически, сам текст условия можно собирать. Но значения все равно лучше передавать параметрами.

ТекстУсловия = "";

Если ЗначениеЗаполнено(Организация) Тогда
    ТекстУсловия = ТекстУсловия + "
|   И Заказы.Организация = &Организация";
КонецЕсли;

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|   Заказы.Ссылка КАК Ссылка,
|   Заказы.Дата КАК Дата,
|   Заказы.Организация КАК Организация
|ИЗ
|   Документ.ЗаказПокупателя КАК Заказы
|ГДЕ
|   Заказы.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания"
+ ТекстУсловия;

Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);

Если ЗначениеЗаполнено(Организация) Тогда
    Запрос.УстановитьПараметр("Организация", Организация);
КонецЕсли;

Результат = Запрос.Выполнить();

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

 

Отчет проверяли на маленькой базе

Это не синтаксис запроса, но без этого пункт будет неполным.

На тестовой базе может быть 10 документов, 30 элементов номенклатуры и один пользователь. На такой базе почти любой запрос работает быстро. Даже плохой.

В рабочей базе все иначе:

  • миллионы движений в регистрах;
  • сотни тысяч элементов справочников;
  • много пользователей;
  • регламентные задания;
  • обмены;
  • закрытие месяца;
  • отчеты за несколько лет.

И вот здесь разница между «запрос возвращает правильный результат» и «запрос нормально работает» становится очень заметной.

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

 

Короткий чек-лист: куда я смотрю первым делом

Если запрос работает медленно, я обычно прохожусь по такому списку:

  1. Есть ли разыменование ссылок через точку в условиях, группировках или соединениях?
  2. Есть ли поля составного типа: Регистратор, Объект, Субконто?
  3. Можно ли явно ограничить тип через ССЫЛКА и ВЫРАЗИТЬ?
  4. Переданы ли отборы внутрь виртуальных таблиц регистров?
  5. Не соединяются ли большие наборы данных раньше, чем нужно?
  6. Можно ли сначала агрегировать данные, а потом соединять?
  7. Нет ли огромного списка в параметре В (&Список)?
  8. Есть ли смысл заменить большой список на временную таблицу?
  9. Нужен ли индекс по временной таблице?
  10. Нет ли лишних полей в группировке?
  11. Не используется ли РАЗЛИЧНЫЕ как пластырь от неправильного соединения?
  12. Не выбираются ли все поля через *?
  13. Не построено ли условие по выражению от поля вместо прямого отбора?
  14. Проверялся ли запрос на объеме данных, похожем на рабочий?

 

Вывод

Плохой запрос в 1С не всегда выглядит плохим. В этом и проблема.

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

Оптимизация запросов начинается не с временных таблиц и не с индексов. Она начинается с вопроса: какой объем данных я заставляю обработать платформу и СУБД?

Если запрос сначала получает миллион строк, потом соединяет их со справочником, потом группирует, потом убирает дубли, а пользователю в итоге показывает двадцать строк — проблема не в том, что «1С тормозит». Проблема в порядке обработки данных.

Для себя держу несколько простых правил:

  • не разыменовывать ссылки без необходимости, особенно в условиях;
  • аккуратно работать с полями составного типа;
  • передавать отборы в виртуальные таблицы как можно раньше;
  • уменьшать объем данных до соединений и группировок;
  • не использовать РАЗЛИЧНЫЕ как способ скрыть ошибку;
  • не тащить в запрос поля «на всякий случай»;
  • проверять тяжелые запросы на данных, похожих на реальные.

Хороший запрос — это не тот, который просто возвращает правильный результат. Хороший запрос — это тот, который получает этот результат предсказуемо и без лишней работы.

И чем больше база, тем важнее эта разница.

Вступайте в нашу телеграмм-группу Инфостарт

Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».

  • 0% комиссии — оплата напрямую исполнителю;
  • Исполнители любого масштаба — от отдельных специалистов до команд под проект;
  • Прямой обмен контактами между заказчиком и исполнителем;
  • Безопасная сделка — при необходимости;
  • Рейтинги, кейсы и прозрачная система откликов.

См. также

Инструментарий разработчика Роли и права Запросы СКД Программист Руководитель проекта 1С:Предприятие 8 Платные (руб)

Инструменты для разработчиков 1С 8.3: Infostart Toolkit. Автоматизация и ускорение разработки на управляемых формах. Легкость работы с 1С.

16500 руб.

02.09.2020    258815    1430    421    

1165

Инструментарий разработчика Рефакторинг и качество кода Программист 1С:Предприятие 8 Бесплатно (free)

Инструмент для тех, кто устал читать модули по 50 тысяч строк и искать ошибки глазами. MetaVision загружает выгруженные файлы конфигурации и за секунды строит графы функций, находит уязвимости и подсвечивает проблемы производительности. Ключевые возможности: Визуализация логики функций (графы условий, циклов, транзакций и вызовов). Статический аудит безопасности (RCE, SSRF, COM-инъекции, пароли в коде). Поиск проблем производительности (запросы в циклах, вложенные блокировки). Полнотекстовый поиск по всем модулям конфигурации. Статистика по объектам и функциям. Безопасность: Программа работает строго локально. Код вашей конфигурации не отправляется в интернет и не анализируется на сторонних серверах. Попробуйте MetaVision сегодня — узнайте, что скрывает ваш код.

20.04.2026    10235    1070    KHoroshulinAV    55    

85

WEB-интеграция Запросы Программист 1С 8.3 Абонемент ($m)

Post1C - это внешняя обработка, которая превращает 1С в полноценный инструмент для тестирования REST API. Всё управление сосредоточено в одном окне: настройка запроса, выполнение, просмотр ответа и генерация кода - без переключения между формами. Аналог Postman, но работающий в привычной среде 1С.

1 стартмани

02.04.2026    2222    68    priem_nv    23    

65

Рефакторинг и качество кода Программист Россия Бесплатно (free)

GRASP-паттерны в 1С: меньше хаоса, больше архитектуры.

28.08.2025    13749    lapinio    49    

61

Инструментарий разработчика Запросы Программист 1С:Предприятие 8 1С:Зарплата и кадры государственного учреждения 3 1С:Зарплата и Управление Персоналом 3.x Абонемент ($m)

QueryConsole1C — расширение, включающее консоль запросов с поддержкой исполняемых представлений — аналогов виртуальных таблиц, основанных на методах программного интерфейса ЗУП. Оно позволяет выполнять запросы с учётом встроенной бизнес-логики, отлаживать алгоритмы получения данных и автоматически генерировать код на встроенном языке 1С.

1 стартмани

16.05.2025    11237    147    zup_dev    30    

83

Рефакторинг и качество кода Программист Стажер Бесплатно (free)

Разбираем принципы SOLID в контексте 1С: как укротить хаос в коде, сделать его гибким, расширяемым и предсказуемым. Практические примеры, механизмы платформы помогающие в этом и шаги к чистой разработке для новичков и профи.

21.04.2025    20714    RPGrigorev    32    

58

Нейросети Рефакторинг и качество кода Тестирование QA Программист 1С:Предприятие 8 Бесплатно (free)

Искусственный интеллект в код-ревью – это не фантастика, а реальность, которая уже сегодня помогает разработчикам улучшать свои проекты. Расскажем о том, как ИИ может автоматически находить баги и предлагать улучшения, экономя ваше время и ресурсы.

11.03.2025    14543    mrXoxot    53    

58
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. ixijixi 2150 04.05.26 10:36 Сейчас в теме
Все по делу, плюсанул.

Пара замечаний, если позволите
ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    СУММА(Продажи.СуммаОборот) КАК Сумма
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания) КАК Продажи
Не надо суммировать выборку из вирт. таблицы регисистра, эти ресурсы уже просуммированы

----

ВЫБРАТЬ РАЗЛИЧНЫЕ
    Товары.Ссылка КАК Заказ
ПОМЕСТИТЬ ВТ_ЗаказыСТоварами
ИЗ
    Документ.ЗаказПокупателя.Товары КАК Товары

Не факт (надо замерять), но возможно так будет быстрее
ВЫБРАТЬ
    Товары.Ссылка КАК Заказ
ПОМЕСТИТЬ ВТ_ЗаказыСТоварами
ИЗ
    Документ.ЗаказПокупателя.Товары КАК Товары
ГДЕ Товары.НомерСтроки = 1
YA_2060655612; 0x00; +2 Ответить
2. Трактор 1281 04.05.26 10:47 Сейчас в теме
Первый запрос с ошибкой из учебника. Исправлен криво. Вот правильное исправление

ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    Продажи.Номенклатура.Артикул КАК Артикул,
    Продажи.КоличествоОборот КАК Количество
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания, ,Номенклатура.ВидНоменклатуры = &ВидНоменклатуры) КАК Продажи


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

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

Группировка и функция Сумма лишние. Не нужны в данном случае.
7. SlavaKron 04.05.26 15:47 Сейчас в теме
(2) Не соглашусь. Иногда "дешевле" сделать пост-условие, если результат "виртуальной" таблицы небольшой.
Особенно это заметное на регистре БУ: часто городят что-то сложное в параметрах, и это работает значительно дольше, чем пост-условие.
3. pbelousov 21 04.05.26 10:49 Сейчас в теме
Имхо, здесь в каждом примере
"ИНДЕКСИРОВАТЬ ПО" - ошибочно.
то есть излишне.
4. siamagic 04.05.26 12:53 Сейчас в теме
Более верный вариант — написать соединение явно:

ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    Номенклатура.Артикул КАК Артикул,
    СУММА(Продажи.КоличествоОборот) КАК Количество
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания) КАК Продажи
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
        ПО Продажи.Номенклатура = Номенклатура.Ссылка
ГДЕ
    Номенклатура.ВидНоменклатуры = &ВидНоменклатуры

СГРУППИРОВАТЬ ПО
    Продажи.Номенклатура,
    Номенклатура.Артикул
Показать


Условие должно быть в соединение - далее читать не стал там очевидно ещё больше ошибок.
5. Трактор 1281 04.05.26 14:24 Сейчас в теме
(4) условие должно быть в параметрах виртуальной таблицы.
Merkalov; +1 Ответить
11. siamagic 06.05.26 13:47 Сейчас в теме
(5) Нет - внутренние соединение по номенклатуре - это не виртуальная таблица, и соединение "по" с условием по реальной таблице.
6. TMV 2 04.05.26 15:27 Сейчас в теме
Какое-то нашествие однотипных статей - где-то завершился курс "1С с 0"?
rozer; shard; Трактор; +3 Ответить
8. Трактор 1281 04.05.26 22:50 Сейчас в теме
(6) Похоже, что народ так осваивает ИИ. Причёсывает бред, выданный электронным идиотом, и выдаёт за своё.
rozer; Dragonim; +2 Ответить
9. rozer 315 06.05.26 11:14 Сейчас в теме

ВЫБРАТЬ
Взаиморасчеты.Контрагент КАК Контрагент,
СУММА(Взаиморасчеты.СуммаОстаток) КАК Долг
ПОМЕСТИТЬ ВТ_Долги
ИЗ
РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки(&ДатаОстатков) КАК Взаиморасчеты

СГРУППИРОВАТЬ ПО
Взаиморасчеты.Контрагент

ИНДЕКСИРОВАТЬ ПО
Контрагент
Показать


Зачем тут группировка? Получая остатки из вирттаблицы они уже сгруппированы по полям в селекте
10. rozer 315 06.05.26 11:17 Сейчас в теме
лучшее что за последнее время видел по теме https://www.youtube.com/live/IxmXvsbi6-Q?si=oiS3h_oeS6D-Pngj
Для отправки сообщения требуется регистрация/авторизация