从 SQL SELECT 中的子查询和 ROW_NUMBER 窗口函数生成“平均”列

Posted

技术标签:

【中文标题】从 SQL SELECT 中的子查询和 ROW_NUMBER 窗口函数生成“平均”列【英文标题】:Generate 'average' column from sub query and ROW_NUMBER window function in SQL SELECT 【发布时间】:2020-10-19 16:15:06 【问题描述】:

我有以下 SQL Server 表(带有示例数据):

问卷调查

id | coachNodeId | youngPersonNodeId | complete
1  | 12          | 678               | 1
2  | 12          | 52                | 1
3  | 30          | 99                | 1
4  | 12          | 678               | 1
5  | 12          | 678               | 1
6  | 30          | 99                | 1
7  | 12          | 52                | 1
8  | 30          | 102               | 1

回答

id | questionnaireId | score
1  | 1               | 1
2  | 2               | 3
3  | 2               | 2
4  | 2               | 5
5  | 3               | 5
6  | 4               | 5
7  | 4               | 3
8  | 5               | 4
9  | 6               | 1
10 | 6               | 3
11 | 7               | 5
12 | 8               | 5

内容节点

id  | text
12  | Zak
30  | Phil
52  | Jane
99  | Ali
102 | Ed
678 | Chris 

我有以下 T-SQL 查询:

SELECT
    Questionnaire.id AS questionnaireId, 
    coachNodeId AS coachNodeId, 
    coachNode.[text] AS coachName, 
    youngPersonNodeId AS youngPersonNodeId, 
    youngPersonNode.[text] AS youngPersonName,
    ROW_NUMBER() OVER (PARTITION BY Questionnaire.coachNodeId, Questionnaire.youngPersonNodeId ORDER BY Questionnaire.id) AS questionnaireNumber,
    score = (SELECT AVG(score) FROM Answer WHERE Answer.questionnaireId = Questionnaire.id)
FROM            
    Questionnaire
LEFT JOIN 
    ContentNode AS coachNode ON Questionnaire.coachNodeId = coachNode.id 
LEFT JOIN 
    ContentNode AS youngPersonNode ON Questionnaire.youngPersonNodeId = youngPersonNode.id
WHERE        
    (complete = 1)
ORDER BY 
    coachNodeId, youngPersonNodeId

此查询输出以下示例数据:

questionnaireId | coachNodeId | coachName | youngPersonNodeId | youngPersonName | questionnaireNumber | score
1               | 12          | Zak       | 678               | Chris           | 1                   | 1
2               | 12          | Zak       | 52                | Jane            | 1                   | 3
3               | 30          | Phil      | 99                | Ali             | 1                   | 5
4               | 12          | Zak       | 678               | Chris           | 2                   | 4
5               | 12          | Zak       | 678               | Chris           | 3                   | 4
6               | 30          | Phil      | 99                | Ali             | 2                   | 2
7               | 12          | Zak       | 52                | Jane            | 2                   | 5
8               | 30          | Phil      | 102               | Ed              | 1                   | 5

解释一下这里发生了什么……有各种各样的教练,他们的工作是对各种年轻人进行问卷调查,并记录分数。教练可能会在以后对同一个年轻人重复几次问卷调查,希望他们得到更好的分数。我要达到的最终目标是教练的经理想看看教练的表现如何,所以他们想看看问卷的分数是否会上升。窗口函数代表了一种方法,用于确定同一教练/年轻人组合进行了多少次问卷调查。

我需要能够根据问卷编号确定平均分数。例如,教练“Zak”在他的第一份问卷(其中问卷编号 = 1)中记录了“1”和“3”分,因此平均值为 2。对于他的第二份问卷(其中问卷编号 = 2),分数为3' 和 '5' 所以平均值是 4。所以在分析这些数据时,我们知道随着时间的推移,Zak 的问卷分数已经从第一次的平均“2”提高到第二次的平均“4”。

我觉得查询需要按coachNodeIdquestionnaireNumber 值分组,所以它会输出类似这样的内容(我省略了questionnaireIdyoungPersonNodeIdyoungPersonNamescore列,因为它们对输出并不重要——它们仅用于导出平均分数——并且对于结果的分组方式没有用处):

coachNodeId | coachName | questionnaireNumber | averageScore
12          | Zak       | 1                   | 2                      (calculation: (1 + 3) / 2)
12          | Zak       | 2                   | 4                      (calculation: (3 + 5) / 2)
12          | Zak       | 3                   | 4                      (only one value: 4)
30          | Phil      | 1                   | 5                      (calculation: (5 + 5) / 2)
30          | Phil      | 2                   | 2                      (only one value: 2)

谁能建议我如何修改我的查询以根据子查询的分数和ROW_NUMBER 窗口函数输出平均分数?我已经达到了我的 SQL 技能的极限!

非常感谢。

【问题讨论】:

【参考方案1】:

没有样本数据有点难以分辨,但我认为您描述的是聚合:

SELECT q.coachNodeId AS coachNodeId, 
       cn.[text] AS coachName, 
       q.youngPersonNodeId AS youngPersonNodeId, 
       ypn.[text] AS youngPersonName,
       AVG(score)
FROM Questionnaire q JOIN
     ContentNode cn
     ON q.coachNodeId = cn.id  JOIN
     ContentNode ypn
     ON q.youngPersonNodeId = ypn.id LEFT JOIN
     Answer a
     ON a.questionnaireId = q.id
WHERE complete = 1
GROUP BY q.coachNodeID, cn.[text] AS coachName, 
         q.youngPersonNodeId, ypn.[text]

【讨论】:

感谢@GordonLinoff,我添加了示例数据。一个相关的事情是“答案”表可能包含单个问卷的多个值,因此我使用子查询来平均这些值。答案表不包含 youngPersonNodeIdcoachNodeId 值,因此我们无法单独在该表中建立特定教练/年轻人组合的第一、第二、第三等问卷,因此我为什么使用ROW_NUMBER 函数来确定同一教练/年轻人组合进行问卷调查的次数。

以上是关于从 SQL SELECT 中的子查询和 ROW_NUMBER 窗口函数生成“平均”列的主要内容,如果未能解决你的问题,请参考以下文章

SQL中的子查询

SQL 编译错误:无法评估不受支持的子查询类型 - SELECT 子句中的函数调用

Oracle SQL,在 SELECT 标头中的子查询中返回唯一(最大)行(在 FROM、WHERE 之前)

MySQL的子查询中FROM和EXISTS子句的使用教程

MySQL基础语法之子链接查询和特殊查询(union 和 limit)

SELECT 子句中的子查询