复杂SQL连接与group by

时间:2012-09-16 04:24:46

标签: sql sql-server sql-server-2008 join group-by

我正在尝试优化需要很长时间的查询。查询的目标是获得最佳类似的F2。(特殊相似性度量) 这是我的一个例子:

 CREATE TABLE Test
(
   F1 varchar(124),
   F2 varchar(124),
   F3 varchar(124)
)
INSERT INTO TEST ( F1, F2, F3 ) VALUES ( 'A', 'B', 'C' )
INSERT INTO TEST (  F1, F2, F3 ) VALUES ( 'D', 'B', 'E' )
INSERT INTO TEST (  F1, F2, F3 ) VALUES ( 'F', 'I', 'G' )
INSERT INTO TEST (  F1, F2, F3 ) VALUES ( 'F', 'I', 'G' )
INSERT INTO TEST (  F1, F2, F3 ) VALUES ( 'D', 'B', 'C' )
INSERT INTO TEST (  F1, F2, F3 ) VALUES ( 'F', 'B', 'G' )
INSERT INTO TEST (  F1, F2, F3 ) VALUES ( 'D', 'I', 'C' )
INSERT INTO TEST (  F1, F2, F3 ) VALUES ( 'A', 'B', 'C' )
INSERT INTO TEST (  F1, F2, F3 ) VALUES ( 'A', 'B', 'K' )
INSERT INTO TEST (  F1, F2, F3 ) VALUES ( 'A', 'K', 'K' )

现在,如果我运行此查询:

SELECT B.f2,COUNT(*) AS CNT  
FROM 
(
select F1,F3 from Test
where F2='B'
 )AS A
    INNER JOIN  Test AS B
   ON A.F1 = B.F1 AND  A.F3 = B.F3
GROUP BY B.F2 
ORDER BY CNT DESC 

该表有1米+行。 什么是更好的方法呢?

5 个答案:

答案 0 :(得分:3)

您也可以在此表单中编写查询,因为您只有一个选择,因此您的检索时间将缩短

SELECT  Test_1.F2, COUNT(Test_1.F1) AS Cnt 
FROM    Test 
INNER JOIN Test AS Test_1 ON Test.F1 = Test_1.F1 AND Test.F3 = Test_1.F3 
WHERE   (Test.F2 = 'B') 
GROUP BY Test_1.F2

答案 1 :(得分:3)

这是编写查询的另一种方法。接近guido在MS SQL中可以运行的答案。

WITH Filtered AS (SELECT DISTINCT F1,F3 FROM Test WHERE F2='B')
SELECT B.f2,COUNT(*) AS CNT
  FROM Test B
       INNER JOIN Filtered
           ON B.F1 = Filtered.F1 AND B.F3 = Filtered.F3
 GROUP BY B.F2
 ORDER BY CNT DESC

我认为您的原始查询可能有错误,就像Fred提到的那样。在你的例子中,F2 =“B”的计数应该是6而不是8,是吗?如果打算使用8,请取出DISTINCT

您可能尝试的另一件事是使TEST表的聚集索引为(F2,F1,F3),并在(F1,F3)上创建另一个非聚集索引。

示例代码也可在SqlFiddle上找到。

答案 2 :(得分:2)

对所有行WHERE F2 = 'B'进行过滤搜索将导致全表扫描,除非您创建一个将F2作为其第一列或唯一列的索引。再往下,连接条件涉及列F1和F3,您提到它们已经是以F1开头的索引的一部分。

我还注意到你的查询的第一部分并没有消除T2 ='B'的(T1,T3)集合的重复,正如人们可能期望的那样,当设置正好与另一个子集相交时同桌。您可能有理由这样做,但在您提供有关您尝试实施的相似性度量算法的一些详细信息之前,我们无法确切知道。

您的ORDER BY子句也会通过在最终结果集上产生可能较大的内部排序来影响查询运行时间。

答案 3 :(得分:1)

如果您的Test表有1m +行,则您组合的联接临时表很容易拥有数亿行。

这可以在mysql中运行,但不能在sql-server afaik上运行:

SELECT F2,COUNT(*)
FROM Test AS B 
WHERE (B.F1,B.F3) IN (
  SELECT F1,F3 FROM Test
  WHERE F2='B') 
GROUP BY F2

答案 4 :(得分:1)

我意识到这已经得到了解答,但我认为这种方法可能要快得多,特别是如果F1和F3有许多重复值:

SELECT B.f2, sum(A.cnt) AS CNT  
FROM (select F1, F3, count(*) as cnt
      from Test
      where F2='B'
      group by f1, f3
     ) A INNER JOIN
     Test B
     ON A.F1 = B.F1 AND A.F3 = B.F3
GROUP BY B.F2 
ORDER BY CNT DESC

如果F1和F3没有很多组合,那么第一个子查询应该减少到几百或几千行。 (您的示例数据只有一个大写字母,因此如果使用所有字母,组合的数量将为576。)SQL Server可能会对结果执行合并或散列连接,这应该可以很好地执行。

您也可以使用Windows函数在没有连接和分组的情况下执行此操作:

select t.f2, sum(nummatches) as cnt
from (select t.*,
             sum(isB) over (partition by f1, f3) as nummatches
      from (select t.*,
                   (case when F2 = 'B' then 1 else 0 end) as IsB
            from test
           ) t
     ) t
group by t.f2
order by 2 desc

窗口函数通常表现更好,因为它们可以处理较小的数据块。