Получение актуальной цены товара: оптимальные методы в 1С и SQL

Бесплатная консультация юриста

+7 (800) 301-07-30

Реклама jurik.ru

Получение актуальной (последней) цены на товар — одна из самых частых задач при работе с базами данных‚ будь то 1С:Предприятие‚ PostgreSQL‚ MS SQL Server или MySQL․ В этой статье мы подробно разберем оптимальные способы решения этой задачи одним запросом‚ рассмотрим особенности виртуальных таблиц 1С и стандартные SQL-конструкции (оконные функции‚ подзапросы‚ TOP/LIMIT)․


Содержание

  1. Подход в 1С:Предприятие — Виртуальная таблица «СрезПоследних»
  2. Стандартные SQL-методы (PostgreSQL‚ MS SQL‚ MySQL‚ Oracle)
    1. Оконные функции (ROW_NUMBER) — современный стандарт
    2. Подзапрос с агрегацией (MAX даты)
    3. TOP 1 / LIMIT 1 / FETCH FIRST — для единичных проверок
    4. CROSS APPLY / LATERAL JOIN, для присоединения к списку товаров
  3. Разбор типичных сценариев из практики
    1. Сценарий: Таблица цен с 25 млн записей (JOIN с таблицей периодов)
    2. Сценарий: Получение последней цены за каждый час/день
    3. Сценарий: Пустой регистр/таблица (обработка NULL)
  4. Сравнение производительности и лучшие практики

Подход в 1С:Предприятие — Виртуальная таблица «СрезПоследних»

В платформе 1С:Предприятие 8 для регистров сведений (например‚ ЦеныНоменклатуры) существует мощный механизм — виртуальная таблица СрезПоследних․ Она позволяет получить актуальные значения реквизитов на заданный момент времени без написания сложных подзапросов․

Синтаксис

ВЫБРАТЬ
 ЦеныНоменклатурыСрезПоследних․Номенклатура КАК Номенклатура‚
 ЦеныНоменклатурыСрезПоследних․Цена КАК Цена‚
 ЦеныНоменклатурыСрезПоследних․ВидЦены КАК ВидЦены
ИЗ
 РегистрСведений․ЦеныНоменклатуры․СрезПоследних(&ДатаСреза‚ Номенклатура В (&СписокНоменклатуры) И ВидЦены = &ВидЦены) КАК ЦеныНоменклатурыСрезПоследних

Ключевые параметры

  • Период (Дата среза) — обязательный параметр (обычно передается &ДатаСреза или КОНЕЦПЕРИОДА(&ДатаСреза‚ ДЕНЬ))․ Срез берется на конец этой секунды․
  • Отбор (Condition) — необязательный‚ но критически важен для производительности․ Позволяет отфильтровать данные до чтения с диска (по Номенклатуре‚ Виду цен‚ Характеристикам и т․д․)․

Пример с параметрами из вашего набора данных

Если у нас три параметра: Дата‚ Номенклатура‚ ТипЦен:

ВЫБРАТЬ
 ЦеныНоменклатурыСрезПоследних․Номенклатура‚
 ЦеныНоменклатурыСрезПоследних․Цена
ИЗ
 РегистрСведений․ЦеныНоменклатуры․СрезПоследних(&Дата‚ Номенклатура = &Номенклатура И ТипЦен = &ТипЦен) КАК ЦеныНоменклатурыСрезПоследних

Важно: Всегда указывайте отбор по измерениям (Номенклатура‚ ТипЦен) во втором параметре СрезПоследних․ Это позволяет платформе использовать индексы регистра эффективно‚ не сканируя всю таблицу․


Стандартные SQL-методы (Для внешних БД и прямых SQL-запросов)

Если вы работаете напрямую с СУБД (PostgreSQL‚ SQL Server‚ MySQL‚ Oracle) или используете прямые SQL-запросы в 1С (через COMConnector или внешние отчеты)‚ подходы отличаются․

Оконные функции: ROW_NUMBER — Золотой стандарт (SQL:2003+)

Это самый производительный и читаемый способ для получения последней цены для списка товаров сразу․

WITH RankedPrices AS (
 SELECT
 p․ProductID‚
 p․Price‚
 p․PriceDate‚
 ROW_NUMBER OVER (PARTITION BY p․ProductID ORDER BY p․PriceDate DESC‚ p․ID DESC) as rn
 FROM tb_Price p
 -- WHERE p․PriceDate <= @TargetDate -- опционально: срез на дату
)
SELECT ProductID‚ Price‚ PriceDate
FROM RankedPrices
WHERE rn = 1;
  • PARTITION BY ProductID — разбивает данные на группы по товарам․
  • ORDER BY PriceDate DESC‚ ID DESC, сортирует внутри группы: сначала по дате (новые выше)‚ затем по ID (на случай одинаковых дат)․
  • WHERE rn = 1 — оставляет только самую последнюю запись для каждого товара․

Подзапрос с MAX(Дата) — Классика

Работает везде‚ но на больших таблицах может быть медленнее оконных функций из-за двойного сканирования или неэффективных планов выполнения․

SELECT p․ProductID‚ p․Price‚ p․PriceDate
FROM tb_Price p
INNER JOIN (
 SELECT ProductID‚ MAX(PriceDate) as MaxDate
 FROM tb_Price
 GROUP BY ProductID
) latest ON p․ProductID = latest․ProductID AND p․PriceDate = latest․MaxDate
-- Важно: если даты дублируются‚ вернется несколько строк․ Нужен доп․ отбор по MAX(ID)
-- AND p․ID = (SELECT MAX(ID) FROM tb_Price WHERE ProductID = p․ProductID AND PriceDate = p․PriceDate);

TOP 1 / LIMIT 1 / FETCH FIRST — Для одного товара

Идеально‚ если нужно узнать цену конкретного товара (параметризованный запрос)․

  • MS SQL / Access: SELECT TOP 1 Price FROM tb_Price WHERE ProductID = @ID ORDER BY PriceDate DESC‚ ID DESC;
  • PostgreSQL / MySQL / SQLite: SELECT Price FROM tb_Price WHERE ProductID = @ID ORDER BY PriceDate DESC‚ ID DESC LIMIT 1;
  • Oracle 12c+ / Standard SQL: SELECT Price FROM tb_Price WHERE ProductID = @ID ORDER BY PriceDate DESC‚ ID DESC FETCH FIRST 1 ROW ONLY;

CROSS APPLY (SQL Server) / LATERAL JOIN (PostgreSQL), JOIN к списку товаров

Когда у вас есть список товаров (например‚ из корзины или заказа) и нужно подтянуть к каждому последнюю цену — это лучший синтаксис․

-- SQL Server (CROSS APPLY)
SELECT t․ProductID‚ ca․Price‚ ca․PriceDate
FROM tb_Products t
CROSS APPLY (
 SELECT TOP 1 Price‚ PriceDate
 FROM tb_Price p
 WHERE p․ProductID = t․ProductID
 ORDER BY p․PriceDate DESC‚ p․ID DESC
) ca;

-- PostgreSQL (LATERAL JOIN)
SELECT t․ProductID‚ l․Price‚ l․PriceDate
FROM tb_Products t
LEFT JOIN LATERAL (
 SELECT Price‚ PriceDate
 FROM tb_Price p
 WHERE p․ProductID = t․ProductID
 ORDER BY p․PriceDate DESC‚ p․ID DESC
 LIMIT 1
) l ON true;

Разбор типичных сценариев из практики

Сценарий: Большая таблица цен (25 млн записей) + JOIN с таблицей периодов

Условие: Таблица tb․Price (25 млн строк)‚ поля: ID_Brewery‚ ProductCode‚ Price‚ PeriodID․ Таблица tb․Period: PeriodID‚ DatePeriod․ Нужно выбрать товары по последней дате с ценой․

Оптимальное решение (Оконная функция):

WITH LastPeriod AS (
 -- 1․ Находим ID последнего периода (один раз)
 SELECT MAX(PeriodID) as LastPeriodID FROM tb_Period
)‚
Ranked AS (
 SELECT
 p․ID_Brewery‚
 p․ProductCode‚
 p․Price‚
 per․DatePeriod‚
 ROW_NUMBER OVER (PARTITION BY p․ID_Brewery‚ p․ProductCode ORDER BY p․PeriodID DESC) as rn
 FROM tb_Price p
 INNER JOIN tb_Period per ON p․PeriodID = per․PeriodID
 -- Оптимизация: сразу отсекаем старые периоды‚ если их много
 WHERE p․PeriodID = (SELECT LastPeriodID FROM LastPeriod) 
)
SELECT ID_Brewery‚ ProductCode‚ Price‚ DatePeriod
FROM Ranked
WHERE rn = 1;

Почему так: Мы не делаем `JOIN` всей таблицы цен с периодами‚ а сразу фильтруем по `PeriodID = MAX(PeriodID)`․ Если нужен срез на конкретную дату‚ используйте `WHERE per․DatePeriod <= @TargetDate` и `ORDER BY per․DatePeriod DESC`․

Сценарий: Последняя цена за каждый час/день (Тайм-серии)

Задача: Получить по одной строке в час (последнюю цену за час) для конкретного полета/товара․

SELECT
 DATE_TRUNC('hour'‚ PriceDateTime) as HourSlot‚ -- или DATEADD(hour‚ DATEDIFF(hour‚ 0‚ PriceDateTime)‚ 0) в MS SQL
 (ARRAY_AGG(Price ORDER BY PriceDateTime DESC))[1] as LastPriceInHour -- PostgreSQL specific
 -- Или стандартный способ через ROW_NUMBER:
FROM (
 SELECT
 Price‚
 PriceDateTime‚
 ROW_NUMBER OVER (PARTITION BY DATE_TRUNC('hour'‚ PriceDateTime) ORDER BY PriceDateTime DESC) as rn
 FROM tb_Price
 WHERE ProductID = 'AB12' AND PriceDateTime >= @StartDate AND PriceDateTime < @EndDate
) t
WHERE rn = 1
ORDER BY HourSlot;

Сценарий: Обработка пустых таблиц/регистров (NULL)

Как отмечено в исходных данных: если в регистре нет записей‚ СрезПоследних в 1С вернет пустую выборку (не NULL)․ В SQL агрегатные функции (MAX) вернут NULL․

1С (Запрос): Результат просто пуст․ При получении значения в коде: `Цена = Результат․Цена ?? 0;`․

SQL (COALESCE):

SELECT COALESCE((
 SELECT TOP 1 Price FROM tb_Price WHERE ProductID = @ID ORDER BY PriceDate DESC
)‚ 0) as Price;

Сравнение производительности и лучшие практики

Метод СУБД / Платформа Сложность Производительность на больших данных Когда использовать
СрезПоследних 1С:Предприятие 8 Низкая (1 строка) Максимальная (использует индексы регистра нативно) Всегда в 1С для регистров сведений․
ROW_NUMBER OVER PG‚ MS SQL‚ Oracle‚ MySQL 8+ Средняя (CTE) Высокая (один проход‚ параллелизм) Стандарт для выборки "последняя N записей на группу"․
CROSS APPLY / LATERAL MS SQL / PG Средняя Высокая (Index Seek на каждую строку внешней таблицы) Присоединение цены к списку товаров (Order Lines‚ Cart)․
TOP 1 / LIMIT 1 Все Низкая Высокая (для 1 товара) Точечный запрос цены одного SKU․
Подзапрос MAX(Date) + JOIN Все (Legacy) Средняя Низкая/Средняя (2 сканирования‚ проблемы с дублями дат) Старые СУБД без поддержки оконных функций․

Ключевые советы по оптимизации:

  1. Индексы — это закон․ Для таблицы цен идеальный покрывающий индекс:
    • 1С: Автоматически по измерениям + Период․
    • SQL: CREATE INDEX IX_Price_Lookup ON tb_Price (ProductID‚ PriceDate DESC) INCLUDE (Price);
  2. Избегайте функций над колонками в WHERE․ Плохо: `WHERE YEAR(PriceDate) = 2026`․ Хорошо: `WHERE PriceDate >= '2026-01-01' AND PriceDate < '2027-01-01'`․
  3. Типизация параметров․ Всегда передавайте параметры с правильным типом данных (DateTime‚ GUID‚ String)‚ чтобы СУБД не делала неявное преобразование (Implicit Conversion)‚ сбивающее индексы․
  4. Параллелизм․ Оконные функции и `APPLY`/`LATERAL` отлично параллелятся современными оптимизаторами․

Получение последней цены «одним запросом» — задача решаемая элегантно и эффективно при выборе правильного инструмента:

  • В 1С:Предприятие, безусловный лидер: РегистрСведений․Имя․СрезПоследних(Дата‚ Отбор)․ Используйте отбор по измерениям для скорости․
  • В стандартном SQL — используйте Оконные функции (ROW_NUMBER) для выборок по группам товаров и CROSS APPLY / LATERAL JOIN для присоединения к документам/корзинам․
  • Для точечных запросов — TOP 1 ․․․ ORDER BY Date DESC с правильным индексом мгновенно вернет результат даже на сотнях миллионов строк․

Помните‚ что «один запрос» не всегда означает «один SELECT без CTE»․ Использование WITH (CTE) делает код читаемым‚ а оптимизатор — умнее․ Проектируйте индексы под ваши паттерны чтения‚ и проблема «последней цены» перестанет быть узким местом вашей системы․