При разработке отчетов или обработок в 1С программисты часто сталкиваются с задачей: необходимо получить список номенклатуры и для каждой позиции найти ровно один документ — как правило, последнее поступление. Трудность заключается в том, что язык запросов 1С не поддерживает передачу параметров во вложенные запросы (так называемые коррелированные подзапросы) напрямую через конструкцию ГДЕ Номенклатура = ВнешняяНоменклатура.
В этой статье мы подробно разберем, как правильно составить запрос, чтобы эффективно получить по одному документу для каждой строки справочника, рассмотрим классический метод «Максимальной даты» и обсудим вопросы производительности.
Этот способ считается «золотым стандартом» в 1С. Разберем ситуацию на примере: нам нужно найти последние документы ПоступлениеТоваровУслуг для всего справочника Номенклатура. Проблема в том, что если мы просто сгруппируем документы по номенклатуре и дате, мы получим дату, но не саму ссылку на документ. Если мы добавим ссылку в группировку, то получим все документы за эту дату.
Для решения мы разделим задачу на три этапа. Рассмотрим их подробнее:
Посмотрим на пример кода запроса:
ВЫБРАТЬ
ПоступлениеТовары.Номенклатура КАК Номенклатура,
МАКСИМУМ(ПоступлениеТовары.Ссылка.Дата) КАК МаксДата
ПОМЕСТИТЬ ВТ_МаксДаты
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТовары
ГДЕ
ПоступлениеТовары.Ссылка.Проведен = ИСТИНА
СГРУППИРОВАТЬ ПО
ПоступлениеТовары.Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ПоступлениеТовары.Номенклатура КАК Номенклатура,
МАКСИМУМ(ПоступлениеТовары.Ссылка) КАК Ссылка
ПОМЕСТИТЬ ВТ_ПоследниеДокументы
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТовары
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_МаксДаты КАК ВТ_МаксДаты
ПО ПоступлениеТовары.Номенклатура = ВТ_МаксДаты.Номенклатура
И ПоступлениеТовары.Ссылка.Дата = ВТ_МаксДаты.МаксДата
СГРУППИРОВАТЬ ПО
ПоступлениеТовары.Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
СпрНоменклатура.Ссылка КАК Товар,
ВТ_ПоследниеДокументы.Ссылка КАК ПоследнийДокументПоступления
ИЗ
Справочник.Номенклатура КАК СпрНоменклатура
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПоследниеДокументы КАК ВТ_ПоследниеДокументы
ПО СпрНоменклатура.Ссылка = ВТ_ПоследниеДокументы.Номенклатура
ГДЕ
СпрНоменклатура.ЭтоГруппа = ЛОЖЬ
Важный момент: Мы используем ЛЕВОЕ СОЕДИНЕНИЕ в финальном запросе, чтобы в результат попала вся номенклатура, даже та, по которой никогда не было поступлений. Если вам нужны только те товары, которые хотя бы раз поступали, используйте ВНУТРЕННЕЕ СОЕДИНЕНИЕ.
Проанализируем ситуацию, когда в системе ведется учет цен поставщиков. В типовых конфигурациях 1С (УТ 10.3, КА 1.1, УПП) при проведении поступления часто записываются данные в регистр сведений ЦеныНоменклатурыКонтрагентов. Если ваша задача — просто найти «последнюю цену» или факт последнего поступления через цену, работа с виртуальной таблицей СрезПоследних будет в десятки раз быстрее, чем запрос к табличным частям документов.
Рассмотрим пример запроса к регистру:
ВЫБРАТЬ
ЦеныСрезПоследних.Номенклатура,
ЦеныСрезПоследних.Регистратор КАК ДокументПоступления,
ЦеныСрезПоследних.Цена
ИЗ
РегистрСведений.ЦеныНоменклатурыКонтрагентов.СрезПоследних(&Период, ) КАК ЦеныСрезПоследних
Этот метод предпочтителен, если включена соответствующая настройка регистрации цен. Основное преимущество здесь в том, что платформа 1С на уровне СУБД уже оптимизировала получение последней записи для каждой комбинации измерений (Номенклатура, Контрагент, ТипЦен).
Если нам не критична точность по времени (секундам), а достаточно просто получить «какой-то последний» документ, можно применить упрощенную схему с группировкой по МАКСИМУМ(Ссылка). Выясним причину, почему это работает: ссылки в 1С (GUID) для документов одного типа, созданных последовательно, обычно возрастают, хотя это и не гарантируется на 100%. Однако в рамках запроса МАКСИМУМ(Ссылка) вернет один уникальный идентификатор для каждой группы номенклатуры.
Пример упрощенного запроса:
ВЫБРАТЬ
ПоступлениеТовары.Номенклатура КАК Номенклатура,
МАКСИМУМ(ПоступлениеТовары.Ссылка) КАК ПоследнийДокумент
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТовары
ГДЕ
ПоступлениеТовары.Ссылка.Проведен = ИСТИНА
СГРУППИРОВАТЬ ПО
ПоступлениеТовары.Номенклатура
Внимание: Этот метод может вернуть некорректный результат, если документы вводились «задним числом» или переносились из других баз. В таких случаях документ с более поздней датой может иметь «меньшую» ссылку.
Рассмотрим основные рекомендации, которые помогут избежать зависания системы при выполнении подобных запросов:
Номенклатура (и Ссылка, если по ней идет соединение). Это значительно ускоряет работу JOIN на стороне SQL-сервера.ПоступлениеТоваровУслуг.Товары за 10 лет может привести к чрезмерной нагрузке на дисковую подсистему.ВЫБРАТЬ ПЕРВЫЕ 1... УПОРЯДОЧИТЬ ПО Дата УБЫВ внутри соединения не позволит вам сопоставить документ конкретной номенклатуре из внешнего запроса. Платформа просто выберет один самый последний документ из всей таблицы и попытается соединить его со всеми строками номенклатуры.Дата лучше использовать МоментВремени. Однако помните, что в языке запросов нельзя использовать функцию МАКСИМУМ напрямую к полю МоментВремени. В этом случае используется связка Дата + Ссылка.Подводя итог, мы выяснили, что наиболее надежным и универсальным способом получения одного документа по каждой номенклатуре является трехэтапный запрос с использованием временных таблиц. Он обеспечивает корректную работу даже при наличии нескольких документов в одной секунде и позволяет легко дополнять результат любыми данными из документов и справочников.