SQL计数和巨大的表上的多个子查询

时间:2016-03-20 12:16:24

标签: sql sql-server

我有几个SQL表,名为FOS,关键字和PRef。他们的结构和关系如下:

+------------------+      +------------------+      +-----------------+
|       FOS        |      |     keywords     |      |       PRef      |
+------------------+      +------------------+      +-----------------+
|fosID (PK)        |--+   |pkID (PK)         |  +---|pID1 (PK)        |
|fosName           |  +---|fosID(FK)         |  +---|pID2 (PK)        |
+------------------+      |paperID (FK)      |--+   +-----------------+
 ( 53k+ rows)             +------------------+       ( 952M+ rows)
                           ( 157M+ rows)

目前我可以通过向我的查询提供单个fosID来实现,但由于fos表包含超过1k的记录,我没有足够的人力来手动提供每个fosID并获得相应的rowCount然后合并所有结果

declare @fosID varchar(10)='1234567890';--my fosID

select fos.fosID,fos.fosName,count(*) as rowCount 
from  PRef pr left join FOS fos on fos.fosID=@fosID 
where 
     pr.pID1 in(SELECT paperID FROM keywords k where k.fosID=@fosID)
  OR pr.pID2 in(SELECT paperID FROM keywords k where k.fosID=@fosID)
group by fos.fosID,fos.fosName

然后它给出了正确的结果:

+----------+--------+----------+
|fosID     |fosName |rowCount  |
+----------+--------+----------+
|1234567890|name1   |34        |
+----------+--------+----------+

现在我想得到一份53k + fos项目的所有fos项目和PRef中的记录数量。

我试图将where k.fosID=@fieldID中的部分修改为where k.fosID in (select fosID from FOS),但产生的次数减少了。

有关如何解决此问题的任何建议?

P.S。我现在正在看游标,但表现真的很......很慢

编辑1:预期结果:

+----------+--------+----------+
|fosID     |fosName |rowCount  |
+----------+--------+----------+
|1234567890|name1   |34        |
|1234567891|name2   |3         |
|1234567892|name3   |23        |
|.....     |....    |...       | 
+----------+--------+----------+
 (exact same number of rows as table FOS)

2 个答案:

答案 0 :(得分:2)

您可以修改子查询以使用相关子查询

select fos.fosID, fos.fosName, count(*) as rowCount 
from  PRef pr cross join
      FOS fos
where pr.pID1 in (SELECT paperID FROM keywords k where k.fosID = fos.fosID) OR
      pr.pID2 in (SELECT paperID FROM keywords k where k.fosID = fos.fosID)
group by fos.fosID, fos.fosName;

我的猜测是性能非常糟糕。

这是另一种选择:

select fos.*, kp.cnt
from fos outer apply
     (select count(*) as cnt
      from keywords k join
           pref pr
           on k.paperID in (pr.pID1, pf.pID2) and
              k.fosID = fos.fosID
     ) kp;

我认为这也会有非常差的性能特征。

如果你可以单独执行每个id,那么SQL Server应该能够提出更好的执行计划:

select fos.*, (kp1.cnt + kp2.cnt)
from fos outer apply
     (select count(*) as cnt
      from keywords k join
           pref pr
           on k.paperID = pr.pID1 and
              k.fosID = fos.fosID
     ) kp1 outer apply
     (select count(*) as cnt
      from keywords k join
           pref pr
           on k.paperID = pr.pID2 and
              k.fosID = fos.fosID
     ) kp2;

答案 1 :(得分:2)

首先,我怀疑通过检查表中的数据类型可以获得显着的改进。看起来你只使用数字数字varchar(10)? 小桌子上没有注意到这种荒谬,但900M行可能浪费超过5GB,影响存储,内存和性能。

第二个FOS仅用于查找fosName,而53k行是工作的较小部分。因此,首先要使每个fosID的计数正确;然后加入名字。

;with CountPerFos as (
    SELECT  k.fosID, COUNT(*) AS fosCount
    FROM    PRef r
            INNER JOIN keywords k ON
                r.PID1 = k.paperID
             OR r.PID2 = k.paperID
    GROUP BY k.fosID
)
SELECT  c.fosID, f.fosName,
        --Need to handle fosIDs missing from CTE above
        COALESCE(c.fosCount, 0)
FROM    FOS f
        LEFT OUTER JOIN CountPerFos c
            f.fosID = c.fosID