MySql 学习笔记 -- IFNULL() + GROUP BY + HAVING

博客介绍了IFNULL和GROUP BY函数。IFNULL接受两个参数,若exp1非NULL则返回它,否则返回exp2计算值。GROUP BY为每个分组返回一行,减少结果集行数,返回值唯一,常配合聚合函数,还可用HAVING过滤分组,默认升序。

IFNULL(exp1, exp2)

该函数接受两个参数,如果exp1不是NULL,返回第一个参数,否则返回exp2计算值

 

GROUP BY为每个分组返回一行,减少了结果集中的行数

GROUP BY子句返回值唯一,和DISTINCT一样

通常配合聚合函数使用(SUM,MIN,MAX, COUNT, AVG)

请注意,SELECT子句中出现的表达式必须与GROUP BY子句中的相同。

还可以使用HAVING过滤GROUP BY子句返回的分组

GROUP允许升序/降序的定义,默认是升序

 

HAVING通常配合GROUP BY一起使用,否则其行为与WHERE类似

<think>我们有一个表MyNumbers,其中有一个数字列(假设列名为num)。要求: 1. 找出只出现一次的数字(即num值在表中出现次数为1) 2. 从这些数字中取最大值 3. 如果没有只出现一次的数字,则返回null 我们可以分步骤实现: 步骤1:先统计每个数字出现的次数(使用GROUP BY) 步骤2:过滤出出现次数为1的数字(HAVING COUNT(num)=1) 步骤3:从这些数字中选择最大值(使用MAX函数) 步骤4:处理没有单一数字的情况(如果没有,MAX会返回NULL,所以不需要特别处理,因为当子查询为空时,MAX返回NULL) 我们可以用一个查询完成: 方法1:使用子查询,先找出所有只出现一次的数字,然后取最大值。 方法2:直接在同一个查询中,将出现次数为1的数字进行聚合取最大值。 注意:如果使用GROUP BY,我们需要先过滤再聚合,因为我们要的是过滤后(只出现一次)的集合中的最大值。 因此,我们可以这样写: 方法1: SELECT MAX(num) AS num FROM ( SELECT num FROM MyNumbers GROUP BY num HAVING COUNT(num) = 1 ) AS T; 方法1使用子查询先得到所有只出现一次的数字,然后在外层查询中取最大值。如果没有只出现一次的数字,那么子查询返回空,外层查询的MAX会返回NULL。 方法2:也可以不用子查询,直接在HAVING之后使用聚合函数,但是这里我们既要分组又要整体取最大值,所以不能直接在一个GROUP BY中完成。因为我们想要的是所有单一数字中的最大值,而单一数字可能不止一个,所以先通过分组过滤得到单一数字的集合,再取最大值。 因此,方法1是可行的。 另外,我们也可以考虑使用窗口函数,但是这里我们只需要最后的最大值,所以使用子查询即可。 注意:在子查询中,我们使用了GROUP BY num,然后HAVING COUNT(num)=1,这样我们就得到了所有只出现一次的数字。然后外层查询取最大值。 但是,如果表中有NULL值怎么办?题目没有说明,但通常我们不考虑NULL(因为COUNT(num)不会计入NULL,而且GROUP BY也不会把NULL分到一组)。但题目要求的是数字,所以假设num列都是数字。如果num允许为NULL,那么COUNT(num)在num为NULL时返回0,但我们这里只出现一次的数字要求是出现一次,所以NULL不会出现(因为NULL在GROUP BY中会被单独分组,但是COUNT(num)对NULL会返回0,所以不会满足COUNT=1的条件?不对,COUNT(列名)会忽略NULL值,所以如果有一行是NULL,那么COUNT(num)返回0。所以NULL不会被包括在内,符合要求。 因此,这个查询可以满足要求。 我们还可以用另一种写法:使用WITH语句(CTE)更清晰。 但题目没有指定数据库,所以我们用通用的SQL。 另外,我们也可以不用子查询,而是将聚合函数嵌套在另一个聚合函数中,但是这样不行,因为HAVING已经过滤了分组。所以这里必须用子查询。 所以,我们选择方法1。 但是,有一种情况要注意:如果整个表为空,那么子查询也为空,MAX会返回NULL,符合要求。 示例: 假设表MyNumbers如下: +-----+ | num | +-----+ | 8 | | 8 | | 3 | | 3 | | 1 | | 4 | | 5 | | 6 | +-----+ 只出现一次的数字有1,4,5,6,然后最大值是6。 如果表中没有只出现一次的数字,比如: +-----+ | num | +-----+ | 8 | | 8 | | 3 | | 3 | +-----+ 则返回NULL。 另外,如果表为空,也应该返回NULL。 因此,我们写出的SQL如下: ```sql SELECT MAX(num) AS num FROM ( SELECT num FROM MyNumbers GROUP BY num HAVING COUNT(*) = 1 ) AS T; ``` 注意:我们也可以使用COUNT(*),因为分组后每一组就是该num的所有行。使用COUNT(*)和COUNT(num)在num不为NULL时结果一样,但如果num为NULL,COUNT(*)会计算行数(如果一行num是NULL,那么COUNT(*)是1,但是COUNT(num)是0)。但是题目要求只出现一次的数字,如果num为NULL,那么它出现一次(一行NULL),但是我们通常不考虑NULL?题目要求的是“数字”,所以如果允许NULL,那么NULL算不算数字?题目没有明确,但按照常识,数字一般不包括NULL。而且,在分组时,多个NULL会被分到同一组吗?在SQL标准中,多个NULL会被分到同一组(尽管NULL与NULL比较是未知,但分组时认为它们是相同的)。但是,在MySQL中,GROUP BY将NULL视为相同的值,所以多个NULL会分到一组。那么,如果有一行NULL,那么COUNT(*)为1,它会出现在子查询中。但题目要求返回最大数字,而NULL不是数字,所以我们应该排除NULL。 因此,为了避免NULL被计入,我们可以修改为在子查询中过滤掉NULL,或者在HAVING条件中排除NULL?但题目没有说,所以按照需求,如果出现NULL,我们希望不要计入。所以我们可以这样: 修改子查询:在WHERE子句中先过滤掉NULL,然后再分组。 所以,更严谨的写法: ```sql SELECT MAX(num) AS num FROM ( SELECT num FROM MyNumbers WHERE num IS NOT NULL -- 过滤掉NULL GROUP BY num HAVING COUNT(*) = 1 ) AS T; ``` 但是,题目没有说明,而且示例表都是数字,所以这个过滤是可选的。根据实际需求。 所以,我们给出两种写法,但第一种写法(不处理NULL)在遇到NULL时可能会将NULL也作为只出现一次的数字(因为COUNT(*)=1),然后外层MAX会返回NULL(因为NULL和任何数字比较都是NULL,而且MAX函数会忽略NULL,但是如果整个集合只有NULL,那么MAX返回NULL,如果集合中有NULL也有数字,那么MAX会返回最大的数字,因为MAX忽略NULL)。注意:MAX函数会忽略NULL值,所以即使子查询中包含了NULL(当num为NULL且只出现一次),外层MAX也不会将其计入,而是返回剩余数字的最大值。但是,如果子查询返回的结果集中有多个值,其中包含NULL和一些数字,那么MAX只会考虑数字。如果子查询返回的都是NULL,那么MAX返回NULL。 然而,我们并不希望将NULL作为数字,所以最好在子查询中排除NULL。 因此,我们采用过滤NULL的写法。 但是,如果题目要求NULL不计入,那么我们这样做。如果题目没有要求,那么也可以不排除,因为当NULL被计入时,外层MAX会忽略NULL,所以不会影响结果(除非整个结果集只有NULL,那么返回NULL,这也符合没有单一数字返回NULL)。所以,实际上可以不排除NULL,因为即使子查询返回了NULL,MAX函数也会忽略NULL而取其他数字的最大值,如果只有NULL,那么MAX(NULL)就是NULL。 所以,我们可以不排除NULL,因为不影响结果。但为了逻辑清晰,排除NULL更好。 这里,我们按照不排除NULL的方式写,因为不影响结果,而且简化语句。 因此,最终SQL: ```sql SELECT MAX(num) AS num FROM ( SELECT num FROM MyNumbers GROUP BY num HAVING COUNT(*) = 1 ) AS T; ``` 测试:如果有一行NULL,且只出现一次,那么子查询会返回一行NULL(因为COUNT(*)=1)。然后外层MAX(num)会忽略NULL,所以如果同时还有其他只出现一次的数字,那么返回其他数字的最大值;如果只有NULL,那么返回NULL(表示没有单一数字)。 这个结果符合要求:因为NULL不被视为数字,但题目要求返回最大数字,所以忽略NULL是合理的。如果没有非NULL的单一数字,就返回NULL。 所以,这个查询满足要求。 另外,如果使用MySQL,我们可以使用IFNULL来处理,但这里不需要,因为MAX在结果集为空时返回NULL。 所以,我们使用上面的查询。 但是,我们也可以不用子查询,使用两次聚合: 另一种写法:使用CASE语句在聚合函数中?但是这样无法同时满足分组和全局聚合。 还有一种写法:直接使用GROUP BY进行分组,然后HAVING COUNT(*)=1,然后从中取最大值,但是这样我们只能得到每个分组的一个值,而不是所有单一数字的最大值。所以不行。 所以,子查询是必要的。 因此,我们最终选择上述查询。 注意:在MySQL中,如果子查询返回空,那么MAX(num)会是NULL,所以符合要求。 下面写出完整的SQL语句: ```sql SELECT MAX(num) AS num FROM ( SELECT num FROM MyNumbers GROUP BY num HAVING COUNT(*) = 1 ) AS T; ``` 如果数据库支持CTE(公共表表达式),我们可以写得更加清晰: ```sql WITH SingleNumbers AS ( SELECT num FROM MyNumbers GROUP BY num HAVING COUNT(*) = 1 ) SELECT MAX(num) AS num FROM SingleNumbers; ``` 两种写法都可以。 由于题目没有指定数据库,我们使用通用的子查询写法。 因此,我们提供第一种写法。 </think>### 解决方案:查询只出现一次的数字并取最大值 根据需求,我们需要在 `MyNumbers` 表中完成以下操作: 1. 找出只出现一次的数字(计数为1) 2. 从这些数字中取最大值 3. 若不存在单一数字则返回 `NULL` #### 完整 SQL 语句 ```sql SELECT MAX(num) AS max_num FROM ( SELECT num FROM MyNumbers GROUP BY num HAVING COUNT(*) = 1 -- 过滤出只出现一次的数字 ) AS single_numbers; -- 子查询结果命名为 single_numbers ``` #### 关键步骤解析 1. **子查询找出单一数字**: ```sql SELECT num FROM MyNumbers GROUP BY num HAVING COUNT(*) = 1 ``` - `GROUP BY num`:按数字分组 - `HAVING COUNT(*) = 1`:筛选出现次数恰好为1的分组 - 子查询返回所有**只出现一次的数字**集合 2. **外层查询取最大值**: ```sql SELECT MAX(num) AS max_num FROM (...) AS single_numbers ``` - `MAX(num)`:从单一数字集合中取最大值 - 自动处理空值情况:当子查询结果为空时(即不存在单一数字),`MAX()` 函数返回 `NULL` #### 示例说明 假设 `MyNumbers` 表数据: | num | |-----| | 5 | | 8 | | 8 | | 3 | | 7 | | 7 | | 4 | 1. **子查询结果**(只出现一次的数字): ``` | num | |-----| | 5 | | 3 | | 4 | ``` 2. **最终结果**:`MAX(5,3,4) = 5` 若无单一数字(如全为重复值)则返回 `NULL` #### 进阶优化(使用 CTE 增强可读性) ```sql WITH single_numbers AS ( SELECT num FROM MyNumbers GROUP BY num HAVING COUNT(*) = 1 ) SELECT MAX(num) AS max_num FROM single_numbers; ``` #### 注意事项 1. **空值处理**:`MAX()` 函数自动处理空集合返回 `NULL` 2. **性能建议**:若表数据量大,建议对 `num` 列建立索引 ```sql CREATE INDEX idx_num ON MyNumbers(num); ``` 3. **特殊值处理**:如果数字列可能包含 `NULL`,可添加过滤条件 ```sql HAVING COUNT(*) = 1 AND num IS NOT NULL ``` 此解决方案在 MySQL、PostgreSQL、SQL Server 等主流数据库均适用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值