如何使用重复的子查询优化SQL Server查询

时间:2013-07-11 03:28:32

标签: sql sql-server subquery

我被限制在遗留数据库结构中并需要一些统计结果。以下查询有效,但效率低且速度慢......

SELECT various, other, native, columns,
    (SELECT client FROM clients WHERE id = clientid) AS client,
    (SELECT name FROM categories WHERE id = (SELECT categoryid FROM clients WHERE id = clientid)) AS category,
    (SELECT fullname FROM staff WHERE id = producerid) AS producer,
    ISNULL((SELECT SUM(amount) FROM JobsVoiceWork v WHERE v.jobid = j.id),0) AS voicecosts,
    (SELECT COUNT(*) FROM Scripts s WHERE s.jobid = j.id) AS numberofscriptscompleted,
    ISNULL((SELECT SUM(duration) FROM TimeLog WHERE jobid = j.id),0)/60 AS totaltime,
    ISNULL((SELECT SUM(duration)  FROM TimeLog WHERE jobid = j.id AND jobeditid = 3 AND jobpart = 'Add'),0)/60 AS PartAdd,
    ISNULL((SELECT SUM(duration)  FROM TimeLog WHERE jobid = j.id AND jobeditid = 3 AND jobpart = 'Update'),0)/60 AS PartUpdate,
    ISNULL((SELECT SUM(duration)  FROM TimeLog WHERE jobid = j.id AND jobeditid = 3 AND jobpart = 'Produce'),0)/60 AS PartProduce,
    ISNULL((SELECT SUM(duration)  FROM TimeLog WHERE jobid = j.id AND jobeditid = 3 AND jobpart = 'Amend'),0)/60 AS PartAmend,
    ISNULL((SELECT SUM(duration)  FROM TimeLog WHERE jobid = j.id AND jobeditid = 4),0)/60 AS EditProducerError,
    ISNULL((SELECT SUM(duration)  FROM TimeLog WHERE jobid = j.id AND jobeditid = 8),0)/60 AS EditVoiceError,
    ISNULL((SELECT SUM(duration)  FROM TimeLog WHERE jobid = j.id AND jobeditid = 1),0)/60 AS EditClientError,
    ISNULL((SELECT SUM(duration)  FROM TimeLog WHERE jobid = j.id AND jobeditid = 2),0)/60 AS EditEntryError,
    ISNULL((SELECT SUM(duration)  FROM TimeLog WHERE jobid = j.id AND jobeditid = 5),0)/60 AS EditPronunciation,
    ISNULL((SELECT SUM(duration)  FROM TimeLog WHERE jobid = j.id AND jobeditid = 6),0)/60 AS EditRemixRequest,
    ISNULL((SELECT SUM(duration)  FROM TimeLog WHERE jobid = j.id AND jobeditid = 7),0)/60 AS EditRevoiceRequest
FROM Jobs j

我已经展示了查询的简化版本,但我已经包含了重复的子查询以清楚地证明了效率低下。我尝试了各种表连接方案,但我无法提高性能。

看起来就像应该可以改进一样。有办法吗?

3 个答案:

答案 0 :(得分:4)

您可以使用CASE语句来消除冗余子查询,例如:

SELECT various, other, native, columns,
    (SELECT client FROM clients WHERE id = clientid) AS client,
    (SELECT name FROM categories WHERE id = (SELECT categoryid FROM clients WHERE id = clientid)) AS category,
    (SELECT fullname FROM staff WHERE id = producerid) AS producer,
    ISNULL((SELECT SUM(amount) FROM JobsVoiceWork v WHERE v.jobid = j.id),0) AS voicecosts,
    (SELECT COUNT(*) FROM Scripts s WHERE s.jobid = j.id) AS numberofscriptscompleted,
    ISNULL(SUM(t.duration),0)/60 AS totaltime,
    ISNULL(SUM(CASE WHEN  t.jobeditid = 3 AND t.jobpart = 'Add' THEN t.duration ELSE 0 END),0)/60 AS PartAdd,
    ISNULL(SUM(CASE WHEN  t.jobeditid = 3 AND t.jobpart = 'Update' THEN t.duration ELSE 0 END),0)/60 AS PartUpdate,
    ISNULL(SUM(CASE WHEN  t.jobeditid = 3 AND t.jobpart = 'Produce' THEN t.duration ELSE 0 END),0)/60 AS PartProduce,
    ISNULL(SUM(CASE WHEN  t.jobeditid = 3 AND t.jobpart = 'Amend' THEN t.duration ELSE 0 END),0)/60 AS PartAmend,
    ISNULL(SUM(CASE WHEN  t.jobeditid = 4 THEN t.duration ELSE 0 END),0)/60 AS EditProducerError,
    ISNULL(SUM(CASE WHEN  t.jobeditid = 8 THEN t.duration ELSE 0 END),0)/60 AS EditVoiceError,
    ISNULL(SUM(CASE WHEN  t.jobeditid = 1 THEN t.duration ELSE 0 END),0)/60 AS EditClientError,
    ISNULL(SUM(CASE WHEN  t.jobeditid = 2 THEN t.duration ELSE 0 END),0)/60 AS EditEntryError,
    ISNULL(SUM(CASE WHEN  t.jobeditid = 5 THEN t.duration ELSE 0 END),0)/60 AS EditPronunciation,
    ISNULL(SUM(CASE WHEN  t.jobeditid = 6 THEN t.duration ELSE 0 END),0)/60 AS EditRemixRequest,
    ISNULL(SUM(CASE WHEN  t.jobeditid = 7 THEN t.duration ELSE 0 END),0)/60 AS EditRevoiceRequest
    FROM Jobs j
    LEFT JOIN Timelog t
       ON j.id = t.jobid

答案 1 :(得分:1)

试试这个 -

SELECT various, other, native, columns,
    c.client,
    c2.name AS category,
    s.fullname AS producer,
    ISNULL(v.amount, 0) AS voicecosts,
    s3.numberofscriptscompleted,
    ISNULL(SUM(t.duration),0)/60 AS totaltime,
    ISNULL(SUM(CASE WHEN t.jobeditid = 3 AND t.jobpart = 'Add' THEN t.duration END),0)/60 AS PartAdd,
    ISNULL(SUM(CASE WHEN t.jobeditid = 3 AND t.jobpart = 'Update' THEN t.duration END),0)/60 AS PartUpdate,
    ISNULL(SUM(CASE WHEN t.jobeditid = 3 AND t.jobpart = 'Produce' THEN t.duration END),0)/60 AS PartProduce,
    ISNULL(SUM(CASE WHEN t.jobeditid = 3 AND t.jobpart = 'Amend' THEN t.duration END),0)/60 AS PartAmend,
    ISNULL(SUM(CASE WHEN t.jobeditid = 4 THEN t.duration END),0)/60 AS EditProducerError,
    ISNULL(SUM(CASE WHEN t.jobeditid = 8 THEN t.duration END),0)/60 AS EditVoiceError,
    ISNULL(SUM(CASE WHEN t.jobeditid = 1 THEN t.duration END),0)/60 AS EditClientError,
    ISNULL(SUM(CASE WHEN t.jobeditid = 2 THEN t.duration END),0)/60 AS EditEntryError,
    ISNULL(SUM(CASE WHEN t.jobeditid = 5 THEN t.duration END),0)/60 AS EditPronunciation,
    ISNULL(SUM(CASE WHEN t.jobeditid = 6 THEN t.duration END),0)/60 AS EditRemixRequest,
    ISNULL(SUM(CASE WHEN t.jobeditid = 7 THEN t.duration END),0)/60 AS EditRevoiceRequest
FROM Jobs j
JOIN clients c ON j.id = c.clientid
JOIN categories c2 ON c2.id = c.clientid
JOIN staff s ON j.id = s.producerid
LEFT JOIN (
     SELECT v.jobid, amount = SUM(amount) 
     FROM JobsVoiceWork v 
     GROUP BY v.jobid
) v ON v.jobid = j.id
JOIN (
     SELECT s.jobid, numberofscriptscompleted = COUNT(*) 
     FROM Scripts s 
     GROUP BY s.jobid
) s3 ON s3.jobid = j.id
LEFT JOIN Timelog t ON j.id = t.jobid
--GROUP BY ...

答案 2 :(得分:0)

我不会承诺这会更好,可能会更糟 - 但它的方法不同,这可能会有所帮助。给它一个旋转,看看它是否有用。

首先,我正在编译一个临时表,为jobs.id + timelog.jobeditid +的所有值求和持续时间+如果jobeditid为3,则timelog.jobpart:

SELECT j.id, tl.jobeditid, case when tl.jobeditid = 3 then tl.jobpart else '' end as [JobPart], (Sum(tl.duration)/60) as AdjTotalDuration
INTO #t
FROM Jobs J CROSS JOIN TimeLog tl
GROUP BY j.id, tl.jobeditid, case when tl.jobeditid = 3 then tl.jobpart else '' end;

现在我已经大大简化了你的查询,使用这个临时表以多种方式;我还将select子句中的大多数子查询移动到from子句中的连接表中:

SELECT various, other, native, columns, c.Client, cg.name as [Category], s.fullname as Producer,
  isnull(v.TotalAmount, 0) as voicecosts, (SELECT COUNT(*) FROM Scripts s WHERE s.jobid = j.id) AS numberofscriptscompleted,
  isnull((SELECT SUM(AdjTotalDuration) FROM #t WHERE jobid = j.id),0) AS TotalTime,
  t3a.AdjTotalDuration as PartAdd,
  t3u.AdjTotalDuration as PartUpdate,
  t3p.AdjTotalDuration as PartProduce,
  t3m.AdjTotalDuration as PartAmend,
  t4.AdjTotalDuration  as EditProducerError,
  t8.AdjTotalDuration  as EditVoiceError,
  t1.AdjTotalDuration  as EditClientError,
  t2.AdjTotalDuration  as EditEntryError,
  t5.AdjTotalDuration  as EditPronunciation,
  t6.AdjTotalDuration  as EditRemixRequest,
  t7.AdjTotalDuration  as EditRevoiceRequest
FROM Jobs j 
  join clients c on j.ClientID = c.ID
  join categories cg on c.CategoryID = cg.ID
  join staff s on j.ProducerID = s.ID
  left join (select jobid, sum(amount) as TotalAmount from JobsVoiceWork group by jobid) v on j.id = v.jobid
  left join (select * from #t where id = j.id and jobeditid = 3 and jobpart = 'Add') t3a
  left join (select * from #t where id = j.id and jobeditid = 3 and jobpart = 'Update') t3u 
  left join (select * from #t where id = j.id and jobeditid = 3 and jobpart = 'Produce') t3p
  left join (select * from #t where id = j.id and jobeditid = 3 and jobpart = 'Amend') t3m 
  left join (select * from #t where id = j.id and jobeditid = 4) t4 
  left join (select * from #t where id = j.id and jobeditid = 8) t8 
  left join (select * from #t where id = j.id and jobeditid = 1) t1 
  left join (select * from #t where id = j.id and jobeditid = 2) t2 
  left join (select * from #t where id = j.id and jobeditid = 5) t5 
  left join (select * from #t where id = j.id and jobeditid = 6) t6 
  left join (select * from #t where id = j.id and jobeditid = 7) t7; 

这会改善它吗?这会让情况变得更糟吗?

如果确实改进了,请标记为答案。

此致 约翰