Sql server 2008 如何根据两个WHERE条件和两个计算计数字段将SQL查询结果拆分为列?
我有以下(简化的)数据库模式:Sql server 2008 如何根据两个WHERE条件和两个计算计数字段将SQL查询结果拆分为列?,sql-server-2008,Sql Server 2008,我有以下(简化的)数据库模式: Persons: [Id] [Name] ------------------- 1 'Peter' 2 'John' 3 'Anna' Items: [Id] [ItemName] [ItemStatus] ------------------- 10 'Cake' 1 20 'Dog' 2 ItemDocuments: [Id] [ItemId] [DocumentName] [Date] ------------------- 101 10 'Ca
Persons:
[Id] [Name]
-------------------
1 'Peter'
2 'John'
3 'Anna'
Items:
[Id] [ItemName] [ItemStatus]
-------------------
10 'Cake' 1
20 'Dog' 2
ItemDocuments:
[Id] [ItemId] [DocumentName] [Date]
-------------------
101 10 'CakeDocument1' '2016-01-01 00:00:00'
201 20 'DogDocument1' '2016-02-02 00:00:00'
301 10 'CakeDocument2' '2016-03-03 00:00:00'
401 20 'DogDocument2' '2016-04-04 00:00:00'
DocumentProcessors:
[PersonId] [DocumentId]
-------------------
1 101
1 201
2 301
我还设置了一个SQL提琴:
关系逻辑如下:每个人都可以处理零个或无限多个ItemDocuments(多对多);每个ItemDocument只属于一个项目(一对多)。项目状态为1-活动,2-关闭
我需要的是一份满足以下要求的报告:
- 对于Persons表中的每个人员,显示具有与此人相关的ItemDocuments的项目计数
- 计数应按ItemStatus分为两列
- 查询应该可以通过两个可选的日期周期进行过滤(在ItemDocuments.date字段上使用两个中间条件),并且项目计数也应该分为两个周期
- 如果一个人没有分配任何ItemDocuments,那么它仍然应该显示在结果中,并且所有计数值都设置为0
- 如果一个人对一个项目有多个ItemDocument,则该项目仍应仅计数一次
COUNT(CASE WHEN t.ItemStatus = 1 THEN 1 ELSE NULL END) AS Active,
COUNT(CASE WHEN t.ItemStatus = 2 THEN 1 ELSE NULL END) AS Closed
我可以使用以下方法按两个时段进行筛选(使用MS SQL server规范中的max/min date常量,以避免可选时段日期为空)
但考虑到表之间的连接,如何将所有这些结合在一起呢
是否有任何技术、join
或MS SQL Server特定的方法可以有效地做到这一点?
我的第一次尝试似乎可以按要求工作,但看起来像是多次丑陋的子查询重复:
DECLARE @start1 DATETIME, @start2 DATETIME, @end1 DATETIME, @end2 DATETIME
-- SET @start2 = '2017-01-01'
SELECT
p.Name,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(@start1, '1753-01-01') AND COALESCE(@end1, '9999-12-31')
)
) AS Active1,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(@start1, '1753-01-01') AND COALESCE(@end1, '9999-12-31')
)
) AS Closed1,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(@start2, '1753-01-01') AND COALESCE(@end2, '9999-12-31')
)
) AS Active2,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(@start2, '1753-01-01') AND COALESCE(@end2, '9999-12-31')
)
) AS Closed2
FROM Persons p
我不确定我是否真的得到了你想要的,但你可以试试这个
WITH AllData AS
(
SELECT p.Id AS PersonId
,p.Name AS Person
,id.Date AS DocDate
,id.DocumentName AS DocName
,i.ItemName AS ItemName
,i.ItemStatus AS ItemStatus
,CASE WHEN id.Date BETWEEN COALESCE(@start1, '1753-01-01') AND COALESCE(@end1, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod1
,CASE WHEN id.Date BETWEEN COALESCE(@start2, '1753-01-01') AND COALESCE(@end2, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod2
FROM Persons AS p
LEFT JOIN DocumentProcessors AS dp ON p.Id=dp.PersonId
LEFT JOIN ItemDocuments AS id ON dp.DocumentId=id.Id
LEFT JOIN Items AS i ON id.ItemId=i.Id
)
SELECT PersonID
,Person
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ActiveIn1
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ClosedIn1
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ActiveIn2
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ClosedIn2
FROM AllData
GROUP BY PersonID,Person
这有一个好问题,样本数据(甚至是一把小提琴!),自己的努力,预期的产出,清晰的解释(尽管仍然很难摸索)。。。投票给upI我试图通过使用CTE避免您丑陋的子查询重复…谢谢,它几乎可以工作。只有一个问题-目前它计数的文件,但我需要项目计数。我的意思是-如果一个项目为一个人分配了多个ItemDocuments,那么在每个活动/关闭期间,该项目仍应仅在每个期间计算一次。我想,这需要更深入的分组/CTE,可能需要一些总和而不是计数。尝试将CTE中的列减少到真正需要的列(没有
DocName
或DocDate
并将CTE更改为SELECT DISTINCT
。应该足够了……好的,我刚刚尝试修改了您的CTE,终于让它工作了。@JustAMartin,太好了,很高兴来到这里!编码愉快!
DECLARE @start1 DATETIME, @start2 DATETIME, @end1 DATETIME, @end2 DATETIME
-- SET @start2 = '2017-01-01'
SELECT
p.Name,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(@start1, '1753-01-01') AND COALESCE(@end1, '9999-12-31')
)
) AS Active1,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(@start1, '1753-01-01') AND COALESCE(@end1, '9999-12-31')
)
) AS Closed1,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(@start2, '1753-01-01') AND COALESCE(@end2, '9999-12-31')
)
) AS Active2,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(@start2, '1753-01-01') AND COALESCE(@end2, '9999-12-31')
)
) AS Closed2
FROM Persons p
WITH AllData AS
(
SELECT p.Id AS PersonId
,p.Name AS Person
,id.Date AS DocDate
,id.DocumentName AS DocName
,i.ItemName AS ItemName
,i.ItemStatus AS ItemStatus
,CASE WHEN id.Date BETWEEN COALESCE(@start1, '1753-01-01') AND COALESCE(@end1, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod1
,CASE WHEN id.Date BETWEEN COALESCE(@start2, '1753-01-01') AND COALESCE(@end2, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod2
FROM Persons AS p
LEFT JOIN DocumentProcessors AS dp ON p.Id=dp.PersonId
LEFT JOIN ItemDocuments AS id ON dp.DocumentId=id.Id
LEFT JOIN Items AS i ON id.ItemId=i.Id
)
SELECT PersonID
,Person
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ActiveIn1
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ClosedIn1
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ActiveIn2
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ClosedIn2
FROM AllData
GROUP BY PersonID,Person