Как сопоставить строки двух таблиц с похожими датами в запросе 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При интеграции 1С со сторонними системами (например, телефонией, АТС, системами видеонаблюдения или логами внешних сервисов) часто возникает необходимость сопоставить данные по дате и времени. Однако время фиксации одного и того же события в разных источниках может не совпадать на несколько секунд из-за рассинхронизации системных часов, задержек при обработке или специфики фиксации звонка. Разберем, как реализовать сопоставление таких записей с допустимым отклонением времени с помощью запроса и программных алгоритмов.

Проанализируем типовую ситуацию: у нас есть таблица значений с файлами записей звонков (содержащая колонку ДатаФайла) и регистр сведений ДетализацияЗвонков (с колонкой Дата). Требуется связать каждую запись файла со звонком в регистре, если разница между их датами составляет не более $\pm 5$ секунд.

Вариант 1. Соединение таблиц по функции РАЗНОСТЬДАТ

Платформа 1С:Предприятие позволяет использовать сложные логические условия и функции для соединения таблиц в секции ПО. Для вычисления интервала между моментами времени применим функцию РАЗНОСТЬДАТ с точностью до секунды.

Рассмотрим базовый пример формирования временной таблицы и её левого соединения с регистром сведений:


ТаблицаСоответствий = Объект.ТаблицаСоответствий.Выгрузить();

Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТЗ", ТаблицаСоответствий);
Запрос.Текст = "
|ВЫБРАТЬ 
|    ТЗ.ИмяФайла КАК ИмяФайла, 
|    ТЗ.ДатаФайла КАК ДатаФайла
|ПОМЕСТИТЬ ВТ_Файлы
|ИЗ 
|    &ТЗ КАК ТЗ
|;
|/////////////////////////////////////////////////////////////
|ВЫБРАТЬ 
|    ВТ_Файлы.ИмяФайла КАК ИмяФайла, 
|    ВТ_Файлы.ДатаФайла КАК ДатаФайла,
|    ДетализацияЗвонков.Дата КАК ДатаЗвонка,
|    ДетализацияЗвонков.Номер КАК НомерТелефона, 
|    ДетализацияЗвонков.Регистратор КАК Регистратор
|ИЗ 
|    ВТ_Файлы КАК ВТ_Файлы 
|    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДетализацияЗвонков КАК ДетализацияЗвонков
|        ПО РАЗНОСТЬДАТ(ВТ_Файлы.ДатаФайла, ДетализацияЗвонков.Дата, СЕКУНДА) МЕЖДУ -5 И 5
|";

Выборка = Запрос.Выполнить().Выбрать();

В данном запросе условие связи проверяет, что разница между временем файла и временем звонка укладывается в диапазон от -5 до +5 секунд включительно. Конструкция МЕЖДУ -5 И 5 эквивалентна выражению с двумя сравнениями через оператор И, но делает текст запроса более компактным и читаемым.

Особенности производительности и оптимизация условий соединения

Выясним причину, по которой базовый вариант запроса может работать медленно на клиент-серверных базах большого объема. Использование функции РАЗНОСТЬДАТ над полем реальной таблицы базы данных (ДетализацияЗвонков.Дата) делает условие невычисляемым по индексу (Non-SARGable). В этом случае СУБД вынуждена выполнять полное сканирование таблицы регистра (Table Scan) либо проверять условие построчно для всех записей.

Для ускорения выполнения запроса применим следующие оптимизации:

  1. Перенос вычислений на сторону временной таблицы: используем функцию ДОБАВИТЬКДАТЕ к полю временной таблицы, чтобы колонка регистра осталась «чистой» и СУБД смогла применить индекс по дате.
  2. Индексация временных таблиц: явно укажем секцию ИНДЕКСИРОВАТЬ ПО для полей, участвующих в условиях.
  3. Предварительная фильтрация регистра по общему периоду: ограничим выборку из регистра минимальной и максимальной датой из переданного списка файлов.

Посмотрим на оптимизированный текст запроса:


Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТЗ", ТаблицаСоответствий);
Запрос.Текст = "
|ВЫБРАТЬ 
|    ТЗ.ИмяФайла КАК ИмяФайла, 
|    ТЗ.ДатаФайла КАК ДатаФайла
|ПОМЕСТИТЬ ВТ_Файлы
|ИЗ 
|    &ТЗ КАК ТЗ
|ИНДЕКСИРОВАТЬ ПО 
|    ДатаФайла
|;
|/////////////////////////////////////////////////////////////
|ВЫБРАТЬ 
|    ВТ_Файлы.ИмяФайла КАК ИмяФайла, 
|    ВТ_Файлы.ДатаФайла КАК ДатаФайла,
|    ДетализацияЗвонков.Дата КАК ДатаЗвонка,
|    ДетализацияЗвонков.Номер КАК НомерТелефона, 
|    ДетализацияЗвонков.Регистратор КАК Регистратор
|ИЗ 
|    ВТ_Файлы КАК ВТ_Файлы 
|    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДетализацияЗвонков КАК ДетализацияЗвонков
|        ПО ДетализацияЗвонков.Дата МЕЖДУ ДОБАВИТЬКДАТЕ(ВТ_Файлы.ДатаФайла, СЕКУНДА, -5) 
|            И ДОБАВИТЬКДАТЕ(ВТ_Файлы.ДатаФайла, СЕКУНДА, 5)
|";

Проблема неоднозначности и дублирования строк

При соединении по диапазону времени в условиях высокой интенсивности событий (например, в кол-центре) возможна ситуация, когда в интервал $\pm 5$ секунд попадут сразу несколько звонков. Это приведет к дублированию строк в результирующей выборке.

Для устранения неоднозначности рекомендуется:

  1. Добавлять дополнительные критерии связи: если имя файла содержит номер телефона, внутренний номер оператора или уникальный идентификатор канала (Call ID), обязательно включайте их в условие соединения ПО.
  2. Выбирать ближайшую запись: если критериев кроме даты нет, можно рассчитать модуль разницы во времени через ВЫБОР КОГДА и сгруппировать данные по минимальному отклонению.

Вариант 2. Программное сопоставление методом двух указателей

Если требуется обработать большой массив данных без риска дублирования записей при коллизиях времени, эффективным решением является программный обход двух отсортированных таблиц за линейное время $O(N + M)$.

Разберем по шагам, как реализовать этот алгоритм:

  1. Выбираем из базы только записи регистра за нужный период, упорядоченные по возрастанию даты.
  2. Сортируем исходную таблицу файлов по возрастанию даты: ТаблицаФайлов.Сортировать("ДатаФайла Возр").
  3. Выполняем синхронный обход обеих коллекций с помощью индексов:

// Предполагается, что ТаблицаФайлов и ТаблицаЗвонков отсортированы по дате по возрастанию
ИндексФайла = 0;
ИндексЗвонка = 0;
ВсегоФайлов = ТаблицаФайлов.Количество();
ВсегоЗвонков = ТаблицаЗвонков.Количество();

Пока ИндексФайла < ВсегоФайлов И ИндексЗвонка < ВсегоЗвонков Цикл
    СтрокаФайла = ТаблицаФайлов[ИндексФайла];
    СтрокаЗвонка = ТаблицаЗвонков[ИндексЗвонка];
    
    Дельта = СтрокаЗвонка.Дата - СтрокаФайла.ДатаФайла; // Разница в секундах
    
    Если Дельта >= -5 И Дельта <= 5 Тогда
        // Найдено соответствие
        СтрокаФайла.Регистратор = СтрокаЗвонка.Регистратор;
        ИндексФайла = ИндексФайла + 1;
        ИндексЗвонка = ИндексЗвонка + 1;
    ИначеЕсли Дельта < -5 Тогда
        // Звонок произошел значительно раньше файла — переходим к следующему звонку
        ИндексЗвонка = ИндексЗвонка + 1;
    Иначе
        // Звонок произошел позже файла — для текущего файла пары нет
        ИндексФайла = ИндексФайла + 1;
    КонецЕсли;
КонецЦикла;

Такой подход исключает дублирование записей, работает за минимальное время в оперативной памяти и не нагружает СУБД сложными расчетами неточных соединений.

← На главную