SQL Server - 如何优化此查询?

Posted

技术标签:

【中文标题】SQL Server - 如何优化此查询?【英文标题】:SQL Server - how can this query be optimized? 【发布时间】:2013-07-22 18:36:01 【问题描述】:

此查询平均需要 4 秒。它将成为存储过程中的子查询,我需要它花费一秒。这是查询:

(select customercampaignname + ' $' + convert(varchar, cast(amount as numeric(36,2) ) ) As 'Check_Stub_Comment2' from (
    select ROW_NUMBER() OVER (ORDER BY amount desc) as rownumber, customercampaignname, amount from (
        select * from (
               select distinct d.customercampaignname
                   ,sum(d.mastercurrencyamount) As amount

                from bb02_donation d
                      JOIN bb02_donationline dl on d.donationid = dl.donationid
                      JOIN bb02_fundraiserrevenuestream frs on dl.fundraiserrevenuestreamid = frs.fundraiserrevenuestreamid and frs.fundraiserid = 1869

                where d.customercampaignname is not null 
                      and d.customercampaignname != '' 
                group by d.CustomerCampaignName
        ) as x
) as sub ) as y where rownumber = 1)

【问题讨论】:

澄清 - “where rownumber =”很重要。我需要能够仅选择第 1 行,或仅选择第 2 行。 我在下面标记的答案确实使这个查询更快,但是它仍然是对数据库的 n+ 调用。就我而言,我只关心 2 个结果,所以我将它们存储到一个临时表中,只需调用一次数据库。然后我能够从临时表中取出前 1 个结果,对其进行排序,然后再次取出前 1 个结果。这将我的总查询时间减少了一半。谢谢大家的回复。 【参考方案1】:

如果您实际上不需要将行号用于任何事情,那么我会选择 TOP 1 行。查询可以简化很多。我喜欢首先从表中选择最能被您的谓词过滤掉的表。希望您对 frs.fundraiserid 以及参与连接的所有列都有一个良好的索引。

SELECT 
 TOP 2 customercampaignname + ' $' + CONVERT(VARCHAR(255), CAST(SUM(d.mastercurrencyamount) AS NUMERIC(36,2) ) ) AS 'Check_Stub_Comment2',
 ROW_NUMBER() OVER(ORDER BY SUM(d.mastercurrencyamount) DESC) as rownumber
FROM bb02_fundraiserrevenuestream frs
JOIN bb02_donationline dl ON
 dl.fundraiserrevenuestreamid = frs.fundraiserrevenuestreamid
JOIN bb02_donation d ON
 d.donationid = dl.donationid          
WHERE frs.fundraiserid = 1869
 AND d.customercampaignname IS NOT NULL
 AND d.customercampaignname != '' 
GROUP BY d.CustomerCampaignName
ORDER BY SUM(d.mastercurrencyamount) DESC

由于您需要能够选择第一行或第二行,因此将其包装在 CTE 或子查询中。

WITH topcampaign AS (
SELECT 
 TOP 2 customercampaignname + ' $' + CONVERT(varchar(255), CAST(SUM(d.mastercurrencyamount) AS NUMERIC(36,2) ) ) AS 'Check_Stub_Comment2',
 ROW_NUMBER() OVER(ORDER BY SUM(d.mastercurrencyamount) DESC) as rownumber
FROM bb02_fundraiserrevenuestream frs
JOIN bb02_donationline dl ON
 dl.fundraiserrevenuestreamid = frs.fundraiserrevenuestreamid
JOIN bb02_donation d ON
 d.donationid = dl.donationid          
WHERE frs.fundraiserid = 1869
 AND d.customercampaignname IS NOT NULL
 AND d.customercampaignname != '' 
GROUP BY d.CustomerCampaignName
ORDER BY SUM(d.mastercurrencyamount) DESC
)
SELECT * from topcampaign WHERE rownumber = 1

作为另一种可能的优化,我将 CONVERT 从 CTE 中取出并放入最终选择中。不确定这是否有很大帮助。

WITH topcampaign AS (
SELECT 
 TOP 2 
 customercampaignname,
 SUM(d.mastercurrencyamount) AS amount,
 ROW_NUMBER() OVER(ORDER BY SUM(d.mastercurrencyamount) DESC) as rownumber
FROM bb02_fundraiserrevenuestream frs
JOIN bb02_donationline dl ON
 dl.fundraiserrevenuestreamid = frs.fundraiserrevenuestreamid
JOIN bb02_donation d ON
 d.donationid = dl.donationid          
WHERE frs.fundraiserid = 1869
 AND d.customercampaignname IS NOT NULL
 AND d.customercampaignname != '' 
GROUP BY 
 d.CustomerCampaignName
ORDER BY 
 SUM(d.mastercurrencyamount) DESC
)
SELECT 
 rownumber,
 customercampaignname + ' $' + CONVERT(varchar(255), CAST(amount AS NUMERIC(36,2) ) ) AS 'Check_Stub_Comment2'
FROM topcampaign 
WHERE rownumber = 1

【讨论】:

谢谢 - 我实际上需要行号,以便我可以选择第一行或第二行。 Top 1 可以,但我仍然需要一种方法来查询第二行。 我编辑了答案以将 ROW_NUMBER 添加到输出中。您可以将 TOP 更改为 TOP N 其中 N 是任何有效整数。所以如果你只需要top 2,还需要rownumber,那就改成TOP 2吧。 让我再解释一下。我需要它要么返回第 1 行,要么只返回第 2 行。所以,我需要说“where rownumber = 2”并且只得到结果集中的第 2 行。我现在正在尝试使用上面的 sql 来实现这一点。 我能够用 select * from (-your sql') as x where rownumber = 2 包装你的 sql 运行时间不到 1 秒,因此目前看来很有希望。【参考方案2】:

我不知道这是否会运行得更快,但根据我的估计,这可以简化为:

Select top 1 customercampaignname + ' $' + convert(varchar(255), cast(sum(d.mastercurrencyamount) as numeric(36,2) ) ) As amount
from bb02_donation d JOIN
     bb02_donationline dl
     on d.donationid = dl.donationid JOIN
     bb02_fundraiserrevenuestream frs
     on dl.fundraiserrevenuestreamid = frs.fundraiserrevenuestreamid and frs.fundraiserid = 1869
where d.customercampaignname is not null and d.customercampaignname != '' 
group by d.CustomerCampaignName
order by sum(d.mastercurrencyamount)

【讨论】:

【参考方案3】:

我不知道这是否有什么不同

;WITH cte AS 
(
    select *, ROW_NUMBER() OVER (ORDER BY amount desc) as rownumber
    from (
        select distinct d.customercampaignname, sum(d.mastercurrencyamount) As amount
        from bb02_donation d
        INNER JOIN bb02_donationline dl on d.donationid = dl.donationid
        INNER JOIN bb02_fundraiserrevenuestream frs on dl.fundraiserrevenuestreamid = frs.fundraiserrevenuestreamid and frs.fundraiserid = 1869
        where d.customercampaignname is not null 
                and d.customercampaignname != '' 
        group by d.CustomerCampaignName
    ) as x
)
SELECT TOP 1 *
FROM cte 
ORDER BY RowNumber

如果表格非常好,您可能需要帮助过滤掉一些东西,例如,您可以只选择 bb02_fundraiserrevenuestream.fundraiserid = 1869,然后执行其余的查询。 我不知道,这可能是一个尝试并查看执行计划的问题。

;WITH Frs AS 
( SELECT dl.fundraiserrevenuestreamid 
  FROM bb02_fundraiserrevenuestream
  WHERE fundraiserid = 1869
)
, cte AS (
    select d.customercampaignname, sum(d.mastercurrencyamount) As amount
    from Frs
    INNER JOIN bb02_donationline dl  ON dl.fundraiserrevenuestreamid = frs.fundraiserrevenuestreamid
    INNER JOIN bb02_donation d on d.donationid = dl.donationid
    where d.customercampaignname is not null 
            and d.customercampaignname != '' 
    group by d.CustomerCampaignName
)
SELECT TOP 1  customercampaignname + ' $' + convert(varchar, cast(amount as numeric(36,2) ) ) As 'Check_Stub_Comment2' 
FROM cte
ORDER BY sum(d.mastercurrencyamount)

与往常一样,INDEXES 可能是这些性能问题的解决方案。

【讨论】:

以上是关于SQL Server - 如何优化此查询?的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 中的 SQL 查询优化

优化 SQL Server 聚合查询

如何使用过滤器和分页优化 SQL Server 查询?

大型sql server查询性能优化

Sql Server 优化 SQL 查询:如何写出高性能SQL语句

如何优化Sql server 大数据量时使用 like 查询的速度?或有啥别的方法实现模糊查询?