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