SQL повторение существует подзапрос - PullRequest
0 голосов
/ 09 марта 2020

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

У меня есть запрос работает, и он выглядит примерно так:

SELECT
     a.col_a
    ,a.col_b
    ,a.col_c
    ,b.col_a
    ,b.col_b
    ,CASE WHEN EXSISTS (SELECT a_sub.col_a
                        FROM a_sub
                        INNER JOIN b_sub
                          ON a_sub.x = b_sub.x
                        WHERE
                          b.col_a = b_sub.col_a
                          AND b.col_b <> b_sub.col_b
                          AND a.date > a_sub.date
                          AND a.date <= DATEADD(d, 28, a_sub.date)
                          AND a.col_c = a_sub.col_c
                          AND (a.col_d IS NULL OR a.col_d <> 7)
                          AND (a_sub.col_d IS NULL OR a_sub.col_d <> 7)
                        ) THEN 'Yes'
                          ELSE 'No'
                          END AS IsRepeat28
FROM a
INNER JOIN b ON a.x = b.x

Это не особенно быстро, но и не слишком медленно, чтобы быть проблемой. Проблема в том, что мне нужно повторить вышеуказанный подзапрос WHEN EXISTS, но для периодов 21, 14, 7 и 1 дня, а также для 5 повторений - тогда время запроса увеличивается примерно с 15 секунд до 10 минут.

I также необходимо скорректировать даты, а также пометить строки, которые являются «IsRepeat», есть также версия для «HasRepeat», которая основана на

AND a_sub.date > a.date AND a_sub.date <= DATEADD(d, 28, a.date)

вместо исходного

AND a.date > a_sub.date AND a.date <= DATEADD(d, 28, a_sub.date)

Это означает, что у нас есть 10 из этих подзапросов EXISTS, и это занимает почти 45 минут.

Мой вопрос действительно двойственный; Есть ли более эффективный способ написания этого подзапроса, и есть ли лучший способ повторить его, вместо того, чтобы фактически один и тот же код был записан 10 раз?

1 Ответ

0 голосов
/ 10 марта 2020

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

Сначала разделите ваш основной прогноз как шаг 1, либо на временную таблицу, либо на переменную таблицы, в зависимости от того, сколько записей у вас есть. , Кэширование каждого набора, который вам нужен в производственном запросе, позволяет вам измерять размер загрузки как счетчик выбора (*).

    insert into @mytablevariable values
    SELECT
       a.col_a,
       a.col_b,
       a.col_c,
       b.col_a,
       b.col_b
       -- build this column later
    FROM a
    INNER JOIN b ON a.x = b.x

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

    insert into @mysub values
    SELECT a_sub.col_a
    FROM a_sub
    INNER JOIN b_sub
       ON a_sub.x = b_sub.x
       AND b.col_b <> b_sub.col_b

Мы специально создали отходы, чтобы справиться с ними, изменив @mysub в соответствии с вашими требованиями к обработке. Наша цель - исключить работу, выбрав правильные данные в правильном порядке.

В этом процессе больше всего выделяется предложение WHERE о том, что поле даты находится в пределах диапазона дат.

    AND a.date > a_sub.date AND a.date <= DATEADD(d, 28, a_sub.date)

Естественно, таблицы не индексируются по значениям столбцов даты, и поэтому при этом сравнении WHERE должно выполняться полное сканирование таблицы, или то, что мы называем Extractor. Экстрактор извлекает часть (подмножество) данных из набора. Поскольку этот экстрактор находится в подзапросе, который выполняется для каждого результата внешней записи, как и любой другой вложенный процесс , вся таблица проверяется снова и снова. Мы всегда хотим минимизировать извлечение до одного прохода, что делается только тогда, когда мы заканчиваем sh организацию критериев на экстракторе. Никогда не вкладывайте экстракторы.

Во-первых, минимизируйте размер результатов экстрактора @mysub с помощью диапазона значений даты, необходимых во внешнем запросе @mytablevariable.

    insert into @mysub values
    SELECT a_sub.col_a
    FROM a_sub
    INNER JOIN b_sub
       ON a_sub.x = b_sub.x
       AND b.col_b <> b_sub.col_b
       and a_sub.date <= DATEADD(d, 28, select min(a.date) from @mytablevariable)
       and a_sub.date > (select max(a.date) from @mytablevariable)

Затем можно спроектировать @mysub. в ваш 21-дневный набор, и меньший набор снова для остальных. В оставшейся части предложения WHERE разверните @mysub столбцами, которые вы сравниваете. Чтобы создать конечный продукт, выберите все из первой таблицы и выполните один поиск для каждой записи следующим образом.

    select 
        *,
        case 
            when b.col_a in (select col_a from @mysub28) then 'Yes'
            else 'No'
        end as IsRepeat28,
        case 
            when b.col_a in (select col_a from @mysub21) then 'Yes'
            else 'No'
        end as IsRepeat21
        -- rinse, repeat
    from 
    @mytablevariable

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

...