Как выбрать первый документ за день в запросе 1С, если их несколько?

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

В учетных системах нередко возникает задача: за сутки может быть введен не один документ определенного вида, а два и более (например, несколько сменных отчетов, инкассаций или чеков закрытия смены). В итоговой выборке требуется однозначно определить и пометить самый первый документ цепочки за конкретный день, либо отобрать только его.

Разберем по шагам, как решить эту задачу в среде 1С:Предприятие 7.7 при работе с прямыми SQL-запросами через компонент 1С++, а также выясним, как аналогичная логика реализуется в запросах платформы 1С:Предприятие 8.

Особенности позиционирования документов в 1С 7.7

Проанализируем структуру хранения документов в таблице журнала _1SJOURN платформы 7.7. Для хронологической сортировки в ней используется составное поле DATE_TIME_IDDOC. Это строковое поле фиксированной длины (23 символа), которое включает в себя:

  1. Первые 8 символов — дата в формате ГГГГММДД;
  2. Следующие 6 символов — время документа в формате ЧЧММСС;
  3. Последние 9 символов — уникальный идентификатор документа IDDOC в 36-ричной системе счисления.

Благодаря такой структуре лексикографический порядок строк полностью совпадает с реальной позицией документа на временной оси 1С. Строковый минимум MIN(DATE_TIME_IDDOC) всегда точно указывает на самый первый документ за выбранный период.

Вариант 1. Получение первого документа за конкретный день (прямой SQL в 1С++)

Если перед нами стоит задача получить ровно один первый документ за конкретную известную дату, достаточно использовать ограничение количества строк и правильную сортировку по возрастанию позиции:


SELECT TOP 1
    iddoc AS [Док $Документ.СменныйОтчет]
FROM
    _1sjourn (NOLOCK)
WHERE
    DATE_TIME_IDDOC >= :НачДата AND DATE_TIME_IDDOC <= :КонДата
    AND IDDocDef = $ВидДокумента.СменныйОтчет
ORDER BY
    DATE_TIME_IDDOC ASC

Обратим внимание на условие по дате: прямое сравнение DATE_TIME_IDDOC = :НачДата приведет к ошибке, так как параметр даты содержит только 8 символов, а поле в базе включает еще время и идентификатор. Поэтому всегда следует использовать строковый срез LEFT(DATE_TIME_IDDOC, 8) = :НачДата либо задавать условие диапазона времени с начала по конец суток.

Вариант 2. Разметка всех первых документов за каждый день периода (1С 7.7 + 1С++)

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

Для этого сгруппируем данные по дням, найдем минимальную позицию и свяжем результат с основной таблицей журналов:


SELECT
    j.iddoc AS [Док $Документ.СменныйОтчет],
    LEFT(j.DATE_TIME_IDDOC, 8) AS [ДатаДок $Дата],
    CASE 
        WHEN min_docs.DATE_TIME_IDDOC IS NOT NULL THEN 1 
        ELSE 0 
    END AS ФлагПервыйЗаДень
FROM
    _1sjourn j (NOLOCK)
LEFT JOIN
    (
        SELECT
            LEFT(DATE_TIME_IDDOC, 8) AS День,
            MIN(DATE_TIME_IDDOC) AS DATE_TIME_IDDOC
        FROM
            _1sjourn (NOLOCK)
        WHERE
            IDDocDef = $ВидДокумента.СменныйОтчет
            AND DATE_TIME_IDDOC BETWEEN :НачДата AND :КонДата
        GROUP BY
            LEFT(DATE_TIME_IDDOC, 8)
    ) min_docs ON j.DATE_TIME_IDDOC = min_docs.DATE_TIME_IDDOC
WHERE
    j.IDDocDef = $ВидДокумента.СменныйОтчет
    AND j.DATE_TIME_IDDOC BETWEEN :НачДата AND :КонДата
ORDER BY
    j.DATE_TIME_IDDOC ASC

Если используется современная версия Microsoft SQL Server, ту же самую задачу можно решить еще компактнее с помощью оконной функции ROW_NUMBER():


SELECT
    j.iddoc AS [Док $Документ.СменныйОтчет],
    CASE 
        WHEN ROW_NUMBER() OVER (
            PARTITION BY LEFT(j.DATE_TIME_IDDOC, 8) 
            ORDER BY j.DATE_TIME_IDDOC ASC
        ) = 1 THEN 1 
        ELSE 0 
    END AS ФлагПервыйЗаДень
FROM
    _1sjourn j (NOLOCK)
WHERE
    j.IDDocDef = $ВидДокумента.СменныйОтчет
    AND j.DATE_TIME_IDDOC BETWEEN :НачДата AND :КонДата
ORDER BY
    j.DATE_TIME_IDDOC ASC

Вариант 3. Реализация аналогичной логики в 1С:Предприятии 8

Выясним, как решается подобная задача на платформе 1С 8. В стандартном языке запросов 1С агрегатные функции МИНИМУМ() и МАКСИМУМ() нельзя применять к типу МоментВремени или к ссылкам на документы. Кроме того, несколько документов могут быть записаны на одну и ту же секунду.

Посмотрим на классическое двухэтапное решение через временные таблицы с группировкой по дню:


ВЫБРАТЬ
    СменныйОтчет.Ссылка КАК Ссылка,
    НАЧАЛОПЕРИОДА(СменныйОтчет.Дата, ДЕНЬ) КАК День,
    СменныйОтчет.Дата КАК Дата
ПОМЕСТИТЬ ВТ_Документы
ИЗ
    Документ.СменныйОтчет КАК СменныйОтчет
ГДЕ
    СменныйОтчет.Дата МЕЖДУ &НачДата И &КонДата
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Документы.День КАК День,
    МИНИМУМ(ВТ_Документы.Дата) КАК МинДата
ПОМЕСТИТЬ ВТ_Минимумы
ИЗ
    ВТ_Документы КАК ВТ_Документы
СГРУППИРОВАТЬ ПО
    ВТ_Документы.День
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Документы.Ссылка КАК Документ,
    ВЫБОР
        КОГДА ВТ_Минимумы.МинДата ЕСТЬ НЕ NULL
            ТОГДА ИСТИНА
        ИНАЧЕ ЛОЖЬ
    КОНЕЦ КАК ЭтоПервыйДокумент
ИЗ
    ВТ_Документы КАК ВТ_Документы
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Минимумы КАК ВТ_Минимумы
        ПО ВТ_Документы.День = ВТ_Минимумы.День
        И ВТ_Документы.Дата = ВТ_Минимумы.МинДата
УПОРЯДОЧИТЬ ПО
    ВТ_Документы.Дата ВОЗР

Если существует вероятность записи документов в одну секунду, для исключения дублирования признака первого документа во временной таблице минимумов берут дополнительно МИНИМУМ(Ссылка) с отбором по минимальной дате.

Вариант 4. Постобработка выборки в коде 1С

Если результат запроса все равно обходится циклом в коде модуля, вычисление первого документа за сутки часто проще и быстрее выполнить программно, без усложнения структуры SQL-запроса:


Выборка = Запрос.Выполнить().Выбрать();
ТекущийДень = ДАТА(1, 1, 1);

Пока Выборка.Следующий() Цикл
    ДеньДокумента = НачалоПериода(Выборка.Дата, "День");
    Если ДеньДокумента <> ТекущийДень Тогда
        // Это гарантированно первый документ за текущие сутки
        ТекущийДень = ДеньДокумента;
        ЭтоПервый = Истина;
    Иначе
        ЭтоПервый = Ложь;
    КонецЕсли;
    
    // Дальнейшая обработка строки
КонецЦикла;

Главным условием для такого алгоритма является обязательное упорядочивание выборки запроса по возрастанию даты и момента времени.

← На главную