如何转置结果集并按周分组?

How do I transpose a result set and group by week?

我有一个基于查询的视图:

SELECT CONVERT(VARCHAR(10), date, 103) AS date,
       eventid, name, time, pts
FROM results
WHERE DATEPART(yy, date) = 2019;

这提供了这样的数据集:

Date        EventID     Name            Time    Points
24/04/2019  10538       Fred Flintstone 22:27   10
24/04/2019  10538       Barney Rubble   22:50   9
24/04/2019  10538       Micky Mouse     23:17   8
24/04/2019  10538       Yogi Bear       23:54   7
24/04/2019  10538       Donald Duck     24:07   6
01/05/2019  10541       Barney Rubble   21:58   10
01/05/2019  10541       Fred Flintstone 22:00   9
01/05/2019  10541       Donald Duck     23:39   8
01/05/2019  10541       Yogi Bear       23:43   7
12/06/2019  10569       Fred Flintstone 22:06   10
12/06/2019  10569       Barney Rubble   22:22   9
12/06/2019  10569       Micky Mouse     23:05   8
12/06/2019  10569       Donald Duck     23:55   7

我需要每个名称的输出行,列出每轮的分数和总数,格式为:

Name            24/04/2019  01/05/2019  12/06/2019  total
Fred Flintstone     10           9          10       29
Barney Rubble        9          10           9       28
Yogi Bear            7           7           7       21
Micky Mouse          8                       8       16
Donald Duck          6           8                   14

一年中最多可以有 16 个非连续事件日期。

PIVOT 没什么问题,但对我来说,最简单、最有效的方法是执行 Cross Tab。语法更简洁、更便携、更易于理解。

首先是一些 DDL 和易于使用的示例数据。 <<< 了解如何执行此操作,它会更快地为您提供更好的答案。

SET NOCOUNT ON;
SET DATEFORMAT dmy; -- I need this because I'm American

-- DDL and easily consumable sample data
DECLARE @Results TABLE 
(
  [Date]  DATE,
  EventId INT,
  [Name]  VARCHAR(40), -- if indexed, go as narrow as possible
  [Time]  TIME,
  Points  INT,
  INDEX uq_poc_results CLUSTERED([Name],[EventId]) -- a covering index is vital for a query like this
); -- note: ^^^ this bad clustered index candidate, I went this route for simplicity

INSERT @Results VALUES
  ('4/04/2019', 10538, 'Fred Flintstone', '22:27',10),
  ('24/04/2019',10538, 'Barney Rubble',   '22:50',9),
  ('24/04/2019',10538, 'Micky Mouse ',    '23:17',8),
  ('24/04/2019',10538, 'Yogi Bear',       '23:54',7),
  ('24/04/2019',10538, 'Donald Duck',     '2307',6),
  ('01/05/2019',10541, 'Barney Rubble',   '21:58',10),
  ('01/05/2019',10541, 'Fred Flintstone', '22:00',9),
  ('01/05/2019',10541, 'Donald Duck',     '23:39',8),
  ('01/05/2019',10541, 'Yogi Bear',       '23:43',7),
  ('12/06/2019',10569, 'Fred Flintstone', '22:06',10),
  ('12/06/2019',10569, 'Barney Rubble',   '22:22',9),
  ('12/06/2019',10569, 'Micky Mouse',     '23:05',8),
  ('12/06/2019',10569, 'Donald Duck',     '23:55',7);

请注意,我在 (Name,EventId) 上创建了一个聚簇索引 - 我将使用一个非聚簇索引来覆盖您在现实世界中需要的列。如果您有很多行,那么您将需要该索引。

基本交叉表

SELECT [Name]       = r.[Name],
       [24/04/2019] = MAX(CASE r.[Date] WHEN '24/04/2019' THEN r.Points ELSE 0 END),
       [01/05/2019] = MAX(CASE r.[Date] WHEN '01/05/2019' THEN r.Points ELSE 0 END),
       [12/06/2019] = MAX(CASE r.[Date] WHEN '12/06/2019' THEN r.Points ELSE 0 END)
FROM     @Results AS r
GROUP BY r.[Name];

结果:

Name                 24/04/2019   01/05/2019   12/06/2019
-------------------- ------------ ------------ ------------
Barney Rubble        9            10           9
Donald Duck          6            8            7
Fred Flintstone      0            9            10
Micky Mouse          8            0            8
Yogi Bear            7            7            0

为了获得总数,我们可以将其包装在子查询中的逻辑中,并像这样添加列:

SELECT
  [Name]       = piv.N,
  [24/04/2019] = piv.D1,
  [01/05/2019] = piv.D2,
  [12/06/2019] = piv.D3,
  Total        = piv.D1+piv.D2+piv.D3
FROM
(
  SELECT r.[Name],
         MAX(CASE r.[Date] WHEN '24/04/2019' THEN r.Points ELSE 0 END),
         MAX(CASE r.[Date] WHEN '01/05/2019' THEN r.Points ELSE 0 END),
         MAX(CASE r.[Date] WHEN '12/06/2019' THEN r.Points ELSE 0 END)
  FROM     @Results AS r
  GROUP BY r.[Name]
) AS piv(N,D1,D2,D3);

Returns:

Name                24/04/2019  01/05/2019  12/06/2019  Total
------------------- ----------- ----------- ----------- -------
Barney Rubble       9           10          9           28
Donald Duck         6           8           7           21
Fred Flintstone     0           9           10          19
Micky Mouse         8           0           8           16
Yogi Bear           7           7           0           14

这不仅可以用很少的 SQL 获得您需要的东西,您还可以从子查询内的预聚合中获益。与 PIVOT 相比,这种方法的一个巨大好处是您可以在一个查询中执行多个聚合。以下是如何将此方法用于多个聚合的两个示例;这首先使用标准 GROUP BY 两次,另一个使用 window 聚合函数 (.. OVER (partition by, order by..):

--==== Traditional Approach
SELECT
  [Name]       = piv.N,
  [24/04/2019] = MAX(piv.D1),
  [01/05/2019] = MAX(piv.D2),
  [12/06/2019] = MAX(piv.D3),
  Total        = MAX(f.Ttl),
  Avg1         = AVG(piv.D1), -- 1st date (24/04/2019)
  Avg2         = AVG(piv.D2), -- 2nd date...
  Avg3         = AVG(piv.D3), -- 3rd date...
  TotalAvg     = AVG(f.Ttl) ,
  Mn           = MIN(f.Ttl) ,
  Mx           = MAX(f.Ttl) 
FROM
(
  SELECT r.[Name],
         MAX(CASE r.[Date] WHEN '24/04/2019' THEN r.Points ELSE 0 END),
         MAX(CASE r.[Date] WHEN '01/05/2019' THEN r.Points ELSE 0 END),
         MAX(CASE r.[Date] WHEN '12/06/2019' THEN r.Points ELSE 0 END)
  FROM     @Results AS r
  GROUP BY r.[Name]
) AS piv(N,D1,D2,D3)
CROSS APPLY (VALUES(piv.D1+piv.D2+piv.D3)) AS f(Ttl)
GROUP BY    piv.N;

--==== Leveraging Window Aggregates
SELECT
  [Name]       = piv.N,
  [24/04/2019] = piv.D1,
  [01/05/2019] = piv.D2,
  [12/06/2019] = piv.D3,
  Total        = f.Ttl,
  Avg1         = AVG(piv.D1) OVER(PARTITION BY piv.N ORDER BY (SELECT NULL)), -- 1st date (24/04/2019)
  Avg2         = AVG(piv.D2) OVER(PARTITION BY piv.N ORDER BY (SELECT NULL)), -- 2nd date...
  Avg3         = AVG(piv.D3) OVER(PARTITION BY piv.N ORDER BY (SELECT NULL)), -- 3rd date...
  TotalAvg     = AVG(f.Ttl)  OVER(PARTITION BY piv.N ORDER BY (SELECT NULL)),
  Mn           = MIN(f.Ttl)  OVER(PARTITION BY piv.N ORDER BY (SELECT NULL)),
  Mx           = MAX(f.Ttl)  OVER(PARTITION BY piv.N ORDER BY (SELECT NULL))
FROM
(
  SELECT r.[Name],
         MAX(CASE r.[Date] WHEN '24/04/2019' THEN r.Points ELSE 0 END),
         MAX(CASE r.[Date] WHEN '01/05/2019' THEN r.Points ELSE 0 END),
         MAX(CASE r.[Date] WHEN '12/06/2019' THEN r.Points ELSE 0 END)
  FROM     @Results AS r
  GROUP BY r.[Name]
) AS piv(N,D1,D2,D3)
CROSS APPLY (VALUES(piv.D1+piv.D2+piv.D3)) AS f(Ttl);

两个Return:

Name              24/04/2019  01/05/2019  12/06/2019  Total  Avg1   Avg2   Avg3   TotalAvg   Mn     Mx
----------------- ----------- ----------- ----------- ------ ------ ------ ------ ---------- ------ ------
Barney Rubble     9           10          9           28     9      10     9      28         28     28
Donald Duck       6           8           7           21     6      8      7      21         21     21
Fred Flintstone   0           9           10          19     0      9      10     19         19     19
Micky Mouse       8           0           8           16     8      0      8      16         16     16
Yogi Bear         7           7           0           14     7      7      0      14         14     14

要动态处理列,您需要查看: Cross Tabs and Pivots, Part 2 - Dynamic Cross Tabs 作者:杰夫·莫登。