SQL Server 2016 странное поведение - условие ИЛИ дает 0 строк, но условие И дает несколько строк - PullRequest
6 голосов
/ 15 марта 2019

У меня следующий запрос SQL:

SELECT T.tnum,
       T.secId,           
FROM   TradeCore T
       INNER JOIN Sec S
               ON S.secId = T.secId
       INNER JOIN TradeTransfer TT
               ON t.tnum = TT.tnum
WHERE  ( T.td >= '2019-01-01' )
       AND ( T.td <= '2019-02-25' )
       AND ( T.fundId = 3 OR TT.fundId = 3 )
       AND ( T.stratId = 7 OR TT.stratId = 7 ) --Line 1
    -- AND ( T.stratId = 7 AND TT.stratId = 7 ) --Line 2

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

Как это возможно?

1 Ответ

10 голосов
/ 15 марта 2019

Любое собрание строк (T.stratId = 7 AND TT.stratId = 7) обязательно должно соответствовать (T.stratId = 7 OR TT.stratId = 7), поэтому логически невозможно, чтобы менее ограничительный предикат возвращал меньше результатов.

Проблема в поврежденном некластерном индексе.

И чехол

  • 154 строки в TradeCore, соответствующих условию даты, и stratId = 7.
  • Соединение TradeTransfer с примененными условиями stratId и fundId приводит к выходу 68 строк (приблизительно 34 строки)
  • Все они успешно объединяются в строку в Sec (с использованием индекса IX_Sec_secId_sectype_Ccy_valpoint), и 68 строк возвращаются в качестве окончательного результата.

enter image description here

Или чехол

  • 1173 строки в TradeCore, соответствующие условию даты, выбрасываются
  • Присоединение к TradeTransfer с остаточным предикатом 3 in (T.fundId, TT.fundId) AND 7 in (T.stratId, TT.stratId) приводит к уменьшению этого значения до 73 (приблизительно 297 строк)
  • Затем все строки удаляются объединением на Sec - несмотря на то, что мы знаем сверху, что по крайней мере 68 из них имеют совпадение.

enter image description here

Количество элементов таблицы Sec составляет 2399 строк. В плане, в котором все строки удаляются соединением, SQL Server выполняет полное сканирование на IX_Sec_idu в качестве входных данных для зондирующей стороны хеш-соединения, но полное сканирование на этот индекс возвращает только 589 строк .

Строки, которые появляются в другом плане выполнения, извлекаются из другого индекса, содержащего эти 1810 пропущенных строк.

Вы подтвердили в комментариях, что следующие возвращают отличающиеся результаты

select count(*) from Sec with(index = IX_Sec_idul); --589 
select count(*) from Sec with(index = IX_Sec_secId_sectype_Ccy_valpoint); --2399
select count(*) from Sec with(index = PK_Sec)  --2399

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

Причина разных индексов

Поскольку оценки строк, входящих в объединение Sec в случае AND, равны только 34, он выбирает план с вложенными циклами и поэтому для выполнения поиска требуется индекс с ведущим столбцом secId. Для случая OR он оценивает 297 строк, и вместо того, чтобы выполнить приблизительно 297 запросов, он вместо этого выбирает хеш-соединение, поэтому выбирает наименьший доступный индекс, содержащий столбец secId.

Fix

Поскольку в кластеризованном индексе есть все строки, вы можете удалить IX_Sec_idul и создать его снова, чтобы, надеюсь, решить эту проблему (сначала сделайте резервную копию).

Вы также должны запустить dbcc checkdb, чтобы увидеть, не скрываются ли какие-либо другие проблемы.

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