总结的知识点主要来源于前段时间在牛客刷SQL题目中遇到的错误
目录
1.WHERE字句不能与高级函数连用
2.去重——distinct
3.不等于某个值
4.查多个范围内的值
5. 升/降序排序
6.占位符
7.统计某类别总数+计算平均值
8.合并查询——UNION (ALL)
9.CASE 函数 (添加新字段)
10.日期相关函数
11.字符串分割
12.取出某属性为最小值的记录
13.CTE与滑动窗口函数
CTE(Common Table Expression,公共表表达式)
滑动窗口函数的基本概念
滑动窗口函数的语法
关键点
常见的滑动窗口函数
示例
滑动窗口函数的优势
14.日期函数与滑动窗口 应用
1.WHERE字句不能与高级函数连用
### 选项A:
```sql
select productid from orders where count(productid) > 1
```
这个查询试图从`orders`表中选择`productid`,其中`productid`的数量大于1。然而,这个查询语句在SQL中是无效的,因为`WHERE`子句不能直接使用聚合函数(如`COUNT`)。聚合函数通常在`HAVING`子句中使用,该子句用于过滤聚合结果。
### 选项D:
```sql
select productid from orders group by productid having count(productid) > 1
```
这个查询是有效的。它首先按`productid`对`orders`表进行分组,然后使用`HAVING`子句来过滤那些`productid`出现次数大于1的组。这意味着它返回的是那些在订单中至少出现两次的产品ID。
总结:
- **选项A**试图执行一个无效的SQL查询,因为它错误地在`WHERE`子句中使用了聚合函数。
- **选项D**正确地使用了`GROUP BY`和`HAVING`子句来选择那些在订单中至少出现两次的产品ID。
### 1. 聚合函数
聚合函数用于从一组值中生成单个值。常见的聚合函数包括:
- `COUNT()`:计算行数。
- `SUM()`:计算数值列的总和。
- `AVG()`:计算数值列的平均值。
- `MAX()`:找出数值列的最大值。
- `MIN()`:找出数值列的最小值。
### 2. GROUP BY 子句
`GROUP BY`子句用于将结果集的行分组,以便可以对每个组执行聚合函数。例如,你可以按产品ID分组,然后计算每个产品的总销售额。
### 3. HAVING 子句
`HAVING`子句用于筛选分组后的结果。与`WHERE`子句不同,`HAVING`可以与聚合函数一起使用。`WHERE`子句用于在分组之前筛选行,而`HAVING`子句用于在分组之后筛选组。
### 4. 使用场景
- **基本聚合**:直接使用聚合函数,如`SELECT COUNT(*) FROM table;`。
- **分组聚合**:使用`GROUP BY`进行分组,然后应用聚合函数,如`SELECT productid, COUNT(*) FROM orders GROUP BY productid;`。
- **筛选聚合结果**:使用`HAVING`子句筛选聚合后的结果,如`SELECT productid FROM orders GROUP BY productid HAVING COUNT(*) > 1;`。
### 5. 注意事项
- `GROUP BY`子句中的列必须出现在选择列表中,或者在聚合函数中使用。
- `HAVING`子句中的条件可以是聚合函数的结果。
- `WHERE`子句不能包含聚合函数,它用于在分组之前筛选行。
### 6. 示例
假设有一个`orders`表,包含`productid`和`quantity`列,你可以使用以下查询来找出每个产品的平均订购数量:
```sql
SELECT productid, AVG(quantity) AS average_quantity
FROM orders
GROUP BY productid;
```
如果你想找出平均订购数量超过10的产品,可以使用`HAVING`子句:
```sql
SELECT productid, AVG(quantity) AS average_quantity
FROM orders
GROUP BY productid
HAVING AVG(quantity) > 10;
```
### 7. 性能优化
- 在使用`GROUP BY`时,确保分组的列上有索引,以提高查询性能。
- 避免在`GROUP BY`子句中使用复杂的表达式或函数,这可能会降低性能。
在SQL查询中,`GROUP BY productid;` 子句的作用是将结果集按照 `productid` 进行分组。这意味着对于每个不同的 `productid`,都会计算一个平均值。让我们详细探讨一下加上和不加 `GROUP BY` 子句对结果的影响:
### 加上 `GROUP BY productid;`
当你使用 `GROUP BY productid;` 子句时,查询会按照 `productid` 对结果进行分组。具体来说,这个查询会:
1. 将 `orders` 表中所有行按照 `productid` 进行分组。
2. 对每个 `productid` 分组,计算 `quantity` 的平均值。
3. 返回每个 `productid` 及其对应的平均 `quantity`。
例如,假设 `orders` 表如下:
| productid | quantity |
|-----------|----------|
| 1 | 10 |
| 1 | 20 |
| 2 | 15 |
| 2 | 25 |
加上 `GROUP BY productid;` 后的查询结果将是:
| productid | average_quantity |
|-----------|------------------|
| 1 | 15 |
| 2 | 20 |
### 不加 `GROUP BY productid;`
如果不使用 `GROUP BY productid;` 子句,查询将不会对结果进行分组。这意味着聚合函数 `AVG(quantity)` 将应用于整个结果集,而不是每个 `productid` 分组。具体来说,这个查询会:
1. 计算整个 `orders` 表中所有 `quantity` 的平均值。
2. 返回一个包含整个表平均 `quantity` 的单一行。
例如,对于上述 `orders` 表,不加 `GROUP BY productid;` 的查询结果将是:
| average_quantity |
|------------------|
| 18.75 |
2.去重——distinct
3.不等于某个值
普通值用“!=” ;不等于空值用 “is not”
4.查多个范围内的值
Where in / not in ( , , )
5. 升/降序排序
Oder by … ASC/DESC
6.占位符
查找包含特定字符串的记录,可以使用 LIKE 操作符,并结合通配符。常用的通配符有:
- %:表示任意长度的任意字符序列(包括空字符序列)。
- _:表示任意单个字符。
7.统计某类别总数+计算平均值
### 原始查询
```sql
SELECT male_num, avg_gpa
FROM user_profile
GROUP BY gender
HAVING AVG(gpa);
```
### 问题分析
1. **`SELECT` 列未定义**:
- `male_num` 和 `avg_gpa` 在查询中没有定义,它们既不是表中的列,也不是聚合函数的结果。
- 你需要明确指定要选择的列,并使用聚合函数计算这些值。
2. **`GROUP BY` 的使用**:
- `GROUP BY` 用于对数据进行分组,但你不能直接在 `SELECT` 中使用未分组的列。
- 如果你想要统计每个性别的总数和平均 GPA,应该使用 `COUNT` 和 `AVG` 函数。
3. **`HAVING` 的使用**:
- `HAVING` 子句通常用于过滤分组后的结果,但它需要一个条件表达式。
- 在你的查询中,`HAVING AVG(gpa)` 是不完整的,因为它没有指定具体的条件。
### 修正后的查询
假设你想要统计男性用户的总数和平均 GPA,可以使用以下查询:
```sql
SELECT
COUNT(*) AS male_num,
AVG(gpa) AS avg_gpa
FROM
user_profile
WHERE
gender = 'M';
```
### 如果需要分组统计
如果你想要统计每个性别的总数和平均 GPA,可以使用以下查询:
```sql
SELECT
gender,
COUNT(*) AS num,
AVG(gpa) AS avg_gpa
FROM
user_profile
GROUP BY
gender;
```
### 如果需要过滤特定性别的分组结果
如果你想要过滤出平均 GPA 大于某个值的性别分组,可以使用 `HAVING` 子句。例如,过滤出平均 GPA 大于 3.5 的性别分组:
```sql
SELECT
gender,
COUNT(*) AS num,
AVG(gpa) AS avg_gpa
FROM
user_profile
GROUP BY
gender
HAVING
AVG(gpa) > 3.5;
```
- SELECT 列的定义:SELECT 子句中的列必须是表中的实际列,或者是通过聚合函数计算得到的值。
- GROUP BY 的使用:GROUP BY 用于分组,SELECT 子句中的列必须是分组列或聚合函数的结果。
- HAVING 子句的使用:HAVING 用于过滤分组后的结果,必须包含一个条件表达式。
- WHERE 子句的使用:WHERE 用于过滤行,HAVING 用于过滤分组后的结果。
分组计算:
8.合并查询——UNION (ALL)
Union all:用于合并两个查询的结果集,并且不会去重。如果两个结果集中有相同的行,它们会被保留。
Union:去重合并
9.CASE 函数 (添加新字段)
SELECT
CASE WHEN age < 25 OR age IS NULL THEN '25岁以下'
WHEN age >= 25 THEN '25岁及以上'
END as age_cut,COUNT(*) as number
FROM user_profile
GROUP BY age_cut
10.日期相关函数
1. 基本日期函数
- YEAR(date)
- 作用:从日期值中提取年份部分。
- 语法:YEAR(date)
- 示例:YEAR('2021-08-15') 返回 2021。
- MONTH(date)
- 作用:从日期值中提取月份部分。
- 语法:MONTH(date)
- 示例:MONTH('2021-08-15') 返回 8。
- DAY(date)
- 作用:从日期值中提取天数部分。
- 语法:DAY(date)
- 示例:DAY('2021-08-15') 返回 15。
- HOUR(time)
- 作用:从时间值中提取小时部分。
- 语法:HOUR(time)
- 示例:HOUR('14:30:00') 返回 14。
- MINUTE(time)
- 作用:从时间值中提取分钟部分。
- 语法:MINUTE(time)
- 示例:MINUTE('14:30:00') 返回 30。
- SECOND(time)
- 作用:从时间值中提取秒数部分。
- 语法:SECOND(time)
- 示例:SECOND('14:30:00') 返回 0。
2. 日期格式化函数
- DATE_FORMAT(date, format)
- 作用:将日期值格式化为指定的字符串格式。
- 语法:DATE_FORMAT(date, format)
- 示例:
- DATE_FORMAT('2021-08-15', '%Y-%m-%d') 返回 '2021-08-15'。
- DATE_FORMAT('2021-08-15', '%Y%m%d') 返回 '20210815'。
3. 日期范围筛选
- BETWEEN start AND end
- 作用:用于筛选某个字段值在指定范围内的记录。
- 语法:field BETWEEN start AND end
- 示例:
- date BETWEEN '2021-08-01' AND '2021-08-31' 用于筛选日期在 2021 年 8 月 1 日到 2021 年 8 月 31 日之间的记录。
4. 日期计算函数
- DATE_ADD(date, INTERVAL expr type)
- 作用:在日期上加上指定的时间间隔。
- 语法:DATE_ADD(date, INTERVAL expr type)
- 示例:
- DATE_ADD('2021-08-15', INTERVAL 10 DAY) 返回 '2021-08-25'。
- DATE_SUB(date, INTERVAL expr type)
- 作用:从日期上减去指定的时间间隔。
- 语法:DATE_SUB(date, INTERVAL expr type)
- 示例:
- DATE_SUB('2021-08-15', INTERVAL 10 DAY) 返回 '2021-08-05'。
- DATEDIFF(date1, date2)
- 作用:计算两个日期之间的天数差。
- 语法:DATEDIFF(date1, date2)
- 示例:
- DATEDIFF('2021-08-25', '2021-08-15') 返回 10。
5. 当前日期和时间函数
- NOW()
- 作用:返回当前的日期和时间。
- 语法:NOW()
- 示例:NOW() 返回当前的日期和时间,例如 '2025-03-24 14:30:00'。
- CURDATE()
- 作用:返回当前的日期。
- 语法:CURDATE()
- 示例:CURDATE() 返回当前的日期,例如 '2025-03-24'。
- CURTIME()
- 作用:返回当前的时间。
- 语法:CURTIME()
- 示例:CURTIME() 返回当前的时间,例如 '14:30:00'。
6. 时间戳函数
- UNIX_TIMESTAMP(date)
- 作用:将日期时间转换为 Unix 时间戳(从 1970-01-01 00:00:00 UTC 开始的秒数)。
- 语法:UNIX_TIMESTAMP(date)
- 示例:UNIX_TIMESTAMP('2021-08-15 14:30:00') 返回对应的 Unix 时间戳。
- FROM_UNIXTIME(timestamp)
- 作用:将 Unix 时间戳转换为日期时间格式。
- 语法:FROM_UNIXTIME(timestamp)
- 示例:FROM_UNIXTIME(1628994600) 返回 '2021-08-15 14:30:00'。
7. 其他日期函数
- LAST_DAY(date)
- 作用:返回指定日期所在月份的最后一天。
- 语法:LAST_DAY(date)
- 示例:LAST_DAY('2021-08-15') 返回 '2021-08-31'。
- DAYOFWEEK(date)
- 作用:返回指定日期是星期几(1 = 星期天,2 = 星期一,...,7 = 星期六)。
- 语法:DAYOFWEEK(date)
- 示例:DAYOFWEEK('2021-08-15') 返回 1(星期天)。
- DAYOFYEAR(date)
- 作用:返回指定日期是一年中的第几天。
- 语法:DAYOFYEAR(date)
- 示例:DAYOFYEAR('2021-08-15') 返回 227。
11.字符串分割
SUBSTRING_INDEX函数:
SUBSTRING_INDEX(str, delim, count):从字符串str中,以delim为分隔符,提取从开始到第count个分隔符之间的内容。
SUBSTRING_INDEX(profile, ',', 3):提取从开始到第3个逗号之前的内容。
SUBSTRING_INDEX(profile, ',', -1):提取从最后一个逗号到结束的内容。
STR_SPLIT函数:
- 在MySQL 8.0及以上版本中,可以使用STR_SPLIT函数来分割字符串。例如:
sqlCopy
SELECT STR_SPLIT(profile, ',', 1) AS height,
STR_SPLIT(profile, ',', 2) AS weight,
STR_SPLIT(profile, ',', 3) AS age,
STR_SPLIT(profile, ',', 4) AS gender
FROM users;
12.取出某属性为最小值的记录
- 子查询部分:
(SELECT university, MIN(gpa) FROM user_profile GROUP BY university)
-
- 这个子查询首先按照university字段对user_profile表进行分组(GROUP BY) university。
- 然后,对于每个分组(即每个大学),计算出该大学中学生的最低gpa值(MIN(gpa))。
- 最终,子查询返回一个包含每个大学及其对应最低gpa值的结果集。
- 主查询部分:
SELECT device_id, university, gpa
FROM user_profile
WHERE (university, gpa) IN (子查询结果)
ORDER BY university
-
- 主查询从user_profile表中选择device_id、university和gpa这三个字段。
- WHERE子句中的条件(university, gpa) IN (...)用于筛选出那些在user_profile表中,其university和gpa的组合与子查询返回的结果集中的某个组合相匹配的记录。也就是说,只选择每个大学中gpa最低的学生记录。
- 最后,ORDER BY university将结果按照university字段进行升序排序。
-----------------------------------------------------------
select emp_no,
birth_date,
first_name,
last_name,
gender,
hire_date
from employees
where hire_date = (select max(hire_date) from employees ) //单一值用“=”,后查询需“()”
举个例子: 假设user_profile表中有以下数据:
device_id | university | gpa |
1 | MIT | 3.0 |
2 | MIT | 2.5 |
3 | Stanford | 3.5 |
4 | Stanford | 3.5 |
子查询会返回:
university | min_gpa |
MIT | 2.5 |
Stanford | 3.5 |
主查询会根据这个结果筛选出:
device_id | university | gpa |
2 | MIT | 2.5 |
3 | Stanford | 3.5 |
4 | Stanford | 3.5 |
这样,我们就得到了每个大学中gpa最低的学生记录,并且按照大学名称排序。
13.CTE与滑动窗口函数
CTE(Common Table Expression,公共表表达式)
是一种临时的结果集,可以在SQL查询中被重复使用。它类似于一个临时表,但它的作用范围仅限于当前查询。CTE可以简化复杂的SQL查询,使查询更易于理解和维护。
CTE的语法结构如下:
WITH CTE_name (column1, column2, ...)
AS (
-- CTE的查询定义
SELECT column1, column2, ...
FROM some_table
WHERE some_condition
)
SELECT column1, column2, ...
FROM CTE_name
WHERE some_condition;
滑动窗口函数的基本概念
滑动窗口函数通过 OVER
子句定义一个窗口(或范围),并在该窗口内对数据进行计算。窗口可以基于行的顺序、分组或特定的范围来定义。常见的滑动窗口函数包括 ROW_NUMBER()
、RANK()
、DENSE_RANK()
、SUM()
、AVG()
、MIN()
、MAX()
等。
滑动窗口函数的语法
滑动窗口函数的语法结构如下:
关键点
PARTITION BY
:- 将数据分成多个分区,窗口函数在每个分区内独立计算。
- 类似于
GROUP BY
,但不会丢失行的细节。
ORDER BY
:- 定义窗口内行的顺序。
ROWS
或RANGE
:- 定义窗口的范围,可以是基于行数的范围(
ROWS
)或基于值的范围(RANGE
)。
- 定义窗口的范围,可以是基于行数的范围(
常见的滑动窗口函数
ROW_NUMBER()
:- 为每一行分配一个唯一的序号,序号在每个分区内从1开始递增。
RANK()
:- 为每一行分配一个排名,排名在每个分区内从1开始递增,相同值的行会分配相同的排名,但会跳过后续的排名。
DENSE_RANK()
:- 与
RANK()
类似,但不会跳过后续的排名。
- 与
SUM()
:- 计算窗口内的总和。
AVG()
:- 计算窗口内的平均值。
MIN()
和MAX()
:- 计算窗口内的最小值和最大值。
示例
假设我们有一个销售记录表 sales
,包含以下列:id
(销售记录ID)、sale_date
(销售日期)、amount
(销售金额)。我们想计算每个销售日期的累计销售额。
滑动窗口函数的优势
- 灵活的窗口定义:可以通过
PARTITION BY
、ORDER BY
和ROWS
/RANGE
定义灵活的窗口。
- 强大的分析能力:可以轻松实现复杂的分析,如累计和、移动平均、排名等。
- 保持行的细节:与传统的聚合函数不同,滑动窗口函数不会丢失行的细节。
14.日期函数与滑动窗口 应用
你正在搭建一个用户活跃度的画像,其中一个与活跃度相关的特征是“最长连续登录天数”,
请用SQL实现“2023年1月1日-2023年1月31日用户最长的连续登录天数”
登陆表 tb_dau:
fdate | user_id |
2023-01-01 | 10000 |
2023-01-02 | 10000 |
2023-01-04 | 10000 |
输出:
user_id | max_consec_days |
10000 | 2 |
WITH continueDay as(
select
fdate,user_id,
DATE_SUB(fdate, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY fdate) DAY) AS org
FROM tb_dau
where fdate between '2023-01-01' and '2023-1-31'
),
continueLogin as(
select
user_id,
count(*) as consec
from continueDay
group by user_id,org
)
SELECT
user_id,
MAX(consec) AS max_consec_days
FROM
continueLogin
GROUP BY
user_id;
思路:
通过 DATE_SUB(fdate, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY fdate) DAY) 创建一个分组标识grp。如果日期是连续的,值会相同。例如:
2023-01-01 和 2023-01-02 的 grp 都是 2022-12-30。
2023-01-04 的 grp 是 2022-12-31。