计算子组的行而忽略重复

时间:2015-12-30 12:09:30

标签: sql sql-server database count

我找不到以抽象和一般方式描述我的问题的方法,所以我只提供一个最小的例子:

我们说我有这三个简单的表格:

CREATE TABLE Document(
  [Id] int IDENTITY(1, 1) NOT NULL PRIMARY KEY,
  [Title] nvarchar(MAX),
  [Patient] nvarchar(MAX)
);

CREATE TABLE Link(
  DocumentId INT FOREIGN KEY REFERENCES Document(Id),
  Text nvarchar(max)  
);

CREATE TABLE ReadStatus( 
  DocumentId INT FOREIGN KEY REFERENCES Document(Id),
  IsRead Bit NOT NULL,
  UserId Int NOT NULL
);
  • 我们有一套文件
  • 文档可以包含0个或更多链接
  • 用户可以读取文档 - 由ReadStatus表跟踪,该表将用户与文档相关联,IsRead=1表示该用户已读取文档{{1}这意味着它尚未被该用户阅读。
  • 如果对于文档IsRead=0和用户XA表中不存在行,我们假设用户ReadStatus没有读取文档{ {1}}。

现在,我需要运行一个查询来选择所有患者。对于每位患者,我需要可用文件的总数和已经阅读的文件数(即A)。这就是我到目前为止所做的:

X

当文档(已经读取)具有多个链接时,会出现问题。如果该文档有3个链接,则INNER JOIN与Link表生成的笛卡尔积将导致IsRead=1选择为3而不是1。

换句话说,鉴于此数据:

SELECT d.Patient,
        COUNT(DISTINCT d.Id) AS DocumentCount,
        COUNT(NULLIF(rs.IsRead,0)) AS ReadDocumentCount,
        COUNT(*) OVER () AS TotalPatientCount
FROM Document d
LEFT OUTER JOIN ReadStatus AS rs ON d.Id = rs.DocumentId AND rs.UserId = 123
INNER JOIN Link AS l ON d.Id = l.DocumentId AND l.Text IN ('Link W', 'Link X', 'Link T', 'Link Z')
GROUP BY d.Patient

我得到了这个结果:

ReadDocumentCount

这是我想要的:

INSERT INTO Document(Title, Patient) VALUES('Doc A', 'Mike')
INSERT INTO Document(Title, Patient) VALUES('Doc B', 'Mike')

INSERT INTO Link(DocumentId, Text) VALUES(1, N'Link W')
INSERT INTO Link(DocumentId, Text) VALUES(1, N'Link X')
INSERT INTO Link(DocumentId, Text) VALUES(1, N'Link Y')
INSERT INTO Link(DocumentId, Text) VALUES(2, N'Link Z')

INSERT INTO ReadStatus(DocumentID, IsRead, UserId) VALUES(1, 1, 123)
INSERT INTO ReadStatus(DocumentID, IsRead, UserId) VALUES(2, 0, 123)

SQL小提琴:http://sqlfiddle.com/#!6/e06bf/3

1 个答案:

答案 0 :(得分:6)

您也可以有条件地使用COUNT(DISTINCT)

SELECT d.Patient,
        COUNT(DISTINCT d.Id) AS DocumentCount,
        COUNT(DISTINCT (CASE WHEN rs.IsRead <> 0 THEN d.id END)) AS ReadDocumentCount,
        COUNT(*) OVER () AS TotalPatientCount
FROM Document d LEFT OUTER JOIN
     ReadStatus rs
     ON d.Id = rs.DocumentId AND rs.UserId = 123 INNER JOIN
     Link l
     ON d.Id = l.DocumentId AND l.Text IN ('Link W', 'Link X', 'Link T', 'Link Z')
GROUP BY d.Patient;