Задача: последние цены номенклатуры
Третья задача с собеседования: получить актуальную цену каждой номенклатуры, соединить со справочником и аккуратно обработать товары, у которых цены ещё нет.
СрезПоследних — виртуальная таблица регистра сведений, которая по каждой комбинации измерений возвращает одну, самую свежую запись на указанную дату. Идеально для вопроса «какая сейчас цена у этого товара».
Цены в 1С обычно хранятся в периодическом регистре сведений «Цены номенклатуры»: измерение — Номенклатура (иногда ещё ВидЦены), ресурс — Цена, и у каждой записи есть дата (период). За месяц у одного товара может накопиться десяток записей — цена менялась. Нам же нужна последняя действующая. Собеседующий проверяет три навыка: знаете ли вы виртуальную таблицу СрезПоследних, умеете ли соединять её со справочником номенклатуры и что делаете с товарами, у которых цены нет вообще.
Шаг 1. СрезПоследних — берём последнюю цену
Наивный подход — сгруппировать записи и взять максимум по дате — работает плохо и пишется долго. Платформа уже умеет отдавать «последнее значение на дату» через виртуальную таблицу СрезПоследних:
ВЫБРАТЬ
Цены.Номенклатура КАК Номенклатура,
Цены.Цена КАК Цена
ИЗ
РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ДатаЦены, ) КАК Цены
Первый параметр СрезПоследних — Период: дата, на которую берём актуальную цену (часто это дата документа — «цена, действовавшая в момент отгрузки»). Второй параметр — условие отбора (например, по конкретному виду цены), мы оставили его пустым. По каждой номенклатуре таблица вернёт ровно одну строку — с самой поздней записью не позже указанной даты.
Шаг 2. Соединяем с номенклатурой
Пока у нас цены есть только по тем товарам, для которых вообще заводили цену. Но задача обычно звучит как «выведи весь список номенклатуры и её текущую цену». Значит, отталкиваться надо от справочника Номенклатура, а цену «подтягивать» к нему. Это работа для левого соединения (ЛЕВОЕ СОЕДИНЕНИЕ): берём все строки левой таблицы (номенклатуру), и к ним — подходящие строки правой (цены), а где пары нет — будет пусто.
ВЫБРАТЬ
Номенклатура.Ссылка КАК Номенклатура,
Номенклатура.Наименование КАК Наименование,
Цены.Цена КАК Цена
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ДатаЦены, ) КАК Цены
ПО Цены.Номенклатура = Номенклатура.Ссылка
ГДЕ
НЕ Номенклатура.ПометкаУдаления
И НЕ Номенклатура.ЭтоГруппа
Порядок таблиц важен: слева — Справочник.Номенклатура (её берём целиком), справа — срез цен. Условие соединения ПО Цены.Номенклатура = Номенклатура.Ссылка связывает записи по товару. В ГДЕ мы заодно отсекли помеченные на удаление позиции и группы (папки) справочника — в отчёте по ценам они не нужны.
Шаг 3. ЕСТЬNULL — обрабатываем отсутствующие цены
Вот главный момент задачи. Для товаров, у которых цены в регистре нет, левое соединение подставит в поле Цена специальное значение NULL — «данных нет». Если оставить как есть, отчёт покажет пустую ячейку, а при попытке что-то посчитать (например, Цена * Количество) результат тоже станет NULL, и итог «поедет». Правильно — заменить отсутствующую цену на понятное значение, обычно на 0. Для этого есть функция ЕСТЬNULL(значение, замена): если первый аргумент NULL, вернётся второй.
ВЫБРАТЬ
Номенклатура.Ссылка КАК Номенклатура,
Номенклатура.Наименование КАК Наименование,
ЕСТЬNULL(Цены.Цена, 0) КАК Цена
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ДатаЦены, ) КАК Цены
ПО Цены.Номенклатура = Номенклатура.Ссылка
ГДЕ
НЕ Номенклатура.ПометкаУдаления
И НЕ Номенклатура.ЭтоГруппа
УПОРЯДОЧИТЬ ПО
Наименование
Теперь у товаров без цены будет честный 0, а не пустота, и любые дальнейшие расчёты отработают корректно.
Результат:
Гвозди 3мм 45,00
Доска 40х150 820,00
Новый товар 0,00
Саморез 4х50 3,50
Как это работает
Разберём по частям. СрезПоследних(&ДатаЦены, ) внутри себя для каждой номенклатуры находит запись с максимальной датой, не превышающей переданный Период, и возвращает её — то есть отдаёт готовую «последнюю цену на дату». ЛЕВОЕ СОЕДИНЕНИЕ гарантирует, что в результат попадут все товары из справочника, даже те, для кого среза нет: недостающие поля правой таблицы заполнятся NULL. А ЕСТЬNULL перехватывает эти NULL и подменяет их нулём.
Почему важно именно левое соединение, а не внутреннее (ВНУТРЕННЕЕ СОЕДИНЕНИЕ)? Внутреннее оставило бы только те товары, у которых цена есть — новинки без цены просто выпали бы из отчёта, и никто бы не заметил, что их забыли оценить. Левое соединение показывает всех и подсвечивает пробелы нулём.
Частые ошибки
Сравнивают с NULL через знак равенства. Условие ГДЕ Цены.Цена = NULL не работает: NULL не равен ничему, даже самому себе. Для проверки «пусто ли значение» есть отдельная конструкция ЕСТЬ NULL (например, ГДЕ Цены.Цена ЕСТЬ NULL), а для подстановки замены — функция ЕСТЬNULL(...). Не путайте эти две вещи.
Берут внутреннее соединение вместо левого. Тогда товары без цены молча исчезают из отчёта. Классическая ошибка, из-за которой «пропадает часть номенклатуры», а причину ищут часами. Если нужен весь список — только ЛЕВОЕ СОЕДИНЕНИЕ.
Ставят справочник справа, а срез слева. Тогда «главной» становится таблица цен, и товары без записей всё равно выпадают. При левом соединении целиком берётся левая таблица — значит, слева должен быть тот источник, который нужен полностью (здесь — Справочник.Номенклатура).
Забывают Период у СрезПоследних. Если нужна цена на дату документа, а Период не передали, платформа вернёт самую свежую цену вообще — это может быть цена «из будущего» относительно документа. Для исторических расчётов всегда задавайте Период явно.
Итоги
- Актуальную цену на дату даёт виртуальная таблица
СрезПоследнихрегистра сведений; первый параметр — Период. - Чтобы в отчёт попал весь список номенклатуры, отталкиваемся от справочника и подтягиваем цену через
ЛЕВОЕ СОЕДИНЕНИЕ. - Отсутствующую цену (
NULL) заменяем на 0 функциейЕСТЬNULL(Цены.Цена, 0). NULLнельзя сравнивать через=; проверка «пусто» — этоЕСТЬ NULL, подстановка —ЕСТЬNULL(...).- Левое соединение показывает все товары; внутреннее молча теряет позиции без цены.