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