SQL:对多个 table 进行分组并加入一个范围,返回包括 0 在内的计数

SQL: Grouping across multiple table joins with a range, returning counts including 0

好的,所以这是一个非常复杂的查询(至少对我而言),涉及四个 table 中的数据、通用 Table 表达式、联接、分组和排序。

基本上,我希望统计一个范围内的用户总回答数。

示例 tables:

Cutrange UserID 列表及其所属范围

uID  | Range
-------------
1033 | 2
1033 | 4
1140 | 1
1140 | 4
1145 | 2
1145 | 4
1146 | 3
1146 | 4
1234 | 2
1234 | 4
...

答案 收集每个用户每个问题的回答值

QuesID | Value | ScaleID | uID
---------------------------------
 8     | 2     | 10      | 1033
 179   | 1     | 159     | 1033
 180   | 1     | 175     | 1033
 8     | 4     | 12      | 1146
 179   | 1     | 159     | 1146
 180   | 1     | 175     | 1146     
 8     | 2     | 10      | 1234
 179   | 2     | 160     | 1234
 180   | 2     | 178     | 1234
 ...

量表 定义每个问题的答案范围

QuesID | Value | ScaleID
------------------------
179    | 1     | 159
179    | 2     | 160
180    | 0     | 174
180    | 1     | 175 
180    | 2     | 176
180    | 3     | 177
180    | 4     | 178
180    | 5     | 179
...

RangeDef 定义分组数据的范围

CutID | Range | RangeDef
-------------------------
1     | 1     | 0-1
1     | 2     | 2-3
1     | 3     | 4-5
1     | 4     | All

我正在加入上面的 tables(实际上是常见的 Table 表达式,是其他加入的结果)以根据每个可能的答案给出每个问题的 UserID 计数报告跨范围分组的值。

这里是查询:

SELECT Scales.QuesID, 
Scales.Value, 
Scales.ScaleID, 
COUNT(Cutrange.uID) AS ttl, 
Cutrange.Range
FROM Cutrange 
JOIN Answers ON Cutrange.uID = Answers.uID 
RIGHT JOIN Scales ON Answers.ScaleID = Scales.ScaleID
GROUP BY Scales.QuesID, 
Scales.Value, 
Scales.ScaleID, 
Cutrange.Range 
ORDER BY Scales.QuesID, 
Scales.Value, 
Scales.ScaleID, 
Cutrange.Range

其中returns:

QuesID | Value | ScaleID | ttl | Range
---------------------------------------
179    | 1     | 159     | 16  | 1
179    | 1     | 159     | 1   | 2
179    | 1     | 159     | 17  | 4
179    | 2     | 160     | 1   | 1
179    | 2     | 160     | 1   | 2
179    | 2     | 160     | 2   | 4
180    | 0     | 174     | 0   | NULL
180    | 1     | 175     | 5   | 1
180    | 1     | 175     | 1   | 2
180    | 1     | 175     | 6   | 4
180    | 2     | 176     | 12  | 1
180    | 2     | 176     | 1   | 2
180    | 2     | 176     | 13  | 4
180    | 3     | 177     | 0   | NULL
180    | 4     | 178     | 0   | NULL
180    | 5     | 179     | 0   | NULL

(注意 ttl 反映的数据集比我在上面的示例 table 中包含的几行更大的数据集)

这很接近,但它不包括范围内所有 4 项的总计。这是因为在这个特定的数据子集中,none 的用户属于范围 3。我希望代表所有范围,并在适当的地方使用 0 值。所以我用 RangeDef table.

添加了一个 JOIN
SELECT Scales.QuesID, 
Scales.Value, 
Scales.ScaleID, 
COUNT(Cutrange.uID) AS ttl, 
RangeDef.Range
FROM Cutrange 
JOIN Answers ON Cutrange.uID = Answers.uID 
RIGHT JOIN Scales ON Answers.ScaleID = Scales.ScaleID, RangeDef
WHERE RangeDef.CutID = 1
GROUP BY Scales.QuesID, 
Scales.Value, 
Scales.ScaleID, 
RangeDef.Range
ORDER BY Scales.QuesID, Scales.Value, 
Scales.ScaleID, RangeDef.Range

其中returns:

QuesID | Value | ScaleID | ttl | Range
---------------------------------------
179    | 1     | 159     | 34  | 1
179    | 1     | 159     | 34  | 2
179    | 1     | 159     | 34  | 3
179    | 1     | 159     | 34  | 4
-------+-------+---------+-----+-------
179    | 2     | 160     | 4   | 1
179    | 2     | 160     | 4   | 2
179    | 2     | 160     | 4   | 3
179    | 2     | 160     | 4   | 4
-------+-------+---------+-----+-------
180    | 0     | 174     | 0   | 1
180    | 0     | 174     | 0   | 2
180    | 0     | 174     | 0   | 3
180    | 0     | 174     | 0   | 4
-------+-------+---------+-----+-------
180    | 1     | 175     | 12  | 1
180    | 1     | 175     | 12  | 2
180    | 1     | 175     | 12  | 3
180    | 1     | 175     | 12  | 4
-------+-------+---------+-----+-------
180    | 2     | 176     | 26  | 1
180    | 2     | 176     | 26  | 2
180    | 2     | 176     | 26  | 3
180    | 2     | 176     | 26  | 4
-------+-------+---------+-----+-------
180    | 3     | 177     | 0   | 1
180    | 3     | 177     | 0   | 2
180    | 3     | 177     | 0   | 3
180    | 3     | 177     | 0   | 4
...

这给了我正确的分组,但是 ttl 列现在关闭了。这是因为 COUNT(Cutrange.uID) 需要按 Cutrange.Range 分组。现在它按 RangeDef.Range 分组。我不知道如何按范围对 ttl 列进行分组,并且还包括所有 4 个范围。 当我将 GROUP BY 语句更改为包含 Cutrange.Range 时,我得到以下信息:

SELECT Scales.QuesID, 
       Scales.Value, 
       Scales.ScaleID, 
       COUNT(Cutrange.uID) AS ttl, 
       RangeDef.Range
FROM Cutrange 
JOIN Answers ON Cutrange.uID = Answers.uID 
RIGHT JOIN Scales ON Answers.ScaleID = Scales.ScaleID, RangeDef
WHERE RangeDef.CutID = 1
GROUP BY     Scales.QuesID, 
             Scales.Value, 
             Scales.ScaleID, 
             Cutrange.Range, 
             RangeDef.Range
ORDER BY  Scales.QuesID, 
          Scales.Value, 
          Scales.ScaleID, 
          RangeDef.Range




QuesID | Value | ScaleID | ttl | Range
---------------------------------------
179    | 1     | 159     | 16  | 1
179    | 1     | 159     | 1   | 1
179    | 1     | 159     | 17  | 1
-------+-------+---------+-----+-------
179    | 1     | 159     | 16  | 2
179    | 1     | 159     | 1   | 2
179    | 1     | 159     | 17  | 2
-------+-------+---------+-----+-------
179    | 1     | 159     | 16  | 3
179    | 1     | 159     | 1   | 3
179    | 1     | 159     | 17  | 3
-------+-------+---------+-----+-------
179    | 1     | 159     | 16  | 4
179    | 1     | 159     | 1   | 4
179    | 1     | 159     | 17  | 4
-------+-------+---------+-----+-------
179    | 2     | 160     | 1   | 1
179    | 2     | 160     | 1   | 1
179    | 2     | 160     | 2   | 1
-------+-------+---------+-----+-------
179    | 2     | 160     | 1   | 2
179    | 2     | 160     | 1   | 2
179    | 2     | 160     | 2   | 2
-------+-------+---------+-----+-------
179    | 2     | 160     | 1   | 3
179    | 2     | 160     | 1   | 3
179    | 2     | 160     | 2   | 3
-------+-------+---------+-----+-------
179    | 2     | 160     | 1   | 4
179    | 2     | 160     | 1   | 4
179    | 2     | 160     | 2   | 4
-------+-------+---------+-----+-------
180    | 0     | 174     | 0   | 1
180    | 0     | 174     | 0   | 2
180    | 0     | 174     | 0   | 3
180    | 0     | 174     | 0   | 4
-------+-------+---------+-----+-------
180    | 1     | 175     | 5   | 1
180    | 1     | 175     | 1   | 1
180    | 1     | 175     | 6   | 1
-------+-------+---------+-----+-------
180    | 1     | 175     | 5   | 2
180    | 1     | 175     | 1   | 2
180    | 1     | 175     | 6   | 2
-------+-------+---------+-----+-------
180    | 1     | 175     | 5   | 3
180    | 1     | 175     | 1   | 3
180    | 1     | 175     | 6   | 3
-------+-------+---------+-----+-------
180    | 1     | 175     | 5   | 4
180    | 1     | 175     | 1   | 4
180    | 1     | 175     | 6   | 4
...

所以这是不正确的,并没有真正给我任何我可以使用的东西。

这是我需要的:

QuesID | Value | ScaleID | ttl | Range
---------------------------------------
179    | 1     | 159     | 16  | 1
179    | 1     | 159     | 1   | 2
179    | 1     | 159     | 0   | 3
179    | 1     | 159     | 17  | 4
-------+-------+---------+-----+-------
179    | 2     | 160     | 1   | 1
179    | 2     | 160     | 1   | 2
179    | 2     | 160     | 0   | 3
179    | 2     | 160     | 2   | 4
-------+-------+---------+-----+-------
180    | 0     | 174     | 0   | 1
180    | 0     | 174     | 0   | 2
180    | 0     | 174     | 0   | 3
180    | 0     | 174     | 0   | 4
-------+-------+---------+-----+-------
180    | 1     | 175     | 5   | 1
180    | 1     | 175     | 1   | 2
180    | 1     | 175     | 0   | 3
180    | 1     | 175     | 6   | 4
-------+-------+---------+-----+-------
180    | 2     | 176     | 12  | 1
180    | 2     | 176     | 1   | 2
180    | 2     | 176     | 0   | 3
180    | 2     | 176     | 13  | 4
-------+-------+---------+-----+-------
180    | 3     | 177     | 0   | 1
180    | 3     | 177     | 0   | 2
180    | 3     | 177     | 0   | 3
180    | 3     | 177     | 0   | 4
...

我试过使用 GROUPING SETS,并在 ttl 中添加 OVER(PARTITION BY),但是 none 仍然有效。我还在 RangeDef table 上尝试了不同的 JOIN 类型(CROSS、RIGHT OUTER),但还是没有成功。 我真的认为

COUNT(Cutrange.uID) OVER(Partition By Cutrange.Range) AS ttl

是答案,但 SQL 似乎要求 Cutrange.uID 包含在聚合函数(我认为 COUNT() 是)或 GROUP BY 语句中。我不是在按用户查找报告,我是在按问题查找报告。

我也尝试在 Answers table(JOIN on uID)中包含 Range 以简化最终查询,但仍然没有得到我在那里。我仍然 运行 遇到范围内 COUNT 个分组的问题。

如有任何帮助,我们将不胜感激。希望上面的数据不会太混乱,所有数据都是从一个更大的数据集中过滤出来的,同样,我上面的来源 table 都是 CTE。提前致谢!!

我注意到的第一件事是您正在对 RangeDef 进行笛卡尔连接,这就是为什么当您添加 table 时您的计数变得异常。我发现构建查询的最佳方法是从 table 开始,您总是希望得到结果并从那里充实查询。在这种情况下,您想从 RangeDef 开始。

SELECT Scales.QuesID, 
Scales.Value, 
Scales.ScaleID, 
COUNT(Cutrange.uID) AS ttl, 
RangeDef.Range
FROM RangeDef
LEFT OUTER JOIN Cutrange
  ON RangeDef.Range = Cutrange.Range
JOIN Answers 
  ON Cutrange.uID = Answers.uID 
RIGHT JOIN Scales 
  ON Answers.ScaleID = Scales.ScaleID
WHERE RangeDef.CutID = 1
GROUP BY Scales.QuesID, 
Scales.Value, 
Scales.ScaleID, 
RangeDef.Range
ORDER BY Scales.QuesID, Scales.Value, 
Scales.ScaleID, RangeDef.Range;

未经测试,但至少能让您走上正轨。

原来你已经很接近了。我将 Scales 上的 RIGHT JOIN 更改为在 RangeDef 上执行笛卡尔连接的子选择。所有这一切实际上是在 Scales table 上添加一个范围列。如果这能为您解决问题,请告诉我。

SELECT s.QuesID, 
s.Value, 
s.ScaleID, 
COUNT(Cutrange.uID) AS ttl, 
s.Range
FROM Cutrange
JOIN Answers 
  ON Cutrange.uID = Answers.uID 
RIGHT JOIN (SELECT s.QuesID, s.Value, s.ScaleID, rd.range FROM Scales s, RangeDef rd WHERE rd.CutID = 1)  s
  ON Answers.ScaleID = s.ScaleID
 AND Cutrange.range = s.range
GROUP BY s.QuesID, 
s.Value, 
s.ScaleID, 
s.Range
ORDER BY s.QuesID, s.Value, 
s.ScaleID, s.Range;

sqlfiddle 注意:我使用 MySQL 来测试它,所以由于保留字,我不得不将范围更改为 range2。使用 MS SQL Server 2014 的选项现在已关闭。