Почему стоимость SQL резко возрастает из-за простого или?

У меня есть следующее утверждение, чтобы найти в моих данных однозначные имена (~ 1 миллион записей):

select Prename, Surname from person p1 
where Prename is not null and Surname is not null 
and not exists (
   select * from person p2 where (p1.Surname = p2.Surname OR p1.Surname = p2.Altname) 
   and p2.Prename LIKE CONCAT(CONCAT('%', p1.Prename), '%') and p2.id <> p1.id
) and inv_date IS NULL

Oracle показывает огромную стоимость 1477315000 и выполнение не заканчивается через 5 минут. Простое разделение OR на отдельный раздел exists увеличивает производительность до 0,5 с и стоит до 45000:

select Prename, Surname from person p1 
where Prename is not null and Surname is not null 
and not exists (
   select * from person p2 where p1.Surname = p2.Surname and
   p2.Prename LIKE CONCAT(CONCAT('%', p1.Prename), '%') and p2.id <> p1.id
) and not exists (
   select * from person p2 where p1.Surname = p2.Altname and 
   p2.Prename LIKE CONCAT(CONCAT('%', p1.Prename), '%') and p2.id <> p1.id
) and inv_date IS NULL

Я не собираюсь настраивать это как можно лучше, так как это редко выполняемый запрос, и я знаю, что CONTACT превосходит любой индекс, но мне просто интересно, откуда такая высокая стоимость. Оба запроса мне кажутся семантически эквивалентными.


person stracktracer    schedule 23.05.2011    source источник


Ответы (3)


Ответ находится в ПЛАНЕ ОБЪЯСНЕНИЯ для ваших запросов. Они могут быть семантически эквивалентными, но план выполнения ваших запросов за кулисами сильно отличается.

EXISTS работает иначе, чем JOIN, и, по сути, оператор OR filter - это то, что объединяет таблицы вместе.

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

person maple_shaft    schedule 23.05.2011
comment
+1 - Чтобы уточнить, EXISTS закорачивает, а OR нет (по крайней мере, в SQL Server, я предполагаю, что Oracle похож). Включая OR в подпрограмму EXISTS, он каждый раз проверяет оба параметра. Разделение означает, что он проверяет только второе, если первое ложно. - person JNK; 23.05.2011
comment
+1 - План выполнения1: Фильтр не существует (...) 1477315000 | Доступ к таблице Человек по индексу ROWID 13863 | Доступ к таблице Человек по индексу ROWID 4019; План 2 огромен и использует два хеш-соединения. - person stracktracer; 23.05.2011
comment
Принято как ответ. Похоже, я переоценил анализатор запросов Oracle - person stracktracer; 24.05.2011

Результаты ваших двух запросов могут быть семантически эквивалентными, но выполнение не эквивалентно с точки зрения операций. Во втором примере для объединения предикатов не используется оператор ИЛИ. Все ваши предикаты во втором примере объединяются с помощью AND.

Производительность лучше, потому что, если первый предикат, объединенный с И, не оценивается как истина, то второй (или любой другой предикат) пропускается (не оценивается). Если вы использовали ИЛИ, тогда оба (или все) предикаты пришлось бы часто оценивать, что замедляет ваш запрос. (Предикаты, объединенные оператором ИЛИ, проверяются до тех пор, пока один из них не станет истинным.)

person Paul Sasik    schedule 23.05.2011
comment
Под семантически эквивалентным я имел в виду создание того же набора результатов, что, я думаю, они и делают ... - person stracktracer; 23.05.2011
comment
@stacktracer: хороший момент. я изменю свой ответ чем-то вроде «операционного эквивалента». Хотя я бы не стал предполагать семантическую эквивалентность разных запросов. Но я думаю, что вы не только быстрее, но и безопаснее со вторым примером, опуская OR. Операционные могут нанести ущерб результатам. - person Paul Sasik; 23.05.2011

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

select 
      p1.Prename, 
      p1.Surname
   from 
      person p1 
         join person p2
            on p1.ID <> p2.ID
            and (  p1.Surname = p2.Surname
                or p1.SurName = p2.AltName )
            and p2.PreName like concat( concat( '%', p1.Prename ), '%' )
   where
          p1.PreName is not null
      and p1.SurName is not null
      and p1.Inv_date is null
      and p2.id is null

В соответствии с вашими комментариями, но из того, что, похоже, вы искали ... НЕТ, НЕ выполняйте левое внешнее соединение ... Если вы ищете похожие имена, которые вы хотите очистить (однако вы справитесь с этим ), вы хотите ПРЕДВАРИТЕЛЬНО УЧИТЫВАТЬ только те записи, для которых ДЕЙСТВИТЕЛЬНО СООТВЕТСТВИЕ через самосоединение (следовательно, обычное соединение). Если у вас есть имя, у которого НЕТ аналогичного имени, вы, вероятно, захотите оставить его в покое ... таким образом, оно будет автоматически исключено из набора результатов.

Теперь вступает в действие предложение WHERE ... У вас есть действующий человек слева ... у которого есть человек справа ... Это дубликаты ... так что у вас есть совпадение, теперь добавив логическое " p2.ID IS NULL "создает тот же результат, что и NOT EXIST, давая окончательные результаты.

Я вернул свой запрос к обычному «соединению».

person DRapp    schedule 23.05.2011
comment
Разве это не дает мне двусмысленных имен? - person stracktracer; 23.05.2011
comment
Я исправил запрос, чтобы отразить, что вы имеете в виду LEFT OUTER JOIN, а не JOIN. Использование JOIN, вероятно, не вернет результатов, если идентификатор не равен нулю. - person Benoit; 23.05.2011
comment
@stracktracer: использование LEFT JOIN b WHERE b.id IS NULL - это умный способ сделать НЕ СУЩЕСТВУЕТ. - person Benoit; 23.05.2011
comment
@stracktracstracktracer, @Benoit, см. исправленные комментарии, возвращающиеся к ОБЫЧНОМУ соединению ... - person DRapp; 23.05.2011
comment
Обычное соединение не дает результатов. Левое внешнее соединение, похоже, работает, но продолжительность составляет 9 секунд и стоит 195377045. - person stracktracer; 24.05.2011