2011-05-23 36 views
7

我有下面的语句来找到自己的数据明确的名称(约1万个条目):为什么SQL成本会用简单的“或”爆炸?

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 

甲骨文表示1477315000巨大的成本,并在5分钟后执行并没有结束。简单地拆分或成自己的存在款性能提升至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超过任何指数,但我只是想知道这种高成本来自哪里。这两个查询在语义上都与我相同。

回答

6

答案位于EXPLAIN PLAN中供您查询。它们在语义上可能是等价的,但是针对您的查询的幕后执行计划则大不相同。

EXISTS与JOIN的操作不同,本质上,OR过滤器语句是将表连接在一起的。

第二个查询中没有JOIN,因为您只从一个表中检索记录。

+3

+1 - 详细说明,'EXISTS'短路和'OR'没有(至少在SQL Server中,我假设Oracle是相似的)。通过在'EXISTS'子文件中包含'OR',它每次都检查两个选项。分离意味着它只检查第二个是否第一个是假的。 – JNK 2011-05-23 14:41:28

+2

+1 - 执行计划1:筛选器不存在(...)1477315000 |表索引人员索引ROWID 13863 |按索引表访问人员ROWID 4019;计划2是巨大的,并使用两个散列连接 – stracktracer 2011-05-23 14:47:08

+0

接受asnwer。看起来我高估了Oracle的查询分析器 – stracktracer 2011-05-24 07:53:16

2

你的两个查询的结果可能在语义上是等价的,但执行的操作不等效。你的第二个例子从来没有使用OR运算符来组合谓词。第二个例子中的所有谓词都使用AND进行组合。

性能更好,因为如果与AND组合的第一个谓词未计算为true,则会跳过第二个(或任何其他谓词)(未评估)。如果您使用了OR,那么这两个(或所有)谓词将不得不经常进行评估,从而减慢查询速度。 (ORed谓词进行检查,直到一个评估为真。)

+3

在语义上相当于我的意思是产生相同的结果集,我认为他们这样做... – stracktracer 2011-05-23 14:46:12

+0

@stacktracer:好点。我会用“操作上的等同”来修改我的答案。虽然我不会假设不同查询的语义等价。但是我认为你不仅更快,而且通过省略OR来保证你的第二个例子。 ORs可能会对结果造成破坏。 – 2011-05-23 14:47:12

1

我会考虑测试重写如下的查询......在“限定”什么被认为是匹配的条件下,从一个到另一个进行直接连接。 。然后,在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 

根据您的意见,但它似乎是你所寻找的......不,不要做一个左外连接...如果你正在寻找你想清除的ALIKE名字(不过你会处理这个),你只希望通过自联接来预优化那些有匹配的记录(因此正常加入)。如果您的名称没有类似的名称,那么您可能希望单独保留它,因此它将自动排除在结果集外。

现在,WHERE子句开始......你左边有一个有效的人......右边有一个人......这些都是重复的......所以你有这个匹配,现在通过投掷在逻辑“p2.ID IS NULL”中创建与NOT EXIST相同的结果,给出最终结果。

我把我的查询恢复到正常的“加入”。

+0

这不会给我模棱两可的名字吗? – stracktracer 2011-05-23 14:58:34

+0

我更正了查询以反映您的意思是一个左外连接,而不是一个连接。如果没有id为空,使用JOIN将返回可能没有结果。 – Benoit 2011-05-23 15:00:47

+1

@stracktracer:使用LEFT JOIN b WHERE b.id IS NULL是做一个NOT EXISTS的聪明方式。 – Benoit 2011-05-23 15:01:34