简体   繁体   English

优化SQL Server查询(CTE)

[英]Optimizing SQL Server Query (CTE)

This query takes forever to run. 此查询需要永远运行。 Does anybody have any good tips on how I can optimize it? 有没有人对如何优化它有任何好的建议?

WITH CTE (Lockindate, Before5, After5) AS (SELECT nl.Lockindate, 
(CASE WHEN CAST(RIGHT(FirstLockActivity,8) AS time(1)) <= '17:00' THEN
'Before 5 PM' END) AS before5,
(CASE WHEN CAST(RIGHT(FirstLockActivity,8) AS time(1)) >= '17:00' THEN
'After 5 PM' END) AS after5
FROM netlock nl WITH(NOLOCK)
JOIN rate rs WITH(NOLOCK)
ON nl.id=rs.id
WHERE nl.lockindate BETWEEN '2016-08-01' AND '2016-08-31')
SELECT lockindate, COUNT(After5), COUNT(Before5)
FROM CTE
GROUP BY lockindate

You don't need a CTE: 您不需要CTE:

SELECT nl.Lockindate, 
   SUM(CASE WHEN CAST(RIGHT(FirstLockActivity,8) AS time(1)) <= '17:00' THEN 1 ELSE 0 END) AS before5,
   SUM(CASE WHEN CAST(RIGHT(FirstLockActivity,8) AS time(1)) > '17:00' THEN 1 ELSE 0 END) AS after5
FROM netlock nl WITH(NOLOCK)
JOIN rate rs WITH(NOLOCK)
ON nl.id=rs.id
WHERE nl.lockindate BETWEEN '2016-08-01' AND '2016-08-31'
GROUP BY nk.lockindate

But this might result in exactly the same plan. 但这可能导致完全相同的计划。 Then you need to check why it's slow (missing indexes on the join/group by columns?) 然后你需要检查它为什么慢(连接/组上的索引缺少列?)

Although it won't speed up things all that much IN THIS CASE (*), the conversions from one datatype to another and then another again are 'not optimal'. 虽然在这种情况下 (*)它不会加速所有事情,但是从一种数据类型到另一种数据类型的转换然后再次转换为“不是最佳的”。

(CASE WHEN CAST(RIGHT(FirstLockActivity,8) AS time(1)) <= '17:00' THEN 'Before 5 PM' END) AS before5,

First of all you do an implicit conversion from a datetime [FirstLockActivity] to a string. 首先,您执行从datetime [FirstLockActivity]到字符串的隐式转换。 The reason for this being that the Right() function expects a string, and hence the conversion. 这是因为Right()函数需要一个字符串,因此需要转换。 Doing implicit conversions can be dangerous. 进行隐式转换可能很危险。 Depending on the configuration of your server (and even of your connection which by itself might be influenced by the regional settings of your operating system) this can result in surprising results, not all of which will have the expected last 8 characters! 根据服务器的配置(甚至连接本身可能受操作系统区域设置的影响),这可能会产生令人惊讶的结果,并非所有结果都具有预期的最后8个字符!

FYI: have a look here: https://msdn.microsoft.com/en-us/library/ms187928.aspx?f=255&MSPPError=-2147217396 . 仅供参考:请看这里: https ://msdn.microsoft.com/en-us/library/ms187928.aspx?f = 255&MSPPError = -2147217396 As you can't explicitly pass the 'style' with CAST I've always suggested to people to rather use Convert(), ESPECIALLY when converting from datetimes to string, and vice versa. 由于你无法用CAST显式传递'style',我总是建议人们在从datetimes转换为string时使用Convert(),ESPECIALLY,反之亦然。

After that you take the right-most 8 characters and then convert these into a time(1). 之后,您取最右边的8个字符,然后将它们转换为时间(1)。 I'm not sure why you want to use a time(1) over a time(0) since you're only interested in the hour part, but in the end it won't make much difference I guess. 我不确定你为什么要在一段时间(0)内使用一个时间(1),因为你只对小时部分感兴趣,但最后它不会有太大的区别我猜。

Anyway, doing all these conversions takes CPU and thus time. 无论如何,做所有这些转换需要CPU,因此需要时间。

Assuming this thing isn't too dumbed down from what you really want to do, the target of the query is to return an indication on how many entries there were before and after 5 PM for each lockindate. 假设这个事情并没有从你真正想做的事情中黯然失色,那么查询的目标是返回每个lockindate在下午5点之前和之后有多少条目的指示。 As such, all you need to do is calculate the hour of each FirstLockActivy and decide from there. 因此,您需要做的就是计算每个FirstLockActivy的小时数并从那里决定。 => Hour(xx) and DatePart(hour, xx) will both return the required information for a fraction of the CPU-cost of those conversions. => Hour(xx)DatePart(hour, xx)都将返回所需信息,而这些信息只占这些转换的CPU成本的一小部分。 Also, you can easily get the before/after in one CASE construction. 此外,您可以轻松地在一个CASE结构中获得前/后。

  WITH CTE (LockinDate, Before5PM)
    AS (SELECT Lockindate, 
               (CASE WHEN Hour(FirstLockActivity) < 17 THEN 1 ELSE 0 END) AS Before5PM

          FROM netlock nl WITH (NOLOCK)
          JOIN rate rs WITH (NOLOCK)
            ON nl.id=rs.id
         WHERE nl.lockindate BETWEEN Convert(datetime, '2016-08-01', 105) AND Convert(datetime, '2016-08-31', 105))
  SELECT LockinDate,
         After5  = SUM(1 - Before5PM),
         Before5 = SUM(Before5PM)  
    FROM CTE
   GROUP BY LockinDate

Assuming the amount of (relevant) records in the tables is huge, then this will have some effect on the duration, but given the speed of modern processors it probably won't be shocking. 假设表中(相关)记录的数量很大,那么这将对持续时间产生一些影响,但考虑到现代处理器的速度,它可能不会令人震惊。 Off course, when you're on a busy server and there isn't all that much (free) CPU going around, then the effect will be much more noticable. 当然,当你在繁忙的服务器上并没有那么多(免费)CPU到处时,那么效果会更加明显。

That said, as for performance I'd suggest to check the indexes on the netlock and rate table . 也就是说,至于性能,我建议检查netlockrate table上的索引。 Ideally netlock has a clustered index (or PK) on the id field and a non-clustered index on lockindate . 理想情况下netlock对聚簇索引(或PK) id字段和在非聚集索引lockindate Additionally, the rate table has a clustered index on the id field with some additional other fields I'm not aware of. 此外, rate表在id字段上有一个聚集索引,还有一些我不知道的其他字段。 If the latter isn't the case, having a non-clustered index on the id field with the FirstLockActivity field in the included column would be great too. 如果后者不是这种情况,那么在id字段上使用包含列中的FirstLockActivity字段的非聚集索引也会很棒。

If you want to have this query without the CTE, you can simply copy paste the CTE into a subquery/derived table like this: 如果您希望在没有CTE的情况下进行此查询,则只需将CTE复制粘贴到子查询/派生表中,如下所示:

 SELECT LockinDate,
        After5  = SUM(1 - Before5PM),
        Before5 = SUM(Before5PM)  
   FROM (SELECT Lockindate, 
                (CASE WHEN Hour(FirstLockActivity) < 17 THEN 1 ELSE 0 END) AS Before5PM

           FROM netlock nl WITH (NOLOCK)
           JOIN rate rs WITH (NOLOCK)
             ON nl.id=rs.id
          WHERE nl.lockindate BETWEEN Convert(datetime, '2016-08-01', 105) AND Convert(datetime, '2016-08-31', 105)) A
  GROUP BY LockinDate

Or, worked out a little more you'd get 或者,你得到的更多

 SELECT LockinDate,
        After5  = SUM(1 - (CASE WHEN Hour(FirstLockActivity) < 17 THEN 1 ELSE 0 END)),
        Before5 = SUM(    (CASE WHEN Hour(FirstLockActivity) < 17 THEN 1 ELSE 0 END))
   FROM netlock nl WITH (NOLOCK)
   JOIN rate rs WITH (NOLOCK)
     ON nl.id=rs.id
  WHERE nl.lockindate BETWEEN Convert(datetime, '2016-08-01', 105) AND Convert(datetime, '2016-08-31', 105)) A
  GROUP BY LockinDate

PS: wrote all this in the browser, there might be some typo's and none the code is tested, prepare to have to fiddle around a bit to get it working =) PS:在浏览器中写下所有这些,可能会有一些拼写错误,并且没有代码经过测试,准备不得不摆弄一下以使其工作=)

PS: if you can't get the query plan, you might still be able to use SET STATISTICS TIME to more easily compare one version of the query to another. PS:如果您无法获得查询计划,您仍然可以使用SET STATISTICS TIME来更轻松地将查询的一个版本与另一个版本进行比较。

(*: In case you do this kind of conversions inside the WHERE clause or a JOIN clause, it will confuse the optimizer and the results could be devastating for the performance of your query) (*:如果你在WHERE子句或JOIN子句中进行这种转换,它会混淆优化器,结果可能对查询的性能造成破坏)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM