2016-05-19 47 views
1

我有以下的(简化)的数据库模式:如何根据两个WHERE条件和两个计算的COUNT字段将SQL查询结果拆分为列?

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拨弄玩:http://www.sqlfiddle.com/#!3/e6082

关系逻辑是这样的:每个人都可以在零或工作无限数量的ItemDocuments(多对多);每个ItemDocument只属于一个Item(一对多)。项目具有状态1 - 活动,2 - 关闭

我需要的是满足下列要求的报告:

  • 每个人在个人表,有关于这个人ItemDocuments项目显示计数
  • 计数应在两列由ItemStatus拆分
  • 查询应通过两个可选的日期时间(使用两者之间的ItemDocuments.Date现场条件)和项目数是过滤也应分为两个阶段
  • 如果一个人没有分配任何ItemDocuments,它仍然应该在结果中显示所有计数值设置为0
  • 如果一个人有一个以上ItemDocument的项目,项目仍应该只计算一次

从本质上讲,这里的结果应该如何看就像如果我用这两个时期为NULL(读取所有数据):

[PersonName] [Active Items for period 1] [Closed Items for period 1] [Active Items for period 2] [Closed Items for period 2] 
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
    'Peter' 1 1 1 1 
    'John' 1 0 1 0 
    'Anna' 0 0 0 0 

虽然我可以单独为每个需求的SQL查询,我理解如何将所有这些结合在一起是有问题的。

例如,我可以在两列使用

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 

分裂ItemStatus计数和我可以由两个时期筛选(最大/从MS SQL服务器规范分钟日期常量,以避免任意周期的日期的NULL)使用

between coalesce(@start1, '1753-01-01') and coalesce(@end1, '9999-12-31') 
between coalesce(@start2, '1753-01-01') and coalesce(@end2, '9999-12-31') 

但如何将所有这些结合在一起考虑表之间的JOIN?

是否有任何技术,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 
+0

具有所有一个很好的问题应该有,样本数据(即使是小提琴!),自己的努力,预计输出,清晰的解释(allthough它仍然很难神交)...投票起来 – Shnugo

+1

我试图避免你的*丑陋的子查询重复*使用a CTE ... – Shnugo

回答

1

我没有绝对的把握,如果我真的得到了你想要的,但你可以试试这个

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 
+0

谢谢,它几乎可行。只有一个问题 - 目前它计数文件,但我需要物品数量。我的意思是 - 如果一个项目有多个分配给一个人的ItemDocuments,那么这个项目在每个活动/关闭期间应该只计算一次。我想,为此需要更深层的分组/ CTE,也许还有一些SUM而不是COUNT。 – JustAMartin

+0

试着减少* CTE中的列*到真正需要的列(没有'DocName'或'DocDate'并将CTE更改为'SELECT DISTINCT'。应该足够... – Shnugo

+0

好吧,我只是尝试修改CTE并最终得到它的工作 – JustAMartin

相关问题