Язык запросов 1С 8.3 — это мощный инструмент для работы с данными, но его синтаксис часто вызывает сложности у начинающих разработчиков. Даже опытные программисты иногда сталкиваются с неочевидными нюансами при формировании сложных конструкций. В этой статье мы разберём не только базовые правила составления запросов, но и рассмотрим типичные ошибки, способы оптимизации и редкие приёмы, которые помогут выжать максимум из встроенного языка.

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

В отличие от классического SQL, где запрос обычно ограничивается выборкой из физических таблиц, в вы оперируете объектами метаданных — справочниками, документами, регистрами. Это требует другого подхода к проектированию запросов и понимания, как платформа транслирует их в реальные SQL-запросы к базе данных.

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

1. Базовый синтаксис запроса в 1С 8.3

Любой запрос в начинается с ключевого слова ВЫБРАТЬ (аналог SELECT в SQL) и заканчивается точкой с запятой. Минимальная конструкция выглядит так:

ВЫБРАТЬ

Справочник.Номенклатура.Наименование КАК Наименование

ИЗ

Справочник.Номенклатура КАК Номенклатура;

Здесь КАК используется для присвоения псевдонима (алиаса) таблице или полю — это обязательный элемент, если в запросе участвует более одной таблицы. Отсутствие алиасов — одна из самых распространённых ошибок начинающих.

Важно понимать, что в нет понятия "таблица" в привычном SQL-смысле. Вместо этого вы работаете с объектами метаданных:

  • 📁 Справочники — например, Справочник.Контрагенты
  • 📄 Документы — например, Документ.РеализацияТоваровУслуг
  • 📊 Регистры — например, РегистрНакопления.ТоварыНаСкладах
  • 🔄 Виртуальные таблицы — например, Документ.ПоступлениеТоваров.ОстаткиИОбороты

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

💡

Используйте // для комментариев внутри запроса — это поможет документировать сложные конструкции прямо в коде.

2. Условия отбора: WHERE, ГДЕ и их нюансы

Для фильтрации данных в запросах используется конструкция ГДЕ (аналог WHERE в SQL). Однако здесь есть несколько важных особенностей:

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

ВЫБРАТЬ

Документ.ЗаказПокупателя.Номер КАК Номер

ИЗ

Документ.ЗаказПокупателя КАК Заказ

ГДЕ

Заказ.Дата >= &НачалоПериода // Параметр должен быть типа Дата!

Во-вторых, в есть уникальные операторы, которых нет в стандартном SQL:

  • 🔍 В(&Массив) — проверка на вхождение в массив значений
  • 📌 ПУСТОЕЗНАЧЕНИЕ(Поле) — проверка на NULL
  • 🔄 ЗНАЧЕНИЕ(ТипЗначения.СправочникСсылка.Контрагенты) — приведение типов

Типичная ошибка — попытка использовать ИЛИ для фильтрации по нескольким полям одного типа. В таких случаях лучше применять В(&Массив):

// Некорректно (может привести к полному сканированию таблицы):

ГДЕ Номенклатура.Артикул = "А001" ИЛИ Номенклатура.Артикул = "А002"

// Корректно:

ГДЕ Номенклатура.Артикул В (&МассивАртикулов)

📊 Какой оператор отбора вы используете чаще всего?
ГДЕ (WHERE)
В (&Массив)
ПУСТОЕЗНАЧЕНИЕ
Другой

3. Объединения таблиц: СОЕДИНЕНИЕ vs ВЫБРАТЬ ИЗ

В 1С 8.3 есть два основных способа объединить данные из нескольких источников:

  1. Явные соединения (ЛЕВОЕ СОЕДИНЕНИЕ, ВНУТРЕННЕЕ СОЕДИНЕНИЕ)
  2. Подзапросы (вложенные ВЫБРАТЬ в секции ИЗ)

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

ВЫБРАТЬ

Номенклатура.Наименование КАК Наименование,

ОстаткиТоваров.КоличествоОстаток КАК Остаток

ИЗ

Справочник.Номенклатура КАК Номенклатура

ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(

&ТекущаяДата,

Склад = &Склад

) КАК ОстаткиТоваров

ПО Номенклатура.Ссылка = ОстаткиТоваров.Номенклатура

Обратите внимание на использование ЛЕВОЕ СОЕДИНЕНИЕ — оно гарантирует, что в результат попадут все записи из левой таблицы (Номенклатура), даже если для них нет соответствий в правой (ОстаткиТоваров).

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

ВЫБРАТЬ

Номенклатура.Наименование

ИЗ

Справочник.Номенклатура КАК Номенклатура

ГДЕ

Номенклатура.Ссылка В (

ВЫБРАТЬ РАЗЛИЧНЫЕ

РеализацияТоваровУслуг.Номенклатура

ИЗ

Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг

ГДЕ

РеализацияТоваровУслуг.Дата >= НачалоМесяца(&ТекущаяДата)

)

Когда использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ вместо ЛЕВОГО?

ВНУТРЕННЕЕ СОЕДИНЕНИЕ возвращает только те записи, для которых есть соответствия в обеих таблицах. Это полезно, когда вам нужны только "пересекающиеся" данные, например, номенклатура, по которой есть остатки на складе. ЛЕВОЕ СОЕДИНЕНИЕ вернёт все записи из левой таблицы, даже если для них нет соответствий справа (в этом случае поля из правой таблицы будут содержать NULL).

4. Группировка и агрегатные функции

Для анализа данных часто требуется сгруппировать записи по какому-либо признаку и рассчитать итоги. В это делается с помощью конструкции СГРУППИРОВАТЬ ПО и агрегатных функций:

ВЫБРАТЬ

Контрагент.Наименование КАК Контрагент,

СУММА(РеализацияТоваровУслуг.СуммаДокумента) КАК ИтогоПродаж,

КОЛИЧЕСТВО(*) КАК КоличествоДокументов

ИЗ

Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагент

ПО РеализацияТоваровУслуг.Контрагент = Контрагент.Ссылка

ГДЕ

РеализацияТоваровУслуг.Дата МЕЖДУ &НачалоПериода И &КонецПериода

СГРУППИРОВАТЬ ПО

Контрагент.Наименование

Ключевые агрегатные функции в :

Функция Описание Пример использования
СУММА() Суммирует значения СУММА(СуммаДокумента)
КОЛИЧЕСТВО() Считает количество записей КОЛИЧЕСТВО(*) или КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура)
МАКСИМУМ()/МИНИМУМ() Находит максимальное/минимальное значение МАКСИМУМ(Дата)
СРЕДНЕЕ() Вычисляет среднее арифметическое СРЕДНЕЕ(Количество)

Частая ошибка — забыть указать все неагрегированные поля в СГРУППИРОВАТЬ ПО. Например, этот запрос вызовет ошибку:

ВЫБРАТЬ

Контрагент.Наименование,

Номенклатура.Наименование, // Не сгруппировано!

СУММА(Количество)

ИЗ..

СГРУППИРОВАТЬ ПО

Контрагент.Наименование // Пропущено Номенклатура.Наименование

💡

Все поля в SELECT, которые не являются агрегатными функциями, ОБЯЗАТЕЛЬНО должны присутствовать в GROUP BY (СГРУППИРОВАТЬ ПО).

5. Параметры и динамические запросы

Одним из ключевых преимуществ языка запросов является возможность использования параметров. Они позволяют сделать запрос динамическим, передавая значения извне. Параметры обозначаются символом & и должны быть объявлены в начале запроса:

ВЫБРАТЬ

Номенклатура.Наименование КАК Наименование

ИЗ

Справочник.Номенклатура КАК Номенклатура

ГДЕ

Номенклатура.Группа = &ГруппаНоменклатуры

И Номенклатура.ПометкаУдаления = ЛОЖЬ

// Установка параметра в коде:

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

Параметры могут быть любых типов, поддерживаемых :

  • 📅 Дата — для фильтрации по периодам
  • 🔢 Число — для количественных ограничений
  • 📝 Строка — для поиска по наименованиям
  • 🔗 Ссылка — для работы со справочниками и документами
  • 📋 Массив — для передачи списка значений (например, в В(&Массив))

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

Для работы с динамическими условиями (например, когда список фильтров формируется пользователем) удобно использовать конструкцию ЕСТЬ NULL:

ВЫБРАТЬ

..

ГДЕ

(&ДатаНачала ЕСТЬ NULL ИЛИ Документ.Дата >= &ДатаНачала)

И (&ДатаОкончания ЕСТЬ NULL ИЛИ Документ.Дата <= &ДатаОкончания)

Установить все обязательные параметры|Проверить типы передаваемых значений|Обработать случай NULL для необязательных параметров|Оптимизировать запрос для часто используемых комбинаций параметров-->

6. Работа с виртуальными таблицами

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

Основные типы виртуальных таблиц:

  • 📊 Остатки() — текущие остатки на указанную дату
  • 🔄 Обороты() — обороты за период
  • 📈 ОстаткиИОбороты() — комбинация остатков и оборотов
  • 💰 ОборотыМеждуСчетами() — для бухгалтерских регистров

Пример запроса для получения остатков товара на складах:

ВЫБРАТЬ

ОстаткиТоваров.Номенклатура КАК Номенклатура,

ОстаткиТоваров.Склад КАК Склад,

ОстаткиТоваров.КоличествоОстаток КАК Остаток

ИЗ

РегистрНакопления.ТоварыНаСкладах.Остатки(

&ТекущаяДата,

Склад В (&МассивСкладов)

) КАК ОстаткиТоваров

Важные нюансы работы с виртуальными таблицами:

  1. Они всегда требуют указания периода (даты или диапазона дат).
  2. Фильтрация по измерениям (например, Склад В (&МассивСкладов)) значительно ускоряет выполнение.
  3. Виртуальные таблицы не поддерживают ПОЛНОЕ СОЕДИНЕНИЕ.
💡

Если вам нужны остатки на начало и конец периода, используйте виртуальную таблицу ОстаткиИОбороты() с указанием начала и конца периода. Это эффективнее, чем два отдельных запроса.

7. Оптимизация запросов: индексы, планы выполнения и типичные ошибки

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

1. Используйте индексированные поля в условиях отбора. В индексы автоматически создаются для:

  • 🔑 Полей с типом Ссылка (справочники, документы)
  • 📅 Полей типа Дата
  • 🔢 Полей с флагом "Индексировать"

2. Избегайте функций в условиях отбора. Например, вместо:

ГДЕ ЛЕВ(Номенклатура.Артикул, 3) = "А00"

лучше написать:

ГДЕ Номенклатура.Артикул LIKE "А00%"

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

Для анализа производительности используйте ПланЗапроса():

План = Запрос.Выполнить().ПланЗапроса();

План.Записать("C:\Temp\plan.txt");

Типичные ошибки, тормозящие запросы:

⚠️ Внимание: Использование РАЗЛИЧНЫЕ в больших выборках может привести к значительному увеличению времени выполнения. Если вам нужны уникальные значения, рассмотрите возможность предварительной обработки данных во временной таблице.
Ошибка Последствия Решение
Отсутствие условий по дате в виртуальных таблицах Полное сканирование регистра Всегда ограничивайте период
Использование ПОДОБНО с ведущим % Не используется индекс Перепишите условие или используйте полнотекстовый поиск
Слишком много соединений в одном запросе Экспоненциальный рост времени выполнения Разбейте запрос на части, используйте временные таблицы

8. Распространённые ошибки и как их избегать

Даже опытные разработчики иногда допускают ошибки при составлении запросов. Вот наиболее частые из них и способы их исправления:

1. Ошибка "Поле не найдено". Возникает, когда вы обращаетесь к полю, которое не существует в указанной таблице или не выбрано в ВЫБРАТЬ. Всегда проверяйте:

  • 🔍 Правильность имени поля (регистр не важен, но опечатки критичны)
  • 📌 Наличие алиаса (КАК) при обращении к полю
  • 🔄 Принадлежность поля к таблице (например, Документ.Номер, а не просто Номер)

2. Ошибка "Типы не совпадают". Часто встречается при сравнении дат со строками или чисел с ссылками. Решение — явное приведение типов:

ГДЕ Документ.Дата = ДАТАВРЕМЯ(1, 1, 2023) // Явное указание типа Дата

3. Зацикливание при рекурсивных запросах. Если вы используете рекурсивные конструкции (например, для обхода иерархии справочников), обязательно добавьте условие выхода:

ВЫБРАТЬ

Справочник.Номенклатура.Ссылка КАК Ссылка,

Справочник.Номенклатура.Родитель КАК Родитель

ИЗ

Справочник.Номенклатура КАК Справочник.Номенклатура

ГДЕ

НЕ Справочник.Номенклатура.ЭтоГруппа // Условие выхода

4. Неправильная работа с NULL. В поле со значением NULL не равно пустой строке или нулю. Для проверки используйте ЕСТЬ NULL или ПУСТОЕЗНАЧЕНИЕ():

ГДЕ Поле ЕСТЬ NULL // Правильно

ГДЕ Поле = "" // Неправильно (не найдёт NULL)

⚠️ Внимание: При работе с виртуальными таблицами регистров накопления помните, что поле Регистратор может содержать NULL для начальных остатков. Всегда учитывайте это в условиях отбора.

FAQ: Ответы на частые вопросы

Как в запросе получить данные из подчинённого справочника?

Для работы с иерархическими справочниками используйте конструкцию ПОМЕСТИТЬ для рекурсивного обхода или присоединяйте справочник сам к себе:

ВЫБРАТЬ

Родитель.Наименование КАК Группа,

Дети.Наименование КАК Подгруппа

ИЗ

Справочник.Номенклатура КАК Родитель

ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Дети

ПО Родитель.Ссылка = Дети.Родитель

ГДЕ

Родитель.ЭтоГруппа = ИСТИНА

Можно ли в одном запросе получить данные из разных баз?

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

  • 🔌 HTTP-сервисы (если внешняя база предоставляет API)
  • 📤 Обмен данными (через XML, JSON или специализированные форматы)
  • 🖥️ COM-соединение (для работы с другими приложениями)

Внутри одной базы можно работать с данными из разных информационных баз, если они подключены через Распределённые информационные базы (РИБ).

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

Сначала проанализируйте план выполнения запроса:

  1. Убедитесь, что используются индексы (в плане не должно быть Полное сканирование).
  2. Разбейте сложный запрос на несколько простых с использованием ВРЕМЕННЫЕ ТАБЛИЦЫ.
  3. Ограничьте объём данных условиями по дате, складу или другим фильтрам.
  4. Избегайте РАЗЛИЧНЫЕ и УПОРЯДОЧИТЬ ПО на больших выборках.

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

Как в запросе получить последние документы по каждому контрагенту?

Для этого используйте оконные функции (доступны в 1С 8.3.14+) или подзапрос с МАКСИМУМ():

ВЫБРАТЬ

ПоследниеДокументы.Контрагент,

ПоследниеДокументы.Дата,

ПоследниеДокументы.Номер

ИЗ (

ВЫБРАТЬ

Документ.Контрагент,

Документ.Дата,

Документ.Номер,

МАКСИМУМ(Документ.Дата) ПО (Документ.Контрагент) КАК МаксДата

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ГДЕ

Документ.Дата >= &НачалоПериода

СГРУППИРОВАТЬ ПО

Документ.Контрагент,

Документ.Дата,

Документ.Номер

) КАК ПоследниеДокументы

ГДЕ

ПоследниеДокументы.Дата = ПоследниеДокументы.МаксДата

В более новых версиях можно использовать РАЗДЕЛИТЬ ПО и РАНГ() для упрощения кода.

Что делать, если запрос возвращает дублирующиеся строки?

Дубли могут возникать из-за:

  • 🔄 Неправильных соединений (например, ПОЛНОЕ СОЕДИНЕНИЕ вместо ЛЕВОЕ)
  • 📋 Отсутствия РАЗЛИЧНЫЕ при выборке уникальных значений
  • 🔍 Ошибок в условиях соединения (например, соединение по неуникальным полям)

Решения:

  1. Добавьте РАЗЛИЧНЫЕ в начало запроса.
  2. Проверьте условия соединения — они должны однозначно связывать записи.
  3. Используйте СГРУППИРОВАТЬ ПО для агрегации дублей.