SQL Server эффективно фильтрует строки, когда время не близко к времени другой таблицы - PullRequest
5 голосов
/ 15 сентября 2010

У меня есть две таблицы, и я ищу строки в одной таблице, где столбец времени равен , а не рядом с любым из значений в столбце времени другой таблицы.(Ближайший определяется как в течение минуты).

Вот пример кода:

create table temp1
(
    id int identity primary key,
    value datetime not null 
)
GO

create index ix_temp1 on temp1(value, id);
GO

set nocount on
insert temp1 (value) values (DATEADD(second, rand() * 1000000, '20100101'))
GO 15000

Таблица temp2 настроена идентично:

create table temp2
(
    id int identity primary key,
    value datetime not null 
)
GO

create index ix_temp2 on temp2(value, id);
GO

set nocount on
insert temp2 (value) values (DATEADD(second, rand() * 1000000, '20100101'))
GO 15000

И вот мой первыйвзломать его (что очень неэффективно)

SELECT t1.id, t1.value
FROM temp1 t1
LEFT JOIN temp2 t2
    ON t1.value between DATEADD(MINUTE, -1, t2.value) and DATEADD(MINUTE, 1, t2.value)
WHERE t2.value is null

Я ищу способы сделать это более эффективно.Будут рассмотрены все решения (новые индексы, решение служб SSIS, решения CLR, временные таблицы, курсоры и т. Д.)

Ответы [ 6 ]

4 голосов
/ 15 сентября 2010

LEFT JOIN / IS NULL не так эффективен на SQL Server, как NOT IN или NOT EXISTS, когда столбцы не обнуляются - см. эту ссылку для деталей .

Тем не менее, это:

SELECT t1.id,
       t1.value
  FROM temp1 t1
 WHERE NOT EXISTS(SELECT NULL
                    FROM temp2 t2
                   WHERE t2.value BETWEEN DATEADD(MINUTE, -1, t1.value)  
                                      AND DATEADD(MINUTE, 1, t1.value))

... по-прежнему существует проблема в том, что использование функции (IE: DATEADD) делает индекс бесполезным. Вы изменяете данные столбца (временно, не записывая их обратно в таблицу), пока индекс соответствует исходному значению.

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

  1. способность к прямому сравнению: t1.value = t2.value
  2. возможность использовать индекс при условии, что оптимизатор считает, что он может быть полезен
2 голосов
/ 15 сентября 2010

Ответ переписан

Для вашего исходного запроса изменение условия соединения с

LEFT JOIN temp2 t2
 ON t1.value BETWEEN DATEADD(MINUTE, -1, t2.value) AND DATEADD(MINUTE, 1, t2.value)

на

LEFT JOIN temp2 t2
 ON t2.value BETWEEN DATEADD(MINUTE, -1, t1.value) AND DATEADD(MINUTE, 1, t1.value)

Имеет огромную разницу.

В обоих случаях в качестве внешнего входа для итератора вложенных циклов используется сканирование по temp1.Тем не менее, для первого условие для temp2 не может быть sargable, поэтому необходимо выполнить сканирование всего temp2 для каждой строки в temp1.Для второй версии это может сделать намного более разумный поиск диапазона по индексу, чтобы получить соответствующие строки.

Однако решение Not Exists согласно @ ответу OMG равно более эффективен в SQL Server

Планы выполнения:

(игнорируйте «Стоимость относительно пакета» для второго - оценочные строки не соответствуют действительности, поэтому этот показательвводит в заблуждение)

ExecutionPlans http://img812.imageshack.us/img812/457/executionplans.jpg

2 голосов
/ 15 сентября 2010

Это, кажется, делает это довольно быстро:

SELECT t.id,
       t.value
FROM 
(
   SELECT t1.id, 
          t1.value, 
          (SELECT MIN(temp2.value) FROM temp2 WHERE temp2.value >= t1.value) as theNext, 
          (SELECT MAX(temp2.value) FROM temp2 WHERE temp2.value <= t1.value) as thePrev
   FROM temp1 t1
) t 
WHERE DATEDIFF(second, t.value, t.theNext) > 60 
  AND DATEDIFF(second, t.thePrev, t.value) > 60

и не требует реструктуризации ваших таблиц.

Удостоверьтесь и используйте секунды для сравнения, так как минуты будут округлены. Это выполняется менее чем за секунду на моем компьютере, используя ваши спецификации для создания таблицы.

РЕДАКТИРОВАТЬ: добавлены <= и> = к следующим и предыдущим вычислениям. Это предотвращает ложное срабатывание, где temp1.value равно temp2.value.

0 голосов
/ 15 сентября 2010

Это действительно быстро ..

; WITH Time_CTE (ID, Table1_Time, Table2_Time) КАК ( SELECT t1.id, t1.value AS Table1_Time, t2.value AS Table2_Time ОТ темп1 т1 ВНУТРЕННЯЯ ПОДКЛЮЧЕНИЕ AND MONTH (t1.value) = МЕСЯЦ (t2.value) И ДЕНЬ (t1.value) = ДЕНЬ (t2.value) )

ВЫБРАТЬ TCTE.id, TCTE.Table1_Time FROM Time_CTE TCTE WHERE DATEDIFF (ss, Table1_Time, Table2_Time) <61 ИЛИ DATEDIFF (ss, Table2_Time, Table1_Time) <61 </p>

0 голосов
/ 15 сентября 2010

Я столкнулся с проблемой, аналогичной этой, преобразовав значение DateTime в целое число минут с 1 января 2000 года и записав это значение в столбец в моей таблице базы данных.Поэтому (в вашем случае) таблица будет выглядеть следующим образом:

create table temp2
(
    id int identity primary key,
    timeValue int not null
)

Чтобы сравнить с этой таблицей, просто преобразуйте значение сравнения в целое число минут (для этого я использую пользовательскую функцию)и сравни.

DECLARE @newTime int;
SET @newTime = dbo.fnGetComparisonTime(@DateTimeValue)

Затем получите ваши данные:

SELECT id, timeValue 
FROM temp2
WHERE timeValue NOT BETWEEN (@newTime - 1) AND @newTime;

И функция преобразования времени в целочисленные минуты?

CREATE FUNCTION dbo.fnGetComparisonTime
    (
        @DateTimeValue datetime
    )
RETURNS int
AS
BEGIN
    -- Declarations
    DECLARE @Output int
    DECLARE @StartDate datetime

    SET @StartDate = '2000-01-01 00:00:00'
    SET @Output = DATEDIFF(minute, @StartDate, @ReportDateTime)    

    -- And we're done!
    RETURN @Output

END

Вы можете поиграть с оператором SELECT,конечно, чтобы получить результаты, которые вы хотите.Преобразование значений DateTime в минуты - это ПУТЬ быстрее, чем обработка с датами напрямую.

Вы можете спросить - есть ли проблема Y2K с этим?(В конце концов, у вас не хватит минут на 31 ^ 2 - 1 минуту.) Да, примерно через 7000 лет.Обязательно тщательно документируйте свой код ....

0 голосов
/ 15 сентября 2010

Мое первое предложение - дать это одному из разработчиков и попросить их написать алгоритм на C или C #

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...