SQL 分组查询中的非聚合列要求及实例解析

ops/2024/11/15 3:46:11/

在 SQL 查询中,当我们对数据进行分组时,通常会用到 GROUP BY 子句。SQL 标准要求:所有非聚合列(即没有使用聚合函数的列)都必须出现在 GROUP BY 子句中,或者是聚合函数的结果。这篇文章通过一个实例来说明这个规则的原因及如何正确书写查询语句。

场景描述

我们有三张表:Customers(顾客信息)、Orders(订单信息)、OrderItems(订单项信息),它们分别包含以下字段:

  • Customers

    • cust_id:顾客 ID
    • cust_name:顾客名称
  • Orders

    • order_num:订单号
    • cust_id:顾客 ID(与 Customers 表关联)
  • OrderItems

    • order_num:订单号(与 Orders 表关联)
    • quantity:商品数量
    • item_price:商品单价

需求是返回每个顾客的 cust_name 和与之关联的订单号 order_num,以及每个订单的总价 OrderTotal,并按顾客名称和订单号进行排序。

数据示例

Customers 表:

cust_idcust_name
cust10andy
cust1ben
cust2tony
cust22tom
cust221an
cust2217hex

Orders 表:

order_numcust_id
a1cust10
a2cust1
a3cust2
a4cust22
a5cust221
a7cust2217

OrderItems 表:

order_numquantityitem_price
a1100010
a220010
a31015
a42550
a51525
a777

正确的 SQL 查询

为了实现需求,我们需要将三张表进行连接,并计算每个订单的总价。计算总价的方法是将 quantityitem_price 相乘,并对每个订单的所有项进行求和。最终的 SQL 查询如下:

sql">SELECT c.cust_name, o.order_num, SUM(quantity * item_price) AS OrderTotal
FROM Customers c
JOIN Orders o ON c.cust_id = o.cust_id
JOIN OrderItems oi ON o.order_num = oi.order_num
GROUP BY c.cust_name, o.order_num
ORDER BY c.cust_name, o.order_num;

查询解析

  • 连接三张表:通过 cust_idCustomersOrders 表连接,通过 order_numOrdersOrderItems 表连接,获取顾客、订单和订单项的完整信息。
  • 计算订单总价:使用 SUM(quantity * item_price) 计算每个订单的总金额。
  • 分组与排序GROUP BY c.cust_name, o.order_num 按顾客名称和订单号分组,使每个分组对应一个顾客的一个订单,从而计算出每个订单的总金额。ORDER BY c.cust_name, o.order_num 对结果按顾客名称和订单号排序。

查询结果

查询将返回顾客的姓名、订单号以及该订单的总价,并按顾客名称和订单号进行排序:

cust_nameorder_numOrderTotal
andya110000
bena22000
tonya3150
toma41250
ana5375
hexa749

错误示例及解析

一个常见的错误查询示例如下:

sql">SELECT c.cust_name, o.order_num, SUM(quantity * item_price) AS OrderTotal
FROM Customers c, Orders o, OrderItems oi
WHERE c.cust_id = o.cust_id AND o.order_num = oi.order_num
GROUP BY c.cust_name
ORDER BY c.cust_name, o.order_num;

错误原因

  • 这个查询中只在 GROUP BY 子句中使用了 cust_name,但 order_num 未包含在 GROUP BY 中,导致 SQL 无法确定如何处理 order_num
  • 在 SQL 中,GROUP BY 子句中的列需要包含所有非聚合列(即未被聚合函数包裹的列)。否则,SQL 将无法知道如何处理这些非聚合列的值,从而导致错误。

1. 什么是聚合列和非聚合列?

  • 聚合列:指的是在 SELECT 语句中通过聚合函数(如 SUM()AVG()MAX()MIN()COUNT() 等)计算出的列。它们通常是针对分组数据进行的汇总统计,例如求某个分组中的所有值的和、平均值等。

  • 非聚合列:在 SELECT 语句中未使用聚合函数的列,即直接显示的列,没有进行任何聚合操作。这些列通常用于显示某个分组的特定属性。

2. 为什么非聚合列必须出现在 GROUP BY 子句中,或者是聚合函数的结果?

在分组查询中,SQL 标准要求所有出现在 SELECT 语句中的列要么是聚合函数的结果,要么出现在 GROUP BY 子句中。其原因如下:

  • 确定性原则:在分组查询中,每个分组内可能包含多行数据,如果我们在 SELECT 中选择了一个非聚合列,但没有将其包含在 GROUP BY 子句中,SQL 不知道应该选取哪个具体的值,因为每个分组中的该列值可能不同,这会导致不确定性。

  • 保证数据一致性:当一个查询返回结果时,用户会希望每一行数据都是确定的、清晰的。如果没有将非聚合列放入 GROUP BY,则会出现难以理解的数据,可能导致数据含义不清或误导。

举例说明

假设有一个 sales 表,结构如下:

idregionsales_amount
1East200
2East150
3West300
4West250

我们希望按 region 进行分组,求每个地区的总销售额。正确的 SQL 查询应该是:

sql">SELECT region, SUM(sales_amount) AS total_sales
FROM sales
GROUP BY region;

输出结果

regiontotal_sales
East350
West550

在这个例子中,SUM(sales_amount) 是一个聚合列,而 region 是一个非聚合列。因为 region 出现在 GROUP BY 子句中,SQL 知道需要按 region 分组,然后对每组的数据进行汇总。

错误的写法

如果我们写成如下形式,将 id(非聚合列)放在 SELECT 语句中,却不出现在 GROUP BY 中:

sql">SELECT id, region, SUM(sales_amount) AS total_sales
FROM sales
GROUP BY region;

这样 SQL 会报错,因为每个 region 可能包含多行不同的 id,系统无法确定返回哪个 id。这就是 SQL 标准所要避免的“不确定性”问题。

如何解决?

如果确实需要某个非聚合列出现在查询结果中,那么我们有两种选择:

  1. 将非聚合列加入 GROUP BY 子句中:如果 id 是我们希望按某个粒度分组的属性,可以将其加入 GROUP BY

    sql">SELECT id, region, SUM(sales_amount) AS total_sales
    FROM sales
    GROUP BY id, region;
    
  2. 使用聚合函数处理该列:如果想得到某个分组内的特定 id,可以使用聚合函数,如 MAX(id)MIN(id),以明确返回的值。

    sql">SELECT MAX(id) AS example_id, region, SUM(sales_amount) AS total_sales
    FROM sales
    GROUP BY region;
    

总结

在 SQL 中,当我们进行分组查询时,所有出现在 SELECT 语句中的非聚合列都必须出现在 GROUP BY 子句中,或者使用聚合函数包裹。这样做的原因是,GROUP BY 子句能确保查询结果是确定的、清晰的。否则,SQL 将无法理解如何处理这些非聚合列,可能导致错误。


http://www.ppmy.cn/ops/133763.html

相关文章

【毫米波雷达(九)】前雷达软件开发遇到的问题汇总及解决方法

前雷达软件开发遇到的问题汇总及解决方法 一、CAN/CANFD通信1、雷达CAN未能正常发出数据2、雷达在车上接收不到车身信息3、程序下载失败4、DV试验发送数据偶发断连5、发送感知信息丢帧或者丢报文6、上电发出第一帧的报文时间长7、ZCANPRO有错误帧二、协议转换(以太网…

【数据结构与算法】第12课—数据结构之归并排序

文章目录 1. 归并排序2. 计数排序3. 排序算法复杂度及稳定性分析在这里插入图片描述 1. 归并排序 分治法(Divide and Conquer)是一种重要的算法设计策略,其核心思想是将一个复杂的大问题分解为若干个小规模的子问题,递归地解决这些…

大华Android面试题及参考答案

请解释 Service 和 IntentService 之间的区别。 Service 是 Android 中的一种组件,用于在后台执行长时间运行的操作,不提供用户界面。它可以通过 startService () 或者 bindService () 方法来启动。当通过 startService () 启动时,服务会一直运行直到自己停止或者被系统回收…

SpringBoot(二十)SpringBoot集成druid

一:数据库连接池是什么呢? 数据库连接池是程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态的对池中连接进行申请、使用、释放。 数据库连接是一件费事的操作,连接池可以使得多个操作共享一个连接,数据库连接池就是为数据库建立一个缓冲区。 当需…

利用VMware workstation pro 17安装 Centos7虚拟机以及修改网卡名称

通过百度网盘分享的文件:安装虚拟机必备软件 链接:https://pan.baidu.com/s/1rbYhDh8x1hTzlSNihm49EA?pwdomxy 提取码:omxy 123网盘 https://www.123865.com/s/eXPrVv-UsKch 提取码:eNcy 先自行安装好VMware workstation pro 17 设置虚拟机…

vue2和vue3的区别详解

vue2 VS vue3 对比vue2vue3配置脚手架cmd命令行可视化方式创建脚⼿架组件通信props、$emit、provide、$arrts、EventBus等props、$emit、provide、inject、arrts等数据监听watch,computedwatch,watchEffect,computed双向绑定Object.definePropertyProxyAPI⽣命周期四个阶段befo…

机器学习小补充(加深理解)

1. 分类交叉熵损失(Categorical Crossentropy) 定义:当标签以独热编码形式表示时使用。 原理:在多分类问题中,分类交叉熵损失用于计算模型预测的概率分布与实际分布之间的差异。模型输出的预测概率通常是一个向量&am…

泷羽sec学习打卡-Windows基础virus

声明 学习视频来自B站UP主 泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 关于windows virus的那些事儿 一、Windows-Virus资源耗尽之无限弹窗cmd-virus测试锁机virus测试无限重启…