При разработке отчетов или обработок в 1С мы часто сталкиваемся с необходимостью получить не только основные реквизиты документа или справочника, но и их дополнительные свойства. Зачастую нужно вытащить сразу несколько различных характеристик или свойств для одной строки данных. Если составить запрос неаккуратно, можно легко получить задвоение строк с пустыми колонками. Давайте разберем по шагам эту проблему и посмотрим, как ее правильно решать.
Рассмотрим ситуацию: есть табличная часть документа ЗаказПокупателя.Запасы. Для каждой строки нам нужно получить два свойства из дополнительных реквизитов характеристики номенклатуры: НашНомер и НомерКлиента. Первоначальная попытка решения часто выглядит как объединение двух подзапросов.
Проанализируем пример не совсем корректного запроса, который приводит к проблемам:
ВЫБРАТЬ
ВТ.Номенклатура КАК Номенклатура,
ВТ.Характеристика КАК Характеристика,
ВТ.Количество КАК Количество,
ВТ.Примечание КАК Примечание,
ХарактеристикиНоменклатурыДополнительныеРеквизиты.Значение КАК НашНомер,
"" КАК НомерКлиента
ИЗ
(ВЫБРАТЬ
ЗаказПокупателяЗапасы.Номенклатура КАК Номенклатура,
ЗаказПокупателяЗапасы.Характеристика КАК Характеристика,
ЗаказПокупателяЗапасы.Количество КАК Количество,
ЗаказПокупателяЗапасы.Примечание КАК Примечание
ИЗ
Документ.ЗаказПокупателя.Запасы КАК ЗаказПокупателяЗапасы
ГДЕ
ЗаказПокупателяЗапасы.Ссылка = &Ссылка) КАК ВТ
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ХарактеристикиНоменклатурыДополнительныеРеквизиты
ПО ВТ.Характеристика = ХарактеристикиНоменклатурыДополнительныеРеквизиты.Ссылка
ГДЕ
ХарактеристикиНоменклатурыДополнительныеРеквизиты.Свойство = &Свойство
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ВТ.Номенклатура,
ВТ.Характеристика,
ВТ.Количество,
ВТ.Примечание,
"",
ХарактеристикиНоменклатурыДополнительныеРеквизиты.Значение
ИЗ
(ВЫБРАТЬ
ЗаказПокупателяЗапасы.Номенклатура КАК Номенклатура,
// ... (те же поля)
ИЗ
Документ.ЗаказПокупателя.Запасы КАК ЗаказПокупателяЗапасы
ГДЕ
ЗаказПокупателяЗапасы.Ссылка = &Ссылка) КАК ВТ
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ХарактеристикиНоменклатурыДополнительныеРеквизиты
ПО ВТ.Характеристика = ХарактеристикиНоменклатурыДополнительныеРеквизиты.Ссылка
ГДЕ
ХарактеристикиНоменклатурыДополнительныеРеквизиты.Свойство = &Свойство1
Выясним причину, почему такой код плох. На выходе мы получаем задвоение таблицы: для одной и той же номенклатуры выводится две строки. В первой заполнена колонка НашНомер и пустая НомерКлиента, во второй — наоборот. Более того, есть грубая архитектурная ошибка: условие отбора ГДЕ ХарактеристикиНоменклатурыДополнительныеРеквизиты.Свойство = &Свойство написано в секции ГДЕ. Это автоматически превращает ЛЕВОЕ СОЕДИНЕНИЕ во ВНУТРЕННЕЕ. Если у характеристики свойство не заполнено, строка просто исчезнет из результата.
Чтобы обойти задвоение, можно разбить получение свойств на отдельные временные таблицы, а затем собрать их вместе. Рассмотрим, как это выглядит:
// ... (Создание ВТ базовых данных) ...
ВЫБРАТЬ
ВТ.Номенклатура КАК Номенклатура,
// ...
ХарактеристикиНоменклатурыДополнительныеРеквизиты.Значение КАК НашНомер
ПОМЕСТИТЬ ВТНашНомер
ИЗ ВТ КАК ВТ
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ХарактеристикиНоменклатурыДополнительныеРеквизиты
ПО ВТ.Характеристика = ХарактеристикиНоменклатурыДополнительныеРеквизиты.Ссылка
ГДЕ ХарактеристикиНоменклатурыДополнительныеРеквизиты.Свойство = &Свойство;
// Аналогично создается ВТНомерКлиента
ВЫБРАТЬ
ВТНашНомер.Номенклатура КАК Номенклатура,
ВТНашНомер.НашНомер КАК НашНомер,
ВТНомерКлиента.НомерКлиента КАК НомерКлиента
ИЗ
ВТНашНомер КАК ВТНашНомер
ПОЛНОЕ СОЕДИНЕНИЕ ВТНомерКлиента КАК ВТНомерКлиента
ПО ВТНашНомер.Номенклатура = ВТНомерКлиента.Номенклатура
И ВТНашНомер.Характеристика = ВТНомерКлиента.Характеристика
// ... и другие поля связи
Такой подход будет работать, но мы настоятельно не рекомендуем его использовать. Полное соединение по множеству полей (особенно текстовых, таких как Примечание) — крайне ресурсоемкая операция. При наличии полностью одинаковых строк в документе, СУБД может вернуть непредсказуемое перемноженное количество записей.
Это самый лаконичный и быстрый способ. Вместо создания кучи временных таблиц, давайте сделаем несколько левых соединений прямо в основном запросе. Критически важный момент: условие отбора конкретного свойства нужно перенести из ГДЕ внутрь условия связи ПО.
Посмотрим на пример правильного запроса:
ВЫБРАТЬ
ЗаказЗапасы.Номенклатура КАК Номенклатура,
ЗаказЗапасы.Характеристика КАК Характеристика,
ЗаказЗапасы.Количество КАК Количество,
ЗаказЗапасы.Примечание КАК Примечание,
ЕСТЬNULL(ДопРеквизитыНашНомер.Значение, "") КАК НашНомер,
ЕСТЬNULL(ДопРеквизитыНомерКлиента.Значение, "") КАК НомерКлиента
ИЗ
Документ.ЗаказПокупателя.Запасы КАК ЗаказЗапасы
// Первое левое соединение для свойства "НашНомер"
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизитыНашНомер
ПО ЗаказЗапасы.Характеристика = ДопРеквизитыНашНомер.Ссылка
И ДопРеквизитыНашНомер.Свойство = &Свойство
// Второе левое соединение для свойства "НомерКлиента"
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизитыНомерКлиента
ПО ЗаказЗапасы.Характеристика = ДопРеквизитыНомерКлиента.Ссылка
И ДопРеквизитыНомерКлиента.Свойство = &Свойство1
ГДЕ
ЗаказЗапасы.Ссылка = &Ссылка
Преимущества этого метода:
ЕСТЬNULL, а сама запись документа не пропадет.Иногда архитектура (например, программное формирование запроса) требует использования конструкции ОБЪЕДИНИТЬ ВСЕ. Чтобы при этом избежать дублей с пустыми ячейками, применим функцию МАКСИМУМ.
Логика проста: объединяем результаты во вложенном запросе, а затем группируем по основным полям. В агрегатных функциях берем максимум. Так как любая заполненная строка больше пустой (""), функция «схлопнет» данные в одну правильную строку.
Разберем подробнее код этого подхода:
ВЫБРАТЬ
ВТ_Объединенная.Номенклатура КАК Номенклатура,
ВТ_Объединенная.Характеристика КАК Характеристика,
ВТ_Объединенная.Количество КАК Количество,
ВТ_Объединенная.Примечание КАК Примечание,
МАКСИМУМ(ВТ_Объединенная.НашНомер) КАК НашНомер,
МАКСИМУМ(ВТ_Объединенная.НомерКлиента) КАК НомерКлиента
ИЗ
(ВЫБРАТЬ
ЗаказЗапасы.Номенклатура КАК Номенклатура,
ЗаказЗапасы.Характеристика КАК Характеристика,
ЗаказЗапасы.Количество КАК Количество,
ЗаказЗапасы.Примечание КАК Примечание,
ЕСТЬNULL(ДопРеквизиты.Значение, "") КАК НашНомер,
"" КАК НомерКлиента
ИЗ
Документ.ЗаказПокупателя.Запасы КАК ЗаказЗапасы
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизиты
ПО ЗаказЗапасы.Характеристика = ДопРеквизиты.Ссылка
И ДопРеквизиты.Свойство = &Свойство
ГДЕ
ЗаказЗапасы.Ссылка = &Ссылка
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ЗаказЗапасы.Номенклатура,
ЗаказЗапасы.Характеристика,
ЗаказЗапасы.Количество,
ЗаказЗапасы.Примечание,
"",
ЕСТЬNULL(ДопРеквизиты.Значение, "")
ИЗ
Документ.ЗаказПокупателя.Запасы КАК ЗаказЗапасы
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизиты
ПО ЗаказЗапасы.Характеристика = ДопРеквизиты.Ссылка
И ДопРеквизиты.Свойство = &Свойство1
ГДЕ
ЗаказЗапасы.Ссылка = &Ссылка) КАК ВТ_Объединенная
СГРУППИРОВАТЬ ПО
ВТ_Объединенная.Номенклатура,
ВТ_Объединенная.Характеристика,
ВТ_Объединенная.Количество,
ВТ_Объединенная.Примечание
Такой вариант избавляет нас от задвоения и при этом работает быстрее и безопаснее, чем полное соединение множества временных таблиц.