SQL语句where,Group By,having order by 的详细使用方法

本文深入讲解SQL中的GroupBy语句,包括基本概念、常见用法、高级特性如GroupBy All、WITH CUBE和WITH ROLLUP,以及GroupBy与其他SQL语句的配合使用。
1. Group By 语句简介:

Group By语句从英文的字面意义上理解就是“根据(by)一定的规则进行分组(Group)”。它的作用是通过一定的规则将一个数据集划分成若干个小的区域,然后针对若干个小区域进行数据处理。

P.S. 这里真是体会到了一个好的命名的力量,Group By从字面是直接去理解是非常好理解的。恩,以后在命名的环节一定要加把劲:)。话题扯远了。

2. Group By 的使用:

上面已经给出了对Group By语句的理解。基于这个理解和SQL Server 2000的联机帮助,下面对Group By语句的各种典型使用进行依次列举说明。

2.1 Group By [Expressions]:

这个恐怕是Group By语句最常见的用法了,Group By + [分组字段](可以有多个)。在执行了这个操作以后,数据集将根据分组字段的值将一个数据集划分成各个不同的小组。比如有如下数据集,其中水果名称(FruitName)和出产国家(ProductPlace)为联合主键:

FruitNameProductPlacePrice
AppleChina$1.1
AppleJapan$2.1
AppleUSA$2.5
OrangeChina$0.8
BananaChina$3.1
PeachUSA$3.0

如果我们想知道每个国家有多少种水果,那么我们可以通过如下SQL语句来完成:

SELECT     COUNT(*) AS 水果种类, ProductPlace AS 出产国
FROM         T_TEST_FRUITINFO
GROUP BY ProductPlace

这个SQL语句就是使用了Group By + 分组字段的方式,那么这句SQL语句就可以解释成“我按照出产国家(ProductPlace)将数据集进行分组,然后分别按照各个组来统计各自的记录数量。”很好理解对吧。这里值得注意的是结果集中有两个返回字段,一个是ProductPlace(出产国), 一个是水果种类。如果我们这里水果种类不是用Count(*),而是类似如下写法的话:

SELECT FruitName, ProductPlace FROM T_TEST_FRUITINFO GROUP BY ProductPlace

那么SQL在执行此语句的时候会报如下的类似错误:

选择列表中的列 ’T_TEST_FRUITINFO.FruitName’ 无效,因为该列没有包含在聚合函数或 GROUP BY 子句中。

这就是我们需要注意的一点,如果在返回集字段中,这些字段要么就要包含在Group By语句的后面,作为分组的依据;要么就要被包含在聚合函数中。我们可以将Group By操作想象成如下的一个过程,首先系统根据SELECT 语句得到一个结果集,如最开始的那个水果、出产国家、单价的一个详细表。然后根据分组字段,将具有相同分组字段的记录归并成了一条记录。这个时候剩下的那些不存在于Group By语句后面作为分组依据的字段就有可能出现多个值,但是目前一种分组情况只有一条记录,一个数据格是无法放入多个数值的,所以这里就需要通过一定的处理将这些多值的列转化成单值,然后将其放在对应的数据格中,那么完成这个步骤的就是聚合函数。这就是为什么这些函数叫聚合函数(aggregate functions)了。

2.2 Group By All [expressions] :

Group By All + 分组字段, 这个和前面提到的Group By [Expressions]的形式多了一个关键字ALL。这个关键字只有在使用了where语句的,且where条件筛选掉了一些组的情况才可以看出效果。在SQL Server 2000的联机帮助中,对于Group By All是这样进行描述的:

如果使用 ALL 关键字,那么查询结果将包括由 GROUP BY 子句产生的所有组,即使某些组没有符合搜索条件的行。没有 ALL 关键字,包含 GROUP BY 子句的 SELECT 语句将不显示没有符合条件的行的组。

其中有这么一句话“如果使用ALL关键字,那么查询结果将包含由Group By子句产生的所有组…没有ALL关键字,那么不显示不符合条件的行组。”这句话听起来好像挺耳熟的,对了,好像和LEFT JOIN 和 RIGHT JOIN 有点像。其实这里是类比LEFT JOIN来进行理解的。还是基于如下这样一个数据集:

FruitNameProductPlacePrice
AppleChina$1.1
AppleJapan$2.1
AppleUSA$2.5
OrangeChina$0.8
BananaChina$3.1
PeachUSA$3.0

首先我们不使用带ALL关键字的Group By语句:

SELECT     COUNT(*) AS 水果种类, ProductPlace AS 出产国
FROM         T_TEST_FRUITINFO
WHERE     (ProductPlace <> ’Japan’)
GROUP BY ProductPlace

那么在最后结果中由于Japan不符合where语句,所以分组结果中将不会出现Japan。

现在我们加入ALL关键字:

SELECT     COUNT(*) AS 水果种类, ProductPlace AS 出产国
FROM         T_TEST_FRUITINFO
WHERE     (ProductPlace <> ’Japan’)
GROUP BY ALL ProductPlace

重新运行后,我们可以看到Japan的分组,但是对应的“水果种类”不会进行真正的统计,聚合函数会根据返回值的类型用默认值0或者NULL来代替聚合函数的返回值。

2.3 GROUP BY [Expressions] WITH CUBE | ROLLUP:

首先需要说明的是Group By All 语句是不能和CUBE 和 ROLLUP 关键字一起使用的。

首先先说说CUBE关键字,以下是SQL Server 2000联机帮助中的说明:

指定在结果集内不仅包含由 GROUP BY 提供的正常行,还包含汇总行。在结果集内返回每个可能的组和子组组合的 GROUP BY 汇总行。GROUP BY 汇总行在结果中显示为 NULL,但可用来表示所有值。使用 GROUPING 函数确定结果集内的空值是否是 GROUP BY 汇总值。

结果集内的汇总行数取决于 GROUP BY 子句内包含的列数。GROUP BY 子句中的每个操作数(列)绑定在分组 NULL 下,并且分组适用于所有其它操作数(列)。由于 CUBE 返回每个可能的组和子组组合,因此不论指定分组列时所使用的是什么顺序,行数都相同。

我们通常的Group By语句是按照其后所跟的所有字段进行分组,而如果加入了CUBE关键字以后,那么系统将根据所有字段进行分组的基础上,还会通过对所有这些分组字段所有可能存在的组合形成的分组条件进行分组计算。由于上面举的例子过于简单,这里就再适合了,现在我们的数据集将换一个场景,一个表中包含人员的基本信息:员工所在的部门编号(C_EMPLINFO_DEPTID)、员工性别(C_EMPLINFO_SEX)、员工姓名(C_EMPLINFO_NAME)等。那么我现在想知道每个部门各个性别的人数,那么我们可以通过如下语句得到:

SELECT C_EMPLINFO_DEPTID, C_EMPLINFO_SEX, COUNT(*) AS C_EMPLINFO_TOTALSTAFFNUM
FROM T_PERSONNEL_EMPLINFO
GROUP BY C_EMPLINFO_DEPTID, C_EMPLINFO_SEX

但是如果我现在希望知道:

1. 所有部门有多少人(这里相当于就不进行分组了,因为这里已经对员工的部门和性别没有做任何限制了,但是这的确也是一种分组条件的组合方式);

2. 每种性别有多人(这里实际上是仅仅根据性别(C_EMPLINFO_SEX)进行分组);

3. 每个部门有多少人(这里仅仅是根据部门(C_EMPLINFO_DEPTID)进行分组);那么我们就可以使用ROLLUP语句了。

SELECT C_EMPLINFO_DEPTID, C_EMPLINFO_SEX, COUNT(*) AS C_EMPLINFO_TOTALSTAFFNUM
FROM T_PERSONNEL_EMPLINFO
GROUP BY C_EMPLINFO_DEPTID, C_EMPLINFO_SEX WITH CUBE

那么这里你可以看到结果集中多出了很多行,而且结果集中的某一个字段或者多个字段、甚至全部的字段都为NULL,请仔细看一下你就会发现实际上这些记录就是完成了上面我所列举的所有统计数据的展现。使用过SQL Server 2005或者RDLC的朋友们一定对于矩阵的小计和分组功能有印象吧,是不是都可以通过这个得到答案。我想RDLC中对于分组和小计的计算就是通过Group By的CUBE和ROLLUP关键字来实现的。(个人意见,未证实)

CUBE关键字还有一个极为相似的兄弟ROLLUP, 同样我们先从这英文入手,ROLL UP是“向上卷”的意思,如果说CUBE的组合是绝对自由的,那么ROLLUP的组合就需要有点约束了。我们先来看看SQL Server 2000的联机中对ROLLUP关键字的定义:

指定在结果集内不仅包含由 GROUP BY 提供的正常行,还包含汇总行。按层次结构顺序,从组内的最低级别到最高级别汇总组。组的层次结构取决于指定分组列时所使用的顺序。更改分组列的顺序会影响在结果集内生成的行数。

那么这个顺序是什么呢?对了就是Group By 后面字段的顺序,排在靠近Group By的分组字段的级别高,然后是依次递减。如:Group By Column1, Column2, Column3。那么分组级别从高到低的顺序是:Column1 > Column2 > Column3。还是看我们前面的例子,SQL语句中我们仅仅将CUBE关键字替换成ROLLUP关键字,如:

SELECT C_EMPLINFO_DEPTID, C_EMPLINFO_SEX, COUNT(*) AS C_EMPLINFO_TOTALSTAFFNUM
FROM T_PERSONNEL_EMPLINFO
GROUP BY C_EMPLINFO_DEPTID, C_EMPLINFO_SEX WITH ROLLUP

和CUBE相比,返回的数据行数减少了不少。:),仔细看一下,除了正常的Group By语句后,数据中还包含了:

1. 部门员工数;(向上卷了一次,这次先去掉了员工性别的分组限制)

2. 所有部门员工数;(向上又卷了依次,这次去掉了员工所在部门的分组限制)。

在现实的应用中,对于报表的一些统计功能是很有帮助的。

这里还有一个问题需要补充说明一下,如果我们使用ROLLUP或者CUBE关键字,那么将产生一些小计的行,这些行中被剔除在分组因素之外的字段将会被设置为NULL,那么还存在一种情况,比如在作为分组依据的列表中存在可空的行,那么NULL也会被作为一个分组表示出来,所以这里我们就不能仅仅通过NULL来判断是不是小计记录了。下面的例子展示了这里说得到的情况。还是我们前面提到的水果例子,现在我们在每种商品后面增加一个“折扣列”(Discount),用于显示对应商品的折扣,这个数值是可空的,也就是可以通过NULL来表示没有对应的折扣信息。数据集如下所示:

FruitNameProductPlacePriceDiscount
AppleChina$1.10.8
AppleJapan$2.10.9
AppleUSA$2.51.0
OrangeChina$0.8NULL
BananaChina$3.1NULL
PeachUSA$3.0NULL

现在我们要统计“各种折扣对应有多少种商品,并总计商品的总数。”,那么我们可以通过如下的SQL语句来完成:

SELECT     COUNT(*) AS ProductCount, Discount
FROM         T_TEST_FRUITINFO
GROUP BY Discount WITH ROLLUP

好了,运行一下,你会发现数据都正常出来了,按照如上的数据集,结果如下所示:

ProductCountDiscount
3NULL
10.8
10.9
11.0
6NULL

好了,各种折扣的商品数量都出来了,但是在显示“没有折扣商品”和“商品小计”的时候判断上确存在问题,因为存在两条Discount为Null的记录。是哪一条呢?通过分析数据我们知道第一条数据(3, Null)应该对应没有折扣商品的数量,而(6,Null)应该对应所有商品的数量。需要判断这两个具有不同意义的Null就需要引入一个聚合函数Grouping。现在我们把语句修改一下,在返回值中使用Grouping函数增加一列返回值,SQL语句如下:

SELECT     COUNT(*) AS ProductCount, Discount, GROUPING(Discount) AS Expr1
FROM         T_TEST_FRUITINFO
GROUP BY Discount WITH ROLLUP

这个时候,我们再看看运行的结果:

ProductCountDiscountExpr1
3NULL0
10.80
10.90
11.00
6NULL1

对于根据指定字段Grouping中包含的字段进行小计的记录,这里会标记为1,我们就可以通过这个标记值将小计记录从判断那些由于ROLLUP或者CUBE关键字产生的行。Grouping(column_name)可以带一个参数,Grouping就会去判断对应的字段值的NULL是否是由ROLLUP或者CUBE产生的特殊NULL值,如果是那么就在由Grouping聚合函数产生的新列中将值设置为1。注意Grouping只会检查Column_name对应的NULL来决定是否将值设置为1,而不是完全由此列是否是由ROLLUP或者CUBE关键字自动添加来决定的。

2.2  Group By 和 Having, Where ,Order by语句的执行顺序:

最后要说明一下的Group By, Having, Where, Order by几个语句的执行顺序。一个SQL语句往往会产生多个临时视图,那么这些关键字的执行顺序就非常重要了,因为你必须了解这个关键字是在对应视图形成前的字段进行操作还是对形成的临时视图进行操作,这个问题在使用了别名的视图尤其重要。以上列举的关键字是按照如下顺序进行执行的:Where, Group By, Having, Order by。首先where将最原始记录中不满足条件的记录删除(所以应该在where语句中尽量的将不符合条件的记录筛选掉,这样可以减少分组的次数),然后通过Group By关键字后面指定的分组条件将筛选得到的视图进行分组,接着系统根据Having关键字后面指定的筛选条件,将分组视图后不满足条件的记录筛选掉,然后按照Order By语句对视图进行排序,这样最终的结果就产生了。在这四个关键字中,只有在Order By语句中才可以使用最终视图的列名,如:

SELECT     FruitName, ProductPlace, Price, ID AS IDE, Discount
FROM         T_TEST_FRUITINFO
WHERE     (ProductPlace = N’china’)
ORDER BY IDE

这里只有在ORDER BY语句中才可以使用IDE,其他条件语句中如果需要引用列名则只能使用ID,而不能使用IDE。

<think>嗯,用户现在想了解SQL查询中wheregroupbyhavingorderby的排序机制。之前他们已经问过关于MySQL覆盖索引回表查询的问题,现在转向SQL语句的执行顺序各个子句的作用。根据用户提供的引用资料,他们可能已经看过一些关于执行顺序的内容,但需要更清晰的解释,可能是在实际应用中遇到了性能问题或者结果不符合预期的情况。 首先,我需要确认用户是否清楚这些子句的基本用法,以及执行顺序如何影响查询结果。比如,wherehaving的区别,group by如何影响聚合函数,order by在何时起作用。用户可能希望有一个结构化的解释,结合实例来展示每个步骤的影响。 用户提到的引用资料指出,执行顺序是from > where > group by > having > select > order by,而书写顺序不同。需要解释为什么执行顺序是这样的,以及这对结果有什么影响。例如,wheregroup by之前执行,所以它过滤的是原始数据,而having过滤的是分组后的数据。这可能是一个常见的混淆点,用户可能在这里容易犯错。 另外,用户可能想知道在使用group by时,select中的字段有什么限制,为什么有时候会报错。引用中提到,select的列必须是group by字段或聚合函数,这需要详细说明,可能举一个例子说明正确错误的写法。 还有,关于order by的位置,用户可能困惑如果在group by前后使用会有什么不同。引用中提到如果有order bygroup by之前,排序后的数据会被分组打乱,而如果在之后,则分组后的结果会被排序。这可能需要用实例来演示。 用户可能还关心性能优化,比如如何合理安排wherehaving的条件,减少数据处理量,提高查询效率。比如,尽早用where过滤数据,减少group by处理的行数,再用having进一步筛选分组后的结果。 需要检查是否有遗漏的点,比如聚合函数的使用位置,或者多个字段排序时的优先级。同时,用户可能希望知道这些子句在复杂查询中的组合使用,比如同时有wheregroup byhavingorder by的情况,执行顺序如何影响最终结果。 最后,总结部分需要简明扼要,强调关键点,帮助用户记忆。可能还需要给出一些最佳实践,比如尽量在where中过滤数据,避免在having中处理大量数据,合理使用索引来优化whereorder by的性能等。</think>### SQL查询中WHEREGROUP BYHAVINGORDER BY的排序机制解析 --- #### 一、核心执行顺序与逻辑 SQL查询的执行顺序与书写顺序不同,其核心流程如下: **实际执行顺序**: `FROM` → `WHERE` → `GROUP BY` → `HAVING` → `SELECT` → `ORDER BY` → `LIMIT` **书写顺序**: `SELECT` → `FROM` → `WHERE` → `GROUP BY` → `HAVING` → `ORDER BY` → `LIMIT` **关键区别**: - `WHERE`在`GROUP BY`之前执行,用于过滤原始数据 - `HAVING`在`GROUP BY`之后执行,用于过滤分组结果 - `ORDER BY`在`SELECT`之后执行,对最终结果排序[^4][^5] --- #### 二、各子句作用与限制 ##### 1. **WHERE子句** - **执行阶段**:数据分组前 - **功能**:过滤原始表中的行(**行级过滤**) - **限制**: - 不能使用聚合函数(如`SUM()`、`COUNT()`) - 不能引用`SELECT`中定义的别名 **示例**: ```sql SELECT department, AVG(salary) FROM employees WHERE hire_date > '2020-01-01' -- 先过滤入职时间 GROUP BY department; ``` ##### 2. **GROUP BY子句** - **执行阶段**:在`WHERE`之后,`HAVING`之前 - **功能**:按指定列分组并计算聚合值 - **强制规则**: - `SELECT`中的非聚合字段必须出现在`GROUP BY`中 - 分组顺序影响结果集结构 **示例**: ```sql SELECT department, COUNT(*) AS emp_count FROM employees GROUP BY department; -- 正确:SELECT字段GROUP BY一致 ``` ##### 3. **HAVING子句** - **执行阶段**:数据分组后 - **功能**:过滤分组结果(**组级过滤**) - **特点**: - 可使用聚合函数(如`HAVING AVG(salary) > 5000`) - 能引用`SELECT`中的别名 **示例**: ```sql SELECT department, AVG(salary) AS avg_salary FROM employees GROUP BY department HAVING avg_salary > 8000; -- 过滤分组后的平均值 ``` ##### 4. **ORDER BY子句** - **执行阶段**:所有数据处理完成后 - **功能**:对最终结果排序 - **特性**: - 可使用`SELECT`中的别名 - 支持多列排序(`ORDER BY col1 DESC, col2 ASC`) - 默认升序(`ASC`),降序需显式声明(`DESC`)[^5] **示例**: ```sql SELECT department, COUNT(*) AS emp_count FROM employees GROUP BY department ORDER BY emp_count DESC; -- 按统计结果降序排列 ``` --- #### 三、典型场景与执行流程演示 **场景**:统计各部门2020年后入职员工的平均工资,筛选出平均工资>6000的部门,按平均工资降序排列 ```sql SELECT department, AVG(salary) AS avg_salary FROM employees WHERE hire_date > '2020-01-01' -- 步骤1:过滤原始数据 GROUP BY department -- 步骤2:分组计算 HAVING avg_salary > 6000 -- 步骤3:过滤分组结果 ORDER BY avg_salary DESC; -- 步骤4:最终排序 ``` **执行流程分解**: 1. `FROM employees`:加载员工表 2. `WHERE hire_date > '2020-01-01'`:筛选符合条件的员工 3. `GROUP BY department`:按部门分组,计算各组的平均工资 4. `HAVING avg_salary > 6000`:淘汰平均工资≤6000的部门 5. `SELECT`:输出`department``avg_salary` 6. `ORDER BY avg_salary DESC`:按平均工资降序排列 --- #### 四、常见错误与避坑指南 1. **WHERE使用聚合函数** ```sql -- 错误示例 SELECT department FROM employees WHERE AVG(salary) > 5000; -- WHERE不能包含聚合函数 ``` **修复方案**:改用`HAVING` 2. **GROUP BY遗漏字段** ```sql -- 错误示例 SELECT department, employee_name FROM employees GROUP BY department; -- employee_name未在GROUP BY中 ``` **修复方案**:将`employee_name`加入`GROUP BY`或使用聚合函数 3. **ORDER BYGROUP BY顺序混淆** ```sql -- 可能产生非预期结果 SELECT department, COUNT(*) FROM employees ORDER BY hire_date -- 先排序后分组,结果可能被覆盖 GROUP BY department; ``` **正确写法**:将`ORDER BY`放在`GROUP BY`之后 --- #### 五、性能优化建议 1. **优先使用WHERE过滤** - 在分组前尽可能减少数据量(`WHERE`比`HAVING`效率更高) - 示例:先过滤无效订单再统计销售额 2. **索引优化策略** | **查询条件** | **推荐索引** | |-------------------|-----------------------| | WHERE + GROUP BY | 联合索引(WHERE字段, GROUP BY字段) | | ORDER BY + GROUP BY | 联合索引(GROUP BY字段, ORDER BY字段) | 3. **避免过度分组** - 当分组字段过多时,考虑使用`DISTINCT`代替`GROUP BY` - 测试对比:`SELECT DISTINCT col1, col2` vs `GROUP BY col1, col2` --- ### 总结 1. **执行顺序核心逻辑**:`FROM`→过滤(`WHERE`)→分组(`GROUP BY`)→二次过滤(`HAVING`)→选择字段→排序 2. **WHEREHAVING区别**:前者过滤原始数据,后者过滤分组结果 3. **ORDER BY的最终性**:始终作用于最终结果集,可使用`SELECT`别名[^4][^5] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值