При работе с Системой Компоновки Данных (СКД) в 1С часто возникает задача вывести информацию из двух разных, но логически связанных источников данных рядом, то есть параллельно, в одном отчете. Стандартные механизмы СКД при использовании полного соединения или некорректной группировки могут приводить к нежелательному «умножению» строк, когда одна запись из одной таблицы сопоставляется со всеми записями из другой, что делает отчет нечитаемым.
Рассмотрим типовую ситуацию: нам необходимо показать список назначенных пациенту медицинских услуг и список фактически выполненных услуг. При этом назначенные услуги могут быть представлены в базе данных как текстовые строки (например, в документе назначения), а выполненные – как элементы справочника «Номенклатура». Связь между ними может быть непрямой или вовсе отсутствовать на уровне отдельных строк, но важен общий контекст (пациент). Мы хотим получить отчет, где для каждого пациента слева будут его назначения, а справа – выполнения, при этом строки должны идти по порядку, а если одной из сторон больше, то пустые значения должны заполняться.
В этой статье мы подробно разберем несколько подходов к решению этой задачи, от модификации запросов до программной обработки результата, чтобы вы могли выбрать наиболее подходящий для вашей ситуации.
Один из эффективных способов отобразить данные параллельно без умножения – это присвоить порядковый номер каждой строке внутри группы (например, для каждого пациента) в обеих таблицах, а затем выполнить полное соединение по этой группе и номеру. Этот метод позволяет выровнять строки и вывести их по порядку.
Разберем по шагам, как это реализовать в запросе СКД:
ВТ_Назначения и ВТ_Выполнения.АВТОНОМЕРЗАПИСИ() для получения уникального номера для каждой записи в каждой временной таблице. Это нужно для следующего шага – нумерации внутри группы.СУММА(1) для подсчета количества строк, удовлетворяющих условию, что фактически даст нам порядковый номер внутри группы.ПОЛНОЕ СОЕДИНЕНИЕ по полям «Пациент» и «Номер». Это гарантирует, что строки будут выведены параллельно, и если у одной из сторон больше записей, то недостающие будут заполнены NULL, которые мы затем заменим на «---» или другие читаемые значения с помощью ЕСТЬNULL().Посмотрим на пример запроса, который демонстрирует эту логику:
ВЫБРАТЬ
"Пациент1" КАК Пациент,
"Назначение24" КАК Назначение
ПОМЕСТИТЬ ВТ_Назначения
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Пациент1",
"Назначение42"
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Пациент2",
"Назначение65"
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Пациент2",
"Назначение87"
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Пациент2",
"Назначение91"
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Назначения.Пациент,
ВТ_Назначения.Назначение,
АВТОНОМЕРЗАПИСИ() КАК ОбщийНомер
ПОМЕСТИТЬ ВТ_Назначения_ОбщаяНумерация
ИЗ
ВТ_Назначения КАК ВТ_Назначения
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Назначения_ОбщаяНумерация.Пациент,
ВТ_Назначения_ОбщаяНумерация.Назначение,
СУММА(1) КАК Номер
ПОМЕСТИТЬ ВТ_Назначения_НомерВнутриГруппы
ИЗ
ВТ_Назначения_ОбщаяНумерация КАК ВТ_Назначения_ОбщаяНумерация
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Назначения_ОбщаяНумерация КАК ВТ_Назначения_ОбщаяНумерация1
ПО ВТ_Назначения_ОбщаяНумерация.Пациент = ВТ_Назначения_ОбщаяНумерация1.Пациент
И ВТ_Назначения_ОбщаяНумерация.ОбщийНомер >= ВТ_Назначения_ОбщаяНумерация1.ОбщийНомер
СГРУППИРОВАТЬ ПО
ВТ_Назначения_ОбщаяНумерация.Пациент,
ВТ_Назначения_ОбщаяНумерация.Назначение
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
"Пациент1" КАК Пациент,
"Выполнение67" КАК Выполнение
ПОМЕСТИТЬ ВТ_Выполнения
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Пациент2",
"Выполнение52"
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Пациент2",
"Выполнение84"
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Пациент3",
"Выполнение35"
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Пациент3",
"Выполнение01"
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Выполнения.Пациент,
ВТ_Выполнения.Выполнение,
АВТОНОМЕРЗАПИСИ() КАК ОбщийНомер
ПОМЕСТИТЬ ВТ_Выполнения_ОбщаяНумерация
ИЗ
ВТ_Выполнения КАК ВТ_Выполнения
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Выполнения_ОбщаяНумерация.Пациент,
ВТ_Выполнения_ОбщаяНумерация.Выполнение,
СУММА(1) КАК Номер
ПОМЕСТИТЬ ВТ_Выполнения_НомерВнутриГруппы
ИЗ
ВТ_Выполнения_ОбщаяНумерация КАК ВТ_Выполнения_ОбщаяНумерация
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Выполнения_ОбщаяНумерация КАК ВТ_Выполнения_ОбщаяНумерация1
ПО ВТ_Выполнения_ОбщаяНумерация.Пациент = ВТ_Выполнения_ОбщаяНумерация1.Пациент
И ВТ_Выполнения_ОбщаяНумерация.ОбщийНомер >= ВТ_Выполнения_ОбщаяНумерация1.ОбщийНомер
СГРУППИРОВАТЬ ПО
ВТ_Выполнения_ОбщаяНумерация.Пациент,
ВТ_Выполнения_ОбщаяНумерация.Выполнение
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ЕСТЬNULL(ВТ_Назначения_НомерВнутриГруппы.Пациент, ВТ_Выполнения_НомерВнутриГруппы.Пациент) КАК Пациент,
ЕСТЬNULL(ВТ_Назначения_НомерВнутриГруппы.Номер, ВТ_Выполнения_НомерВнутриГруппы.Номер) КАК Номер,
ЕСТЬNULL(ВТ_Назначения_НомерВнутриГруппы.Назначение, "---") КАК Назначение,
ЕСТЬNULL(ВТ_Выполнения_НомерВнутриГруппы.Выполнение, "---") КАК Выполнение
ИЗ
ВТ_Назначения_НомерВнутриГруппы КАК ВТ_Назначения_НомерВнутриГруппы
ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Выполнения_НомерВнутриГруппы КАК ВТ_Выполнения_НомерВнутриГруппы
ПО ВТ_Назначения_НомерВнутриГруппы.Пациент = ВТ_Выполнения_НомерВнутриГруппы.Пациент
И ВТ_Назначения_НомерВнутриГруппы.Номер = ВТ_Выполнения_НомерВнутриГруппы.Номер
УПОРЯДОЧИТЬ ПО
Пациент,
Номер
Важное замечание: Метод с самосоединением для нумерации строк внутри группы, хоть и работоспособен, может быть достаточно ресурсоемким и медленным на больших объемах данных. Мы должны это учитывать при проектировании отчетов.
Для версий платформы 1С 8.3.12 и старше появилась возможность использовать оконные функции в запросах. Это значительно упрощает и ускоряет нумерацию строк внутри групп. Функция ROW_NUMBER() является идеальным инструментом для нашей задачи.
Как мы используем ROW_NUMBER():
ROW_NUMBER() с ключевым словом ПОРЯДОК ПО для указания порядка нумерации и неявной группировки. Например, для нумерации назначений по пациентам:
ВЫБРАТЬ
ВТ_Назначения.Пациент,
ВТ_Назначения.Назначение,
ROW_NUMBER() ПОРЯДОК ПО ВТ_Назначения.Пациент, ВТ_Назначения.Назначение КАК НомерВГруппе
ИЗ
ВТ_Назначения КАК ВТ_Назначения
Здесь ПОРЯДОК ПО ВТ_Назначения.Пациент, ВТ_Назначения.Назначение означает, что записи будут нумероваться внутри каждой уникальной комбинации «Пациент», «Назначение» в указанном порядке. Если нам нужна нумерация только по пациенту, то следует использовать РАЗДЕЛИТЬ ПО ВТ_Назначения.Пациент ПОРЯДОК ПО ВТ_Назначения.Назначение для более явного указания группировки и порядка внутри нее.
ROW_NUMBER(), мы, как и в предыдущем подходе, выполняем ПОЛНОЕ СОЕДИНЕНИЕ по полям «Пациент» и полученному номеру внутри группы. Этот подход значительно производительнее для больших объемов данных.СКД предоставляет мощные инструменты для настройки вывода отчета через макеты. Мы можем использовать несколько наборов данных и настроить пользовательский макет для вывода информации параллельно.
Разберем этапы реализации:
Этот подход менее гибок в плане попарного сопоставления строк без предварительной обработки запросом, но очень удобен, если нам нужно просто вывести два списка данных для одной и той же сущности (Пациент) рядом.
Когда стандартные средства СКД или сложные запросы не дают желаемого результата, или когда требуется очень специфическая логика вывода, мы можем прибегнуть к программной модификации табличного документа. Этот метод дает максимальную гибкость, но требует большего объема кода и может быть менее производительным для очень больших отчетов.
Мы будем работать с событием ПриКомпоновкеРезультата схемы компоновки данных. В этом событии мы получаем доступ к уже сформированному, но еще не выведенному табличному документу.
Рассмотрим шаги:
ПриКомпоновкеРезультата устанавливаем параметр СтандартнаяОбработка = Ложь. Это предотвратит автоматический вывод отчета СКД.ТаблицаНазначений и ТаблицаВыполнений).ДокументРезультат, переданный в процедуру.ТаблицаНазначений в одной части строки и данные из ТаблицаВыполнений в другой.ДокументРезультат.Этот метод предоставляет полный контроль над структурой и содержимым отчета, но требует глубокого понимания программной работы с табличными документами и может быть достаточно трудоемким в реализации и поддержке.
Вернемся к обсуждению из форума. Иногда причина "умножения" строк или сложности в сопоставлении кроется в неполном понимании логической связи между данными. Если существует хотя бы косвенная связь между "назначением" и "выполнением" (например, по типу услуги, дате, документу-основанию или некому уникальному идентификатору, который можно сопоставить), то вместо полного соединения по номеру может быть достаточно левого или правого соединения.
Проанализируем ситуацию:
Назначения ЛЕВОЕ СОЕДИНЕНИЕ Выполнения ПО <УсловиеСвязи>). Невыполненные назначения будут иметь NULL в колонках выполнения.Этот подход значительно проще в реализации, если можно найти адекватное условие для соединения. Он не вызовет "умножения" строк, если условие соединения корректно (т.е. одна запись из левой таблицы не соединяется с несколькими записями из правой, если это не предусмотрено логикой отчета). Если же одна запись назначения может соответствовать нескольким выполнениям (или наоборот), то такой подход снова приведет к увеличению количества строк, но это будет уже *логически оправданное* умножение.
Например, если назначили "Осмотр офтальмолога", а выполнение "Прием врача-офтальмолога", то можно попробовать соединение по частичному совпадению наименований или по сопоставлению справочных данных, если таковые имеются.
Мы рассмотрели несколько методов вывода двух таблиц параллельно в отчетах СКД 1С, каждый из которых имеет свои преимущества и недостатки:
ROW_NUMBER()): Наиболее производительный и элегантный способ нумерации строк внутри групп, если ваша платформа 1С поддерживает их (версия 8.3.12 и выше).Выбор конкретного метода зависит от нескольких факторов: версии вашей платформы 1С, объема обрабатываемых данных, сложности логики сопоставления между "назначениями" и "выполнениями", а также требований к производительности и гибкости отчета. Мы рекомендуем начать с поиска логической связи для соединения, затем перейти к оконным функциям или нумерации в запросе, и только в крайнем случае использовать программную обработку табличного документа.