当前位置: 首页 > news >正文

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

在 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 查询如下:

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

错误示例及解析

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

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 查询应该是:

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 中:

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

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

    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.lryc.cn/news/484829.html

相关文章:

  • Unity中实现战斗帧同步的高级技术
  • Qt 正则表达式提取文件中的 USB 设备 ID
  • 使用 Python 和 OpenCV 实现摄像头人脸检测并截图
  • 【二叉搜素树】——LeetCode二叉树问题集锦:6个实用题目和解题思路
  • 【计算机视觉】FusionGAN
  • 问:SQL优化,七条实践总结?
  • unity单例模式的不同声明(待完善
  • 大模型在蓝鲸运维体系应用——蓝鲸运维开发智能助手
  • vue2,vue3响应式的理解
  • 群控系统服务端开发模式-应用开发-前端退出功能
  • Web入门
  • 基于SpringBoot网上超市的设计与实现录像
  • python爬虫(二)爬取国家博物馆的信息
  • 【mysql的当前读和快照读】
  • [CKS] Audit Log Policy
  • 【Linux】-学习笔记03
  • Leetcode热题100-32 最长有效括号
  • 【大数据学习 | HBASE】hbase的读数据流程与hbase读取数据
  • A027-基于Spring Boot的农事管理系统
  • Redisson的可重入锁
  • SQL Server Service Broker完整示例
  • CentOS7 升级OpenSSH9.0全过程和坑
  • RSTP的配置
  • 力扣257:二叉树的所有路径
  • Tcl 和 Python 在二次开发研究
  • 【NLP优化】Ubuntu 20.04 下 源码安装 CasADi + Ipopt / acados
  • [241110] 微软发布多智能体系统Magentic-One | 社区讨论:Ubuntu 26.04 LTS 发布前移除 Qt 5
  • AI风向标|算力与通信的完美融合,SRM6690解锁端侧AI的智能密码
  • MySQL查询执行(六):join查询
  • python习题练习