2013-06-21 2 views
3

У меня есть запрос в моей производственной среде, которая требует много времени для выполнения. Я не писал этот запрос, но должен найти способ сделать это быстрее, так как он вызывает большую проблему с производительностью на данный момент. Мне нужно заменить NOT IN на Left Join, но не уверен, как его переписать. Это выглядит следующим образом на данный моментПереписать SQL-запрос - мне нужно заменить NOT IN на Join

SELECT TOP 1 IT.ITEMID 
FROM (SELECT CAST(ITEMID AS NUMERIC) + 1 ITEMID 
     FROM Items 
     WHERE ISNUMERIC(ITEMID) = 1 
       AND CAST(ITEMID AS NUMERIC) >= 50000) IT 
WHERE IT.ITEMID NOT IN (SELECT CAST(ITEMID AS NUMERIC) ITEMID 
         FROM Items 
         WHERE ISNUMERIC(ITEMID) = 1) 
ORDER BY IT.ITEMID 

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

+2

'НЕ в' к' LEFT JOIN/IS NULL' (или 'NOT EXISTS') не поможет, если у вас есть нет индексов, которые будут использоваться в запросе. –

+3

LEFT JOIN/IS NULL обычно медленнее, чем NOT IN/NOT EXISTS. Как сказал @ypercube, это проблема индекса, которая не поддерживается CAST и fucntions в предикатах, которые в любом случае недействительны для использования индекса. – gbn

+3

Также 'ISNUMERIC (ITEMID) = 1 И CAST (ITEMID AS NUMERIC)> = 50000' не делает того, что вы надеетесь, что это будет сделано, потому что a) нет гарантии на порядок, в котором предикаты оцениваются, и b) просто потому что 'ISNUMERIC' возвращает 1, нет гарантии, что вы можете присвоить значение' NUMERIC'. –

ответ

6

Попробуйте один -

;WITH cte AS 
(
    SELECT DISTINCT ITEMID = 
       CASE WHEN ISNUMERIC(ITEMID) = 1 
        THEN ITEMID 
       END 
    FROM Items 
) 
SELECT TOP 1 ITEMID = ITEMID + 1 
FROM cte t 
WHERE ITEMID >= 50000 
    AND NOT EXISTS(
      SELECT 1 
      FROM cte t2 
      WHERE t.ITEMID + 1 = t2.ITEMID 
    ) 
ORDER BY t.ITEMID 
+0

Возможная вариация: перемещение 'WHERE ITEMID> = 50000' внутри cte. –

+0

Этот запрос может по-прежнему терпеть неудачу для не числовых исходных данных. [SQL Fiddle] (http://sqlfiddle.com/#!3/aa418/1) Вы не можете полагаться на 'CAST', происходящий после' WHERE' –

+0

@Martin Smith - да, вы правы. Благодарю. – Devart

5

Как уже упоминалось в комментариях НЕ СУЩЕСТВУЕТ версия запроса, как правило, быстрее в SQLServer, чем LEFT JOIN - для полноты картины, вот обе версии:

левый присоединиться вариант существующего запроса:

with cte as 
(SELECT CAST(it.ITEMID AS NUMERIC) ITEMID 
FROM Items 
WHERE ISNUMERIC(ITEMID) = 1) 
select top 1 i.ITEMID + 1 ITEMID 
FROM cte i 
LEFT JOIN cte ni ON i.ITEMID + 1 = ni.ITEMID 
WHERE i.ITEMID >= 50000 AND ni.ITEMID IS NULL 

Не существует вариант существующего запроса:

with cte as 
(SELECT CAST(it.ITEMID AS NUMERIC) ITEMID 
FROM Items 
WHERE ISNUMERIC(ITEMID) = 1) 
select top 1 i.ITEMID + 1 ITEMID 
FROM cte i 
WHERE i.ITEMID >= 50000 AND NOT EXISTS 
(SELECT NULL 
FROM cte ni 
WHERE i.ITEMID + 1 = ni.ITEMID) 
+0

большое спасибо, я сравнил оба и нашел, что ты абсолютно прав. – Aryan

4

Как @gbn указал на комментарии, отлитого и функции по предикатам что сводит индекс использовать в любом случае, так что нет никакого смысла в преобразовании этого из NOT IN в LEFT JOIN/IS NULL или NOT EXISTS. И NOT EXISTS обычно работает лучше, чем LEFT NULL в SQL-Server.

NOT IN не рекомендуется из-за проблем (ошибочных, неожиданных результатов) при наличии нулей (в сравниваемых столбцах или полученных выражениями) и неэффективных планов из-за недействительности столбцов/выплат.

И ISNUMERIC() не всегда делает то, что вы думаете (как @ Damien_The_Unbeliever отметил в другом комментарии.) Есть случаи, когда результат IsNumeric равен 1, но сбой не выполняется.

Таким образом, разумная вещь - по-моему - добавить еще один столбец в таблицу и преобразовать (значения, которые могут быть преобразованы) в числовые и сохранить их в этом столбце. Затем вы можете написать запрос без кастования и использовать индекс в этом столбце.

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

+0

Спасибо @ypercube за ваше ценное предложение. К сожалению, я не могу изменить базовую таблицу. Все, что мне нужно сделать, это найти способ сделать этот запрос немного лучше. – Aryan

+0

'NULL' не будет отклонен' CAST', и результат большинства вычисленных выражений будет считаться «NULL», доступным SQL Server, поэтому план «NOT IN» будет иметь дополнительную неэффективность по сравнению с «НЕ СУЩЕСТВУЕТ» '(это' NULL' -способность столбцов, а не наличие «NULL', влияющих на план) –

+0

@MartinSmith Правильно, нулевая ошибка влияет на планы. Я имел в виду только возможные неправильные результаты, когда есть нули. Я тоже это добавлю. –

4

Я согласен с @ypercube, что разумная задача - исправить вашу схему.

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

CREATE TABLE #T 
(
ITEMID NUMERIC(18,0) PRIMARY KEY 
        WITH (IGNORE_DUP_KEY = ON) 
)  

INSERT INTO #T 
SELECT CASE WHEN ISNUMERIC(ITEMID) = 1 THEN ITEMID END 
FROM Items 
WHERE CASE WHEN ISNUMERIC(ITEMID) = 1 THEN ITEMID END >= 50000 


SELECT TOP 1 ITEMID+1 
FROM #T T1 
WHERE NOT EXISTS (SELECT * FROM #T T2 WHERE T2.ITEMID = T1.ITEMID +1) 
ORDER BY ITEMID 
+0

спасибо, что я не тестировал это, но хотел бы сделать это в понедельник – Aryan

+0

@Aryan - Да без фактического тестирования на ваших данных трудно понять, что будет лучше, если бы я присоединился к CTE, чтобы присоединиться к слиянию, я бы ожидал, что это будет лучше, так как им придется выполнять полное сканирование и сортировать дважды. Если вложенные циклы, вероятно, зависят от того, сколько строк нужно сканировать до того, как будет найден «TOP 1». –