我有一个包含年份列表的表格,从 2010 年开始,按照.
CREATE TABLE Years
(YearName int)
;
INSERT INTO Years
(YearName)
VALUES
(2010),
(2011),
(2012),
(2013),
(2014),
(2015),
(2016),
(2017),
(2018),
(2019),
(2020),
(2021),
(2022),
(2023),
(2024),
(2025)
我有第二张包含人的桌子,按照
CREATE TABLE People
(PersonID int PRIMARY KEY, PersonName varchar(50))
;
INSERT INTO People
(PersonID, PersonName)
VALUES
(1, 'Bob'),
(2, 'Kate'),
(3, 'Jo'),
(4, 'Fred')
;
我有一张表格,其中包含人们每年所做的各种类型的工作:
CREATE TABLE Workload
(ID int PRIMARY KEY, PersonID int, YearName int, WorkType varchar(8), Hours int)
;
INSERT INTO Workload
(ID, PersonID, YearName, WorkType, Hours)
VALUES
(1, 1, 2014, 'Plumbing', 7),
(2, 1, 2020, 'Washing', 9),
(3, 1, 2020, 'Cooking', 10),
(4, 1, 2020, 'Drawing', 4),
(5, 1, 2021, 'Reading', 2),
(6, 2, 2020, 'Washing', 9),
(7, 2, 2021, 'Cooking', 10),
(8, 2, 2022, 'Drawing', 4),
(9, 3, 2014, 'Cooking', 4),
(10, 3, 2014, 'Plumbing', 22),
(11, 3, 2015, 'Washing', 7)
;
我想总结每个人每年完成的工作总量。所以,我以此为出发点:
SELECT PersonName, YearName, SUM(Hours) as WorkDone
FROM People p INNER JOIN Workload w ON p.PersonID=w.PersonID
WHERE YearName BETWEEN YEAR(GetDate())-9 AND YEAR(GETDATE())
GROUP BY PersonName, YearName
这很好用,但我想要的是一个输出,在他们没有完成这些任务的每一年中,每个人的价值为零:
人 | 年 | 工作量 |
---|---|---|
鲍勃 | 2014 | 7 |
鲍勃 | 2015 | 0 |
鲍勃 | 2016 | 0 |
鲍勃 | 2017 | 0 |
鲍勃 | 2018 | 0 |
鲍勃 | 2019 | 0 |
鲍勃 | 2020 | 23 |
鲍勃 | 2021 | 0 |
鲍勃 | 2022 | 0 |
鲍勃 | 2023 | 0 |
凯特 | 2014 | 0 |
凯特 | 2015 | 0 |
凯特 | 2016 | 0 |
凯特 | 2017 | 0 |
凯特 | 2018 | 0 |
凯特 | 2019 | 0 |
凯特 | 2020 | 9 |
凯特 | 2021 | 10 |
凯特 | 2022 | 4 |
凯特 | 2023 | 0 |
...其他人和年份等等。
我如何最好地实现这一目标?我觉得我可能需要交叉应用这三个表,但似乎无法弄清楚如何这样做并获得我需要的结果。
您可以
cross join
年和人表生成所有可能的组合,然后将工作量表与 LEFT JOIN
- 这确保“缺失”的年/人元组不会被过滤掉。最后一步是聚合,COALESCE()
在不匹配的元组上返回0
。
SELECT p.PersonName, y.YearName, COALESCE(SUM(w.Hours), 0) as WorkDone
FROM People p
CROSS JOIN Years y
LEFT JOIN Workload w ON w.PersonID = p.PersonID AND w.YearName = y.YearName
WHERE y.YearName BETWEEN YEAR(GetDate())-9 AND YEAR(GETDATE())
GROUP BY p.PersonID, p.PersonName, y.YearName
ORDER BY p.PersonName, y.YearName
注意在
group by
子句中包含person id会更安全;两个不同的人可能有相同的名字,您可能不希望将他们的工作负载组合在一起。
我们还可以使用相关子查询(或
apply
)来进行工作负载计算,这将避免外部聚合:
SELECT p.PersonName, y.YearName, w.*
FROM People p
CROSS JOIN Years y
CROSS APPLY (
SELECT COALESCE(SUM(w.Hours), 0) as WorkDone
FROM Workload w
WHERE w.PersonID = p.PersonID AND w.YearName = y.YearName
) w
WHERE y.YearName BETWEEN YEAR(GetDate())-9 AND YEAR(GETDATE())
ORDER BY p.PersonName, y.YearName
通过使用此查询,提取每个人的最小年份,并根据不存在的天数设置零值
;with _list as (
select *
from (
SELECT
PersonName, YearName,sum(w.Hours) over(partition by p.PersonID,YearName) as Workload
,row_number() over(partition by p.PersonID,YearName order by p.PersonID) as rw
,min(YearName) over(partition by p.PersonID order by p.PersonID) as minYearName
--, SUM(Hours) as WorkDone
FROM People p
INNER JOIN Workload w ON p.PersonID=w.PersonID
)a
where a.rw=1
)
select
ISNULL(a.PersonName,b.PersonName) as PersonName ,b.YearName
,ISNULL(a.Workload,b.Workload) as Workload
from (
select a.PersonName,b.YearName,0 Workload
from Years b
cross join (
select PersonName,min(minYearName) as minYearName
from _list
group by PersonName
)a
where b.YearName between a.minYearName and DATEPART(YEAR ,GETDATE())
)b
left join _list a on a.YearName=b.YearName
and a.PersonName=b.PersonName