关于 T-SQL 脚本的建议,改为使用动态 SQL

Raz*_*anu 5 t-sql sql-server-2016

下面的脚本将查询 Ola 的维护解决方案结果表 (CommandLog),并在过去 4 周内对每两周一次的重新索引执行持续时间进行比较。

现在看起来,这是一个快速的技巧,可以帮助我调整维护窗口。但我想删除日期硬编码,这样我就不必在未来的每个周末手动添加一个新的 JOIN。

请提供完整的重写(动态 SQL?)或仅提供有关如何实现此功能或要包含的其他一些有用功能的一些提示。确定更改 sproc 并将额外的输出添加到表中。使用 SQL 2016。如果已经有一个脚本可以满足这个目的,那么很高兴使用那个脚本。

WITH t0 AS 
(
SELECT ObjectName, IndexName, IndexType
FROM Tools.dbo.CommandLog
WHERE 1=1
AND DatabaseName = 'testdb'
AND CommandType = 'ALTER_INDEX'
GROUP BY ObjectName, IndexName, IndexType
)

SELECT 
 t0.ObjectName
,t0.IndexName
,t0.IndexType
,DATEDIFF(ss,t1.StartTime,t1.EndTime) as '20-40 01-06'
,DATEDIFF(ss,t2.StartTime,t2.EndTime) as '5-40 01-07'
,DATEDIFF(ss,t3.StartTime,t3.EndTime) as '20-40 01-13'
,DATEDIFF(ss,t4.StartTime,t4.EndTime) as '5-40 01-14'
,DATEDIFF(ss,t5.StartTime,t5.EndTime) as '20-40 01-20'
,DATEDIFF(ss,t6.StartTime,t6.EndTime) as '5-40 01-21'
,DATEDIFF(ss,t7.StartTime,t7.EndTime) as '20-40 01-27'
,DATEDIFF(ss,t8.StartTime,t8.EndTime) as '5-40 01-28'
FROM t0
LEFT JOIN Tools.dbo.CommandLog as t1 ON t0.ObjectName = t1.ObjectName AND t0.IndexName = t1.IndexName AND t1.StartTime BETWEEN '2018-01-06 00:30:00' AND '2018-01-06 23:59:59'
LEFT JOIN Tools.dbo.CommandLog as t2 ON t0.ObjectName = t2.ObjectName AND t0.IndexName = t2.IndexName AND t2.StartTime BETWEEN '2018-01-07 00:30:00' AND '2018-01-07 23:59:59'
LEFT JOIN Tools.dbo.CommandLog as t3 ON t0.ObjectName = t3.ObjectName AND t0.IndexName = t3.IndexName AND t3.StartTime BETWEEN '2018-01-13 00:30:00' AND '2018-01-13 23:59:59'
LEFT JOIN Tools.dbo.CommandLog as t4 ON t0.ObjectName = t4.ObjectName AND t0.IndexName = t4.IndexName AND t4.StartTime BETWEEN '2018-01-14 00:30:00' AND '2018-01-14 23:59:59'
LEFT JOIN Tools.dbo.CommandLog as t5 ON t0.ObjectName = t5.ObjectName AND t0.IndexName = t5.IndexName AND t5.StartTime BETWEEN '2018-01-20 00:30:00' AND '2018-01-20 23:59:59'
LEFT JOIN Tools.dbo.CommandLog as t6 ON t0.ObjectName = t6.ObjectName AND t0.IndexName = t6.IndexName AND t6.StartTime BETWEEN '2018-01-21 00:30:00' AND '2018-01-21 23:59:59'
LEFT JOIN Tools.dbo.CommandLog as t7 ON t0.ObjectName = t7.ObjectName AND t0.IndexName = t7.IndexName AND t7.StartTime BETWEEN '2018-01-27 00:30:00' AND '2018-01-27 23:59:59'
LEFT JOIN Tools.dbo.CommandLog as t8 ON t0.ObjectName = t8.ObjectName AND t0.IndexName = t8.IndexName AND t8.StartTime BETWEEN '2018-01-28 00:30:00' AND '2018-01-28 23:59:59'
WHERE 1=1
ORDER BY 
 t0.ObjectName
,t0.IndexName
,t0.IndexType
Run Code Online (Sandbox Code Playgroud)

McN*_*ets 9

日历表

正如LowlyDBAAnti-weakpasswords在他们的评论中指出的那样,您可以找到不止一种生成日历表的方法:

为了回答这个问题,我将生成一个(基本)日历表,只是为了向您展示如何将它与您的数据一起使用,您将在网上找到使用更详细字段(秒、一年中的一天、季度、等等)

CREATE TABLE Calendar(cDate datetime, cDay int, cDayOfWeek int, cDayName varchar(20));

DECLARE @date date = '20180101';
WHILE @date <= '20180131'
BEGIN
    INSERT INTO Calendar VALUES (@date, 
                                 DAY(@date), 
                                 DATEPART(weekday, @date), 
                                 DATENAME(weekday, @date));

    SET @date = DATEADD(day, 1, @date);
END
Run Code Online (Sandbox Code Playgroud)

因此,根据您的查询,您似乎只对获取周六和周日的结果感兴趣。在这种情况下,您应该Calendar通过day name或过滤表day of week。如果您想按 过滤,请查看SET DATEFIRSTday of week

SELECT * FROM Calendar WHERE cDayName IN ('Saturday', 'Sunday');
GO
Run Code Online (Sandbox Code Playgroud)
日期 | cDay | cDayOfWeek | 日期名称
:------------------ | ---: | ---------: | :-------
06/01/2018 00:00:00 | 6 | 7 | 周六
07/01/2018 00:00:00 | 7 | 1 | 星期日  
13/01/2018 00:00:00 | 13 | 7 | 周六
14/01/2018 00:00:00 | 14 | 1 | 星期日  
20/01/2018 00:00:00 | 20 | 7 | 周六
21/01/2018 00:00:00 | 21 | 1 | 星期日  
27/01/2018 00:00:00 | 27 | 7 | 周六
28/01/2018 00:00:00 | 28 | 1 | 星期日  

用随机数据填充 CommandLog 表

现在让我模拟一个 CommandLog 表并用 Jan-01 和 Jan-31 之间的 1000 个随机日期填充它。

CREATE TABLE CommandLog 
(
    ObjectName varchar(10), 
    IndexName varchar(10), 
    IndexType int, 
    StartTime datetime,
    EndTime datetime
);

DECLARE @step int = 1;
DECLARE @startDate datetime;

WHILE @step <= 1000
BEGIN
    SET @startDate = DATEADD(minute, RAND() * 59, 
                             DATEADD(hour, RAND() * 23, 
                                     DATEADD(day, RAND() * 31, '20180101')))

    INSERT INTO CommandLog VALUES('OBJ1', 
                                  'INDEX1', 
                                  1, 
                                  @startDate, 
                                  DATEADD(second, RAND() * 59,@startDate));
    SET @step += 1;
END

/* just to check first 10 records */
SELECT TOP 10 * FROM CommandLog;
GO
Run Code Online (Sandbox Code Playgroud)
对象名称 | 索引名称 | 索引类型 | 开始时间 | 时间结束            
:--------- | :-------- | --------: | :------------------ | :------------------
OBJ1 | 索引1 | 1 | 16/01/2018 04:36:00 | 16/01/2018 04:36:43
OBJ1 | 索引1 | 1 | 06/01/2018 09:11:00 | 06/01/2018 09:11:33
OBJ1 | 索引1 | 1 | 05/01/2018 14:23:00 | 05/01/2018 14:23:43
OBJ1 | 索引1 | 1 | 10/01/2018 19:53:00 | 10/01/2018 19:53:11
OBJ1 | 索引1 | 1 | 14/01/2018 14:31:00 | 14/01/2018 14:31:53
OBJ1 | 索引1 | 1 | 06/01/2018 18:43:00 | 06/01/2018 18:43:07
OBJ1 | 索引1 | 1 | 21/01/2018 21:52:00 | 21/01/2018 21:52:41
OBJ1 | 索引1 | 1 | 28/01/2018 06:51:00 | 28/01/2018 06:51:03
OBJ1 | 索引1 | 1 | 19/01/2018 08:39:00 | 19/01/2018 08:39:58
OBJ1 | 索引1 | 1 | 30/01/2018 19:57:00 | 30/01/2018 19:57:50

格式化数据

好的,到目前为止一切顺利,在这种情况下我选择了 PIVOT 解决方案,但首先我需要格式化源数据以使其成为可能。可以使用Calendar表格过滤或分组记录。

ON         cl.StartTime >= DATEADD(minute, 30, c.cDate)
AND        cl.StartTime < DATEADD(day, 1, c.cDate)
Run Code Online (Sandbox Code Playgroud)
/* check first 10 records again */
SELECT     TOP 10 
           cl.ObjectName, cl.IndexName, cl.IndexType, cDayName,
           CASE cDayName 
                WHEN 'Saturday' THEN QUOTENAME('20-40 ' + FORMAT(cDate, 'dd-MM'))
                WHEN 'Sunday'   THEN QUOTENAME('5-40 ' + FORMAT(cDate, 'dd-MM'))
           END FormatDate,
           DATEDIFF(ss, cl.StartTime, cl.EndTime) DiffSeconds
FROM       CommandLog cl
INNER JOIN Calendar c
ON         cl.StartTime >= DATEADD(minute, 30, c.cDate)
AND        cl.StartTime < DATEADD(day, 1, c.cDate)
WHERE      cDayName IN ('Saturday', 'Sunday')
GO
Run Code Online (Sandbox Code Playgroud)
对象名称 | 索引名称 | 索引类型 | cDayName | 格式日期 | 差异秒
:--------- | :-------- | --------: | :------- | :------------ | ----------:
OBJ1 | 索引1 | 1 | 星期六 | [20-40 06-01] | 33
OBJ1 | 索引1 | 1 | 星期六 | [20-40 06-01] | 7
OBJ1 | 索引1 | 1 | 星期六 | [20-40 06-01] | 25
OBJ1 | 索引1 | 1 | 星期六 | [20-40 06-01] | 40
OBJ1 | 索引1 | 1 | 星期六 | [20-40 06-01] | 0
OBJ1 | 索引1 | 1 | 星期六 | [20-40 06-01] | 33
OBJ1 | 索引1 | 1 | 星期六 | [20-40 06-01] | 13
OBJ1 | 索引1 | 1 | 星期六 | [20-40 06-01] | 52
OBJ1 | 索引1 | 1 | 星期六 | [20-40 06-01] | 50
OBJ1 | 索引1 | 1 | 星期六 | [20-40 06-01] | 15

PIVOT 查询

嗯,这是我试图模拟的 PIVOT 查询。请记住,PIVOT 查询需要众所周知的列数,并且您通常必须动态生成它。

SELECT ObjectName, IndexName, IndexType,
      [20-40 06-01],[5-40 07-01],[20-40 13-01],[5-40 14-01],
      [20-40 20-01],[5-40 21-01],[20-40 27-01],[5-40 28-01]
FROM
(
    SELECT     cl.ObjectName, cl.IndexName, cl.IndexType,
               CASE cDayName WHEN 'Saturday' THEN '20-40 ' + FORMAT(cDate, 'dd-MM')
                             WHEN 'Sunday'   THEN '5-40 ' + FORMAT(cDate, 'dd-MM')
                             END FormatDate,
               DATEDIFF(ss, cl.StartTime, cl.EndTime) DiffSeconds
    FROM       CommandLog cl
    INNER JOIN Calendar c
    ON         cl.StartTime >= DATEADD(minute, 30, c.cDate)
    AND        cl.StartTime < DATEADD(day, 1, c.cDate)
    WHERE      cDayName IN ('Saturday', 'Sunday')
)src
PIVOT 
(
    SUM(DiffSeconds) FOR FormatDate IN ([20-40 06-01],[5-40 07-01],[20-40 13-01],
                                        [5-40 14-01],[20-40 20-01],[5-40 21-01],
                                        [20-40 27-01],[5-40 28-01])
) pvt;
GO
Run Code Online (Sandbox Code Playgroud)
对象名称 | 索引名称 | 索引类型 | 20-40 06-01 | 5-40 07-01 | 20-40 13-01 | 5-40 14-01 | 20-40 20-01 | 5-40 21-01 | 20-40 27-01 | 5-40 28-01
:--------- | :-------- | --------: | ----------: | ---------: | ----------: | ---------: | ----------: | ---------: | ----------: | ---------:
OBJ1 | 索引1 | 1 | 第1196章 1480 | 第747话 1010 | 第1197章 第1031章 901 | 605

生成动态查询

您可以使用STUFF函数动态生成列名。然后简单地连接字符串。

DECLARE @cols nvarchar(max);

SET @cols = STUFF((SELECT ',' + 
                   CASE cDayName 
                     WHEN 'Saturday' THEN QUOTENAME('20-40 ' + FORMAT(cDate, 'dd-MM'))
                     WHEN 'Sunday'   THEN QUOTENAME('5-40 ' + FORMAT(cDate, 'dd-MM'))
                   END
                   FROM Calendar
                   WHERE cDayName IN ('Saturday', 'Sunday')
                   ORDER BY cDate
                   FOR XML PATH(''), TYPE
                  ).value('.', 'NVARCHAR(MAX)'), 1, 1, '');

DECLARE @cmd nvarchar(max);

SET @cmd =     
    'SELECT ObjectName, IndexName, IndexType, ' + @cols
 + ' FROM
     (
       SELECT     cl.ObjectName, cl.IndexName, cl.IndexType,
                  CASE cDayName 
                       WHEN ''Saturday'' THEN ''20-40 '' + FORMAT(cDate, ''dd-MM'')
                       WHEN ''Sunday''   THEN ''5-40 '' + FORMAT(cDate, ''dd-MM'')
                  END FormatDate,
                  DATEDIFF(ss, cl.StartTime, cl.EndTime) DiffSeconds
       FROM       CommandLog cl
       INNER JOIN Calendar c
       ON         cl.StartTime >= DATEADD(minute, 30, c.cDate)
       AND        cl.StartTime < DATEADD(day, 1, c.cDate)
       WHERE      cDayName IN (''Saturday'', ''Sunday'')
     )src
    PIVOT 
    (
      SUM(DiffSeconds) FOR FormatDate IN (' + @cols + ')'
 + ') pvt';

EXEC sp_executesql @cmd;
GO
Run Code Online (Sandbox Code Playgroud)

最后结果

对象名称 | 索引名称 | 索引类型 | 20-40 06-01 | 5-40 07-01 | 20-40 13-01 | 5-40 14-01 | 20-40 20-01 | 5-40 21-01 | 20-40 27-01 | 5-40 28-01
:--------- | :-------- | --------: | ----------: | ---------: | ----------: | ---------: | ----------: | ---------: | ----------: | ---------:
OBJ1 | 索引1 | 1 | 第1196章 1480 | 第747话 1010 | 第1197章 第1031章 901 | 605

GROUP BY 解决方案

好的,现在是星期六下午,我除了读一些有趣的书之外无事可做。看看你的查询,我认为你可以用单个 GROUP BY + SUM(CASE... 查询使用Calendar表替换所有这些表连接。

类似于这个:

SELECT     cl.ObjectName, cl.IndexName, cl.IndexType,
           SUM(CASE WHEN cDay = 6 THEN DATEDIFF(ss, cl.StartTime, cl.EndTime) ELSE 0 END) AS [20-40 06-01],
           SUM(CASE WHEN cDay = 7 THEN DATEDIFF(ss, cl.StartTime, cl.EndTime) ELSE 0 END) AS [5-40 07-01],
           SUM(CASE WHEN cDay = 13 THEN DATEDIFF(ss, cl.StartTime, cl.EndTime) ELSE 0 END) AS [20-40 13-01],
           SUM(CASE WHEN cDay = 14 THEN DATEDIFF(ss, cl.StartTime, cl.EndTime) ELSE 0 END) AS [5-40 14-01],
           SUM(CASE WHEN cDay = 20 THEN DATEDIFF(ss, cl.StartTime, cl.EndTime) ELSE 0 END) AS [20-40 20-01],
           SUM(CASE WHEN cDay = 21 THEN DATEDIFF(ss, cl.StartTime, cl.EndTime) ELSE 0 END) AS [5-40 21-01],
           SUM(CASE WHEN cDay = 27 THEN DATEDIFF(ss, cl.StartTime, cl.EndTime) ELSE 0 END) AS [20-40 27-01],
           SUM(CASE WHEN cDay = 28 THEN DATEDIFF(ss, cl.StartTime, cl.EndTime) ELSE 0 END) AS [5-40 28-01]
FROM       CommandLog cl
INNER JOIN Calendar c
ON         cl.StartTime >= DATEADD(minute, 30, c.cDate)
AND        cl.StartTime < DATEADD(day, 1, c.cDate)
WHERE      cDayName IN ('Saturday', 'Sunday')
GROUP BY   cl.ObjectName, cl.IndexName, cl.IndexType;
GO
Run Code Online (Sandbox Code Playgroud)
对象名称 | 索引名称 | 索引类型 | 20-40 06-01 | 5-40 07-01 | 20-40 13-01 | 5-40 14-01 | 20-40 20-01 | 5-40 21-01 | 20-40 27-01 | 5-40 28-01
:--------- | :-------- | --------: | ----------: | ---------: | ----------: | ---------: | ----------: | ---------: | ----------: | ---------:
OBJ1 | 索引1 | 1 | 第1196章 1480 | 第747话 1010 | 第1197章 第1031章 901 | 605

这个查询也可以动态生成:

DECLARE @cols nvarchar(max);

SET @cols = STUFF((SELECT ', ' + ('SUM(CASE WHEN cDay = ' 
                                 + FORMAT(cDay, 'D')
                                 + ' THEN DATEDIFF(ss, cl.StartTime, cl.EndTime) END) AS '
                                 + CASE cDayName 
                                        WHEN 'Saturday' THEN QUOTENAME('20-40 ' + FORMAT(cDate, 'dd-MM'))
                                        WHEN 'Sunday'   THEN QUOTENAME('5-40 ' + FORMAT(cDate, 'dd-MM'))
                                   END
                                 + ' ')
                   FROM Calendar
                   WHERE cDayName IN ('Saturday', 'Sunday')
                   ORDER BY cDate
                   FOR XML PATH(''), TYPE
                  ).value('.', 'NVARCHAR(MAX)'), 1, 1, '');

DECLARE @cmd nvarchar(max);

SET @cmd =   'SELECT     cl.ObjectName, cl.IndexName, cl.IndexType, '
           + @cols
           + 'FROM       CommandLog cl
              INNER JOIN Calendar c
              ON         cl.StartTime >= DATEADD(minute, 30, c.cDate)
              AND        cl.StartTime < DATEADD(day, 1, c.cDate)
              WHERE      cDayName IN (''Saturday'', ''Sunday'')
              GROUP BY   cl.ObjectName, cl.IndexName, cl.IndexType';

/* REMOVE Warning: Null value is eliminated by an aggregate or other SET operation. */
SET ANSI_WARNINGS OFF;

EXEC sp_executesql @cmd;
GO
Run Code Online (Sandbox Code Playgroud)

dbfiddle在这里

  • 很好的例子,尽管我会使用 [基于计数表的日期/日历表](http://www.sqlservercentral.com/articles/calendar/145206/) 创建方法而不是循环。 (2认同)