Как реализовать аналог функции First в SQL запросах 1sqlite для 1С 7.7

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При переходе с разработки запросов в среде MS Access на работу с 1sqlite в среде 1С 7.7 программисты часто сталкиваются с тем, что привычная функция First() оказывается недоступна. Попытка выполнить запрос вида SELECT First(Цена) FROM Таблица приводит к ошибке "no such function: First". В рамках данной статьи мы подробно разберем, почему это происходит, и проанализируем несколько эффективных способов решения этой задачи средствами движка SQLite.

Почему функция First не работает в 1sqlite

Прежде всего, выясним причину отсутствия данной функции. Функция First() (как и Last()) является специфическим расширением диалекта SQL от Microsoft (Access/Jet). Она возвращает значение из первой записи физического набора данных. Однако в стандарте SQL и в большинстве промышленных СУБД (SQLite, SQL Server, PostgreSQL) понятие "первой записи" без явного указания порядка сортировки ORDER BY не имеет смысла, так как реляционные таблицы представляют собой неупорядоченные наборы данных.

Рассмотрим альтернативные методы, которые позволяют получить аналогичный результат в 1sqlite, сохраняя корректность данных и высокую скорость выполнения запроса.

Способ 1: Использование специфики SQLite (Bare Columns)

Это самый простой и элегантный способ, доступный именно в SQLite (начиная с версии 3.7.11). Проанализируем ситуацию: нам нужно найти минимальную цену для каждого товара и при этом узнать, какому магазину эта цена соответствует. В других СУБД использование обычного поля (не входящего в GROUP BY) вместе с агрегатной функцией MIN() вызвало бы ошибку или вернуло бы произвольное значение. Но в SQLite реализовано правило "голых столбцов" (bare columns).

Если в запросе используется MIN() или MAX(), SQLite гарантирует, что значения остальных полей в строке будут взяты именно из той физической записи, которая содержала это минимальное или максимальное значение. Посмотрим на пример кода:


// Пример получения минимальной цены и соответствующего ей магазина
ТекстЗапроса = "
|SELECT
|    Товар,
|    MIN(Цена) as МинимальнаяЦена,
|    Магазин
|FROM
|    Цены
|GROUP BY
|    Товар
|";

В данном случае для каждого товара будет найдена строка с минимальной ценой, и в поле Магазин попадет именно тот магазин, который указан в этой конкретной строке. Это прямое и эффективное решение задачи без использования функции First().

Способ 2: Классический подход через подзапросы и JOIN

Если вы хотите написать запрос, который будет максимально соответствовать стандартам SQL и будет понятен любому разработчику, используйте связывание таблицы с ее агрегированным представлением. Этот метод полезен, когда логика выбора "первого" значения сложнее, чем просто поиск минимума.

Разберем этот метод по шагам:

  1. Создаем подзапрос, который находит минимальную цену для каждого товара.
  2. Соединяем (JOIN) основную таблицу с этим подзапросом по двум полям: по товару и по цене.
  3. Если одному товару с минимальной ценой соответствует несколько магазинов, применяем дополнительную группировку.

// Универсальный способ через подзапрос
ТекстЗапроса = "
|SELECT
|    T1.Товар,
|    T1.Цена,
|    MIN(T2.Магазин) as Магазин
|FROM (
|    SELECT Товар, MIN(Цена) as МинЦена
|    FROM ТаблицаЦен
|    GROUP BY Товар
|) as T1
|INNER JOIN ТаблицаЦен as T2 ON T2.Товар = T1.Товар AND T2.Цена = T1.МинЦена
|GROUP BY T1.Товар, T1.Цена
|";

Здесь мы гарантированно получаем уникальную строку для каждого товара. Использование MIN(T2.Магазин) во внешнем запросе позволяет "схлопнуть" записи, если одна и та же минимальная цена зафиксирована в нескольких магазинах одновременно.

Способ 3: Оконные функции (FIRST_VALUE)

Если используемая версия 1sqlite базируется на SQLite версии 3.25 или выше, нам становятся доступны оконные функции. Это мощный инструмент, который позволяет избежать сложной логики с подзапросами. Рассмотрим применение функции FIRST_VALUE.

Важно помнить, что оконные функции требуют обязательного наличия секции OVER. Выясним, как это выглядит в коде:


// Использование оконной функции для получения первого магазина в группе
ТекстЗапроса = "
|SELECT DISTINCT
|    Товар,
|    FIRST_VALUE(Магазин) OVER (
|        PARTITION BY Товар
|        ORDER BY Цена ASC, Магазин ASC
|    ) as ПервыйМагазин,
|    MIN(Цена) OVER (PARTITION BY Товар) as МинЦена
|FROM
|    Цены
|";

Обратите внимание: если при использовании этого кода вы получаете ошибку "misuse of window function", значит, либо версия движка слишком стара, либо функция вызвана без корректного определения окна (секции OVER). Для решения простых задач с минимальными ценами первый способ (Bare Columns) остается предпочтительным из-за своей простоты и производительности.

Работа с представлениями (VIEW) в 1sqlite

В процессе обсуждения часто возникает вопрос об использовании CREATE VIEW. Представления удобны для сохранения логики сложных запросов. Однако при работе с ними в 1sqlite есть свои нюансы. Проанализируем типичный сценарий использования:


// Правильный цикл работы с VIEW
ТекстЗапроса1 = "DROP VIEW IF EXISTS v_MinPrices";
Запрос.ВыполнитьЗапрос(ТекстЗапроса1);

ТекстЗапроса2 = "
|CREATE VIEW v_MinPrices AS
|SELECT Товар, MIN(Цена) as Цена
|FROM Прайс
|GROUP BY Товар
|";
Запрос.ВыполнитьЗапрос(ТекстЗапроса2);

Выясним причину использования DROP VIEW. В SQLite команда CREATE VIEW выдает ошибку, если объект с таким именем уже существует. В отличие от таблиц, VIEW часто создаются динамически в процессе работы обработки 1С, поэтому предварительное удаление (или использование CREATE VIEW IF NOT EXISTS) является обязательным.

Что касается производительности: создание представления практически мгновенно, так как SQLite не выполняет сам запрос в этот момент, а лишь сохраняет его определение. Время будет затрачено только при финальной выборке SELECT * FROM v_MinPrices. Оптимизатор SQLite достаточно умен, чтобы "прокидывать" фильтры из внешнего запроса внутрь представления, поэтому использование VIEW не замедляет работу системы.

Подведение итогов

Для эффективной замены функции First() в 1sqlite мы рекомендуем:

  1. Использовать первый способ (прямой выбор поля вместе с MIN/MAX), если вам нужны данные из той же строки, где находится экстремум. Это "фишка" SQLite, которая работает быстро и надежно.
  2. Использовать подзапросы с JOIN, если требуется полная совместимость с другими диалектами SQL.
  3. Всегда явно указывать ORDER BY, если под "первой записью" подразумевается логический порядок (например, по дате или ID), так как физический порядок строк в базе может измениться.

Применяя эти подходы, вы сможете создавать производительные и стабильные отчеты в 1С 7.7, используя все преимущества современного движка SQLite.

← На главную