Запрос с подзапросом в предложении WHERE сохраняет время ожидания - PullRequest
1 голос
/ 18 мая 2011

У меня есть следующий запрос (слегка измененный для ясности):

CREATE PROCEDURE Kctc.CaseTasks_GetCaseTasks
@CaseNumber int
... other parameters
,@ChangedBefore datetime
,@ChangedAfter datetime
AS
SELECT Kctc.CaseTasks.CaseTaskId
  ...blah blah blah
  FROM Kctc.CaseTasks
  ... some joins here
  WHERE  
  ... some normal where clauses
  AND 
  (
    (@ChangedAfter IS NULL AND @ChangedBefore IS NULL)
    OR
    EXISTS (SELECT *
               FROM Kctc.FieldChanges
               WHERE Kctc.FieldChanges.RecordId = Kctc.CaseTasks.CaseTaskId AND 
                     Kctc.FieldChanges.TableName = 'CaseTasks' AND 
                     Kctc.FieldChanges.DateOfChange BETWEEN
                         ISNULL(@ChangedAfter, '2000/01/01') AND
                         ISNULL(@ChangedBefore, '2050/01/01'))
  )

Этот запрос прерывается всякий раз, когда пользователь указывает значения для @ChangedBefore или @ChangedAfter, поэтому вызывает подзапрос.

Подзапрос проверяет наличие записи в таблице с именем FieldChanges (которая фактически записывает изменения для каждого поля в таблице CaseTasks).

Запросы FieldChanges не очень эффективныпотому что это включает фильтрацию по текстовому полю TableName, которое не проиндексировано.И я знаю, что подзапросы по своей сути неэффективны.

Так что мой вопрос в целом заключается в том, есть ли способ изменить запрос так, чтобы он работал лучше?

Я не могу придумать способ выразить подзапрос как объединение, в то же время возвращая только одну строку CaseTask, когда есть несколько связанных FieldChanges (то есть, сохраняя семантику EXISTS).Я еще не проиндексировал поле TableName таблицы FieldChanges, потому что не решаюсь индексировать текстовые поля.

Так что мне делать?

Ответы [ 4 ]

1 голос
/ 18 мая 2011

Мой первый инстинкт - ограничить набор результатов

SELECT *
FROM Kctc.FieldChanges
WHERE Kctc.FieldChanges.RecordId = Kctc.CaseTasks.CaseTaskId AND
    Kctc.FieldChanges.TableName = 'CaseTasks' AND 
    Kctc.FieldChanges.DateOfChange BETWEEN
        ISNULL(@ChangedAfter, '2000/01/01') AND
        ISNULL(@ChangedBefore, '2050/01/01'

изменено на

SELECT TOP 1 Kctc.FieldChanges.RecordId 
FROM Kctc.FieldChanges
WHERE Kctc.FieldChanges.RecordId = Kctc.CaseTasks.CaseTaskId AND
     Kctc.FieldChanges.TableName = 'CaseTasks' AND 
     Kctc.FieldChanges.DateOfChange BETWEEN
         ISNULL(@ChangedAfter, '2000/01/01') AND
         ISNULL(@ChangedBefore, '2050/01/01'

затем посмотрите на индекс в полях в предложении where

РЕДАКТИРОВАТЬ: Относительно ТОП-1 - вероятно, не дает такой большой выгоды, но не должно причинять вреда, и может помочь избежать сканирования таблицы. Использование одного поля вместо * должно возвращать только этот столбец (я предполагаю, что это НЕ столбец значения NULL)

Дополнительные соображения: объявляйте и устанавливайте локальное значение вместо вещи ISNULL, которая обрабатывается несколько раз:

DECLARE @checkmyafter datetime; -- assumption on my part here on the type
SET @checkmyafter = ISNULL(@ChangedAfter, '2000/01/01');

сделать то же самое с ранее, затем использовать

...
 SELECT TOP 1 Kctc.FieldChanges.RecordId 
    FROM Kctc.FieldChanges
    WHERE Kctc.FieldChanges.RecordId = Kctc.CaseTasks.CaseTaskId AND
         Kctc.FieldChanges.TableName = 'CaseTasks' AND 
         Kctc.FieldChanges.DateOfChange BETWEEN
             @checkmybefore  AND  @checkmyafter 
...

ОДНА БОЛЬШЕ: Проверьте последовательность WHERE xxx AND - используйте кандидат MOST LIKELY, чтобы изолировать FIRST в последовательности, в зависимости от того, какое условие есть, чтобы он мог выйти быстрее. Если это RecordId, то найдите, если TableName лучше, сначала используйте его. Если у одного столбца также есть индекс, считайте, что все остальные равны.

1 голос
/ 18 мая 2011

В качестве первого отрезка вы можете попробовать поместить индекс в таблицу Kctc.FieldChanges в поля RecordId, TableName и DateOfChange (один индекс со всеми тремя полями) и посмотреть, поможет ли это.

Делись и наслаждайся.

0 голосов
/ 18 мая 2011

Добавление SET ARITHABORT ON к хранимой процедуре заставило ее выполнить менее чем за 1 секунду.

Я понятия не имею, что это значит.Предположительно, «перестань беспокоиться».

0 голосов
/ 18 мая 2011

Это не будет "хорошим" решением , но оно может быть лучше, чем то, что происходит сейчас:

SELECT Kctc.CaseTasks.CaseTaskId
  ...blah blah blah
  FROM Kctc.CaseTasks
  ... some joins here
  LEFT JOIN (
     SELECT RecordID
     FROM Kctc.FieldChanges
     WHERE Kctc.FieldChanges.TableName = 'CaseTasks'
     AND Kctc.FieldChanges.DateOfChange BETWEEN
                         ISNULL(@ChangedAfter, '2000/01/01') AND
                         ISNULL(@ChangedBefore, '2050/01/01')
     GROUP BY RecordID
  ) AS MatchingChanges ON Kctc.CaseTasks.RecordId = MatchingChanges.RecordId
  WHERE  
  ... some normal where clauses
     AND (MatchingChanges.RecordID Is Not Null OR ((@ChangedAfter IS NULL AND @ChangedBefore IS NULL))

Зависит от того, что именно является планом запросаесли он выполнял подзапрос несколько раз, эта формулировка могла бы помочь.

...