多个子查询到同一个表 - 性能

时间:2013-11-29 17:50:15

标签: sql sql-server subquery

我的公司最近将数据库引擎从paradox更改为ms sql 2008.它的速度更快,但我们遇到一个性能低的查询问题。

查询搜索DOK_SP表中DO_WZ1,DO_WZ2 ... DO_WZ6字段中的任何字段中是否存在DM201211表中的字段NUMER。

SELECT
Z.STATUS,COUNT(K.NUMER) AS DOC_COUNT, MIN(K.ZDNIA) AS OLDEST
FROM DM201311 K LEFT OUTER JOIN ZLEC Z ON K.DO_ZAMOWIENIA=Z.NR   
WHERE NUMER NOT IN (SELECT DO_WZ1 FROM DOK_SP WHERE DO_WZ1 IS NOT NULL AND ID_DOK<>'-1') AND NUMER NOT IN (SELECT DO_WZ2 FROM DOK_SP WHERE DO_WZ2 IS NOT NULL AND ID_DOK<>'-1') AND NUMER NOT IN (SELECT DO_WZ3 FROM DOK_SP WHERE DO_WZ3 IS NOT NULL AND ID_DOK<>'-1') AND NUMER NOT IN (SELECT DO_WZ4 FROM DOK_SP WHERE DO_WZ4 IS NOT NULL AND ID_DOK<>'-1') AND NUMER NOT IN (SELECT DO_WZ5 FROM DOK_SP WHERE DO_WZ5 IS NOT NULL AND ID_DOK<>'-1') AND NUMER NOT IN (SELECT DO_WZ6 FROM DOK_SP WHERE DO_WZ6 IS NOT NULL AND ID_DOK<>'-1') 
GROUP BY Z.STATUS 

这在悖论(执行的几秒)中运作良好。现在在MS SQL 2008中它执行大约17秒。在我的简短调查之后,我发现多个子查询会导致这种情况。是否可以通过删除子查询来优化它?

如您所见,所有子查询都是针对同一个表完成的。我已尝试过EXIST条款,但我只能从17秒降到14秒。我相信有更好的解决方案。

注意:我不允许T-SQL接近这个!

6 个答案:

答案 0 :(得分:2)

SELECT Z.STATUS,COUNT(K.NUMER) AS DOC_COUNT, MIN(K.ZDNIA) AS OLDEST
FROM DM201311 K LEFT OUTER JOIN ZLEC Z ON K.DO_ZAMOWIENIA=Z.NR   
WHERE NUMER NOT IN (SELECT COALESCE(DO_WZ1, DO_WZ2, DO_WZ3, DO_WZ4, DO_WZ5, DO_WZ6)
                    FROM DOK_SP WHERE COALESCE(DO_WZ1, DO_WZ2, DO_WZ3, DO_WZ4, DO_WZ5, DO_WZ6) IS NOT NULL
                    AND ID_DOK<>'-1')
GROUP BY Z.STATUS

答案 1 :(得分:1)

尝试使用此版本更新您的查询

SELECT  Z.STATUS ,
        COUNT(K.NUMER) AS DOC_COUNT ,
        MIN(K.ZDNIA) AS OLDEST
FROM    DM201311 K
        LEFT OUTER JOIN ZLEC Z ON K.DO_ZAMOWIENIA = Z.NR
WHERE   NUMER NOT IN ( SELECT   DO_WZ1
                       FROM     DOK_SP
                       WHERE    (DO_WZ1 IS NOT NULL AND DO_WZ2 IS NOT NULL AND DO_WZ3 IS NOT NULL AND DO_WZ4 IS NOT NULL AND DO_WZ5 IS NOT NULL AND DO_WZ6 IS NOT NULL)
                                AND ID_DOK <> '-1' )
GROUP BY Z.STATUS 

如果ID_DOK是一个数字,您可以更改&lt;&gt; '-1'到&gt; = 0 尽量避免否定,这会影响你的表现。

答案 2 :(得分:1)

尝试使用NOT EXISTS重写查询的NOT IN部分:

WHERE   NUMER NOT IN (SELECT DO_WZ1 FROM DOK_SP 
                    WHERE DO_WZ1 IS NOT NULL AND ID_DOK<>'-1') 

变为

NOT EXISTS(SELECT DO_WZ1 FROM DOK_SP 
    WHERE DO_WZ1 = NUMER
        AND ID_DOK<>'-1') 

here

如果仍然存在性能问题,请检查查询计划并根据需要添加缺少的索引 如果您的DOC_SP很大,则(DO_ZW1,ID_OK),(DO_ZW2,ID_OK)等上的某些索引可能会有所帮助 (或全部进行搜索+扫描,取决于数据)

另一个解决方案:为每个NOT IN添加LEFT OUTER JOINS并测试空值(即未找到)。 这是一种常见的模式

LEFT OUTER JOIN (SELECT DO_WZ1 FROM DOK_SP WHERE ID_DOK<>'-1') W1 ON NUMER=DO_WZ1
LEFT OUTER JOIN (SELECT DO_WZ2 FROM DOK_SP WHERE ID_DOK<>'-1') W2 ON NUMER=DO_WZ2
...
WHERE 
  W1.DO_WZ1 IS NULL
AND W2.DO_WZ2 IS NULL
.. 

或者,为了避免多个连接,只有一个在JOIN中有OR(可能会导致扫描,包含列的索引会有帮助)

SELECT
    Z.STATUS,COUNT(K.NUMER) AS DOC_COUNT, MIN(K.ZDNIA) AS OLDEST
FROM DM201311 K 
LEFT OUTER JOIN ZLEC Z ON K.DO_ZAMOWIENIA=Z.NR   
LEFT OUTER JOIN ( SELECT 1 as X, DO_WZ1, DO_WZ2 DO_WZ3, DO_WZ4, DO_WZ5, DO_WZ6  
                    FROM DOK_SP 
                    WHERE ID_DOK<>'-1') WZ 
                       ON NUMER = WZ.DO_WZ1
                        OR NUMER = WZ.DO_WZ2
                        OR NUMER = WZ.DO_WZ3
                        OR NUMER = WZ.DO_WZ4
                        OR NUMER = WZ.DO_WZ5
                        OR NUMER = WZ.DO_WZ6
WHERE 
    WZ.X IS NULL
GROUP BY Z.STATUS 

答案 3 :(得分:0)

SELECT
Z.STATUS,COUNT(K.NUMER) AS DOC_COUNT, MIN(K.ZDNIA) AS OLDEST
FROM DM201311 K 
LEFT OUTER JOIN ZLEC Z 
ON K.DO_ZAMOWIENIA=Z.NR   
WHERE NUMER NOT IN (
SELECT DO_WZ1 FROM DOK_SP WHERE DO_WZ1 IS NOT NULL AND ID_DOK<>'-1' 
UNION
SELECT DO_WZ2 FROM DOK_SP WHERE DO_WZ2 IS NOT NULL AND ID_DOK<>'-1' 
UNION 
SELECT DO_WZ3 FROM DOK_SP WHERE DO_WZ3 IS NOT NULL AND ID_DOK<>'-1' 
UNION
SELECT DO_WZ4 FROM DOK_SP WHERE DO_WZ4 IS NOT NULL AND ID_DOK<>'-1' 
UNION
SELECT DO_WZ5 FROM DOK_SP WHERE DO_WZ5 IS NOT NULL AND ID_DOK<>'-1' 
UNION 
SELECT DO_WZ6 FROM DOK_SP WHERE DO_WZ6 IS NOT NULL AND ID_DOK<>'-1') 
GROUP BY Z.STATUS 

答案 4 :(得分:0)

@Alain_Deloin建议您将“not in”替换为“not exists”。原因是“不在”很慢。但是,“不在”和“不存在”并不总是代表相同的逻辑。在这种情况下,这是一种非直观但有效的方式来取代“不在”。

不要这样做,这太慢了。

where somefield not in
(select somefield
 from sometable
 where whatever
 )

改为做。

where somefield in 
(select somefield
from sometable
except
select somefield
 from sometable
 where whatever
 )

答案 5 :(得分:0)

悖论!?哦,我...你还在使用Delphi 5吗? 无论如何,我想如果Paradox可以处理它不是一个大型数据库。你为那些表创建了索引吗? 良好的索引可以解决性能问题。 为了以防万一,记住:使用MS SQL创建FK,不会创建索引。