You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQL Server按主键分组时为何强制聚合同表列?求更优方案

前言

SQL分组有一条通用规则:若要在SELECT子句中引用某列,要么将其包裹在聚合函数中,要么将其加入GROUP BY子句。我理解这条规则及其原因,但我遇到了一个特殊场景,认为应该存在例外:当多表关联且按其中一个表的主键分组时。


假设有两张表(table_A和table_B)。如果我按table_A的主键(PK)分组,那么table_A的其他列在每个分组内的值必然完全相同。我认为SQL Server应该允许我直接引用table_A的其他列,无需包裹聚合函数。

我期望能正常运行的查询如下:

SELECT
    AVG(B.score) AS average_score
    ,A.f_name
    ,A.l_name
FROM table_A A
JOIN table_B B ON B.table_A_id = A.id
GROUP BY
    A.id

但实际会收到如下错误:

Column 'table_A.f_name' is invalid in the select list because it is
not contained in either an aggregate function or the GROUP BY clause.

我想到了几种解决办法,但都不尽人意。

方案1 - 冗余聚合
SELECT
    AVG(B.score) AS average_score
    ,MIN(A.f_name) AS first_name
    ,MIN(A.l_name) AS last_name
FROM table_A A
JOIN table_B B ON B.table_A_id = A.id
GROUP BY
    A.id

查询结果:

average_score first_name last_name
------------- ---------- ----------
12            Bob        Ross      
11            Ricky      Bobby     
12            Rick       Ross 

我不喜欢这种方式,因为它模糊了代码的实际意图:我们并非真的要取最小值。实际上无论选择哪种聚合函数,MIN()和MAX()都会返回相同结果。

SELECT
    AVG(B.score) AS average_score
    ,MAX(A.f_name) AS first_name
    ,MAX(A.l_name) AS last_name
FROM table_A A
JOIN table_B B ON B.table_A_id = A.id
GROUP BY
    A.id

查询结果:

average_score first_name last_name
------------- ---------- ----------
12            Bob        Ross      
11            Ricky      Bobby     
12            Rick       Ross  

这说明table_A的所有列在分组内值完全相同,因此我认为不应强制我对这些列使用聚合函数,毕竟已经按主键分组了。

方案2 - 冗余分组

这里我们将SELECT子句中未用聚合函数的所有列都加入GROUP BY。

SELECT
    AVG(B.score) AS average_score
    ,A.f_name
    ,A.l_name
FROM table_A A
JOIN table_B B ON B.table_A_id = A.id
GROUP BY
    A.f_name
    ,A.l_name
    ,A.id

查询结果:

average_score f_name     l_name
------------- ---------- ----------
12            Bob        Ross      
11            Ricky      Bobby     
12            Rick       Ross      

同样,这种方式模糊了代码意图。我们的目标是让每个用户自成一组,但代码看起来像是先按firstName分组,再按lastName细分。由于可能存在重名用户,仍需加入主键确保每组仅一人,但如果直接按主键分组本来可以一步到位。这种写法在语法上不够简洁(不确定性能是否受影响,但可读性差)。

方案3 - 冗余子查询

这里我们在SELECT子句中使用子查询而非关联查询。

SELECT
    AVG(B.score) AS average_score
    ,(SELECT A.f_name FROM table_A A WHERE A.id = B.table_A_id) AS first_name
    ,(SELECT A.L_name FROM table_A A WHERE A.id = B.table_A_id) AS last_name
FROM table_B B
GROUP BY
    B.table_A_id

查询结果:

average_score first_name last_name
------------- ---------- ----------
12            Bob        Ross      
11            Ricky      Bobby     
12            Rick       Ross      

我也不喜欢这种方式,因为每要返回table_A的一列就需要重复几乎相同的子查询。table_A和table_B显然更适合用关联查询。当需要返回更多数据时,问题会更严重,尤其是需要通过table_A关联其他表时。

SELECT
    AVG(B.score) AS average_score
    ,(SELECT A.f_name FROM table_A A WHERE A.id = B.table_A_id) AS first_name
    ,(SELECT A.L_name FROM table_A A WHERE A.id = B.table_A_id) AS last_name
    ,(SELECT C.team FROM table_C C WHERE C.id = (SELECT A.table_C_id FROM table_A A WHERE A.id = B.table_A_id)) AS team_name
FROM table_B B
GROUP BY
    B.table_A_id

查询结果:

average_score first_name last_name  team_name
------------- ---------- ---------- ----------
12            Bob        Ross       Team A    
11            Ricky      Bobby      Team A    
12            Rick       Ross       Team B    

以下是使用方案1实现相同逻辑的查询:

SELECT
    AVG(B.score) AS average_score
    ,MIN(A.f_name) AS first_name
    ,MIN(A.l_name) AS last_name
    ,MIN(C.team) AS team_name
FROM table_A A
JOIN table_B B ON B.table_A_id = A.id
JOIN table_C C ON C.id = A.table_C_id
GROUP BY
    A.id

查询结果:

average_score first_name last_name  team_name
------------- ---------- ---------- ----------
12            Bob        Ross       Team A    
11            Ricky      Bobby      Team A    
12            Rick       Ross       Team B    

大家是否也觉得这种情况很棘手?有没有比我想到的更优雅的解决办法?其他数据库也存在这个问题吗?


内容的提问来源于stack exchange,提问作者illchangethislater

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 19:47:54