文章目录

  • MySQL窗口函数(分组内排序、筛选)
    • 简介
    • 与GROUP BY区别
    • 窗口函数语法
      • `partition_clause` 句法
      • `order_by_clause` 句法
      • `frame_clause` 句法
    • MySQL窗口函数列表
      • 聚合函数 + over()
      • 排序函数 + over()
      • ntile()函数 + over()
      • first_value()函数 + over()
      • lag()函数 + over()
      • last_value()函数 + over()
      • lead()函数 + over()
      • nth_value()函数 + over()
      • cume_dist()函数 + over()
      • percent_rank()函数 + over()
      • 拓展:mysql 5.7怎么实现row_number函数呢?
    • 经典题目

MySQL窗口函数(分组内排序、筛选)

简介

​ 窗口函数(window functions),也被称为 “开窗函数”,也叫OLAP函数(Online Anallytical Processing,联机分析处理),可对数据库数据进行实时分析处理。它是数据库的标准功能之一,主流的数据库比如Oracle,PostgreSQL都支持窗口函数功能,MySQL 直到 8.0 版本才开始支持窗口函数。

​ 窗口函数,简单来说就是对于一个查询SQL,将其结果集按指定的规则进行分区,每个分区可以看作是一个窗口,分区内的每一行,根据 其所属分区内的行数据进行函数计算,获取计算结果,作为该行的窗口函数结果值。

与GROUP BY区别

窗口函数与group聚合查询类似,都是对一组(分区)记录进行计算,区别在于group对一组记录计算后返回一条记录作为结果,而窗口函数对一组记录计算后,这组记录中每条数据都会对应一个结果。

下面举个例子看一下:

假设我们有一个sales表,为员工的年度销额表:

CREATE TABLE sales(sales_employee VARCHAR(50) NOT NULL,fiscal_year INT NOT NULL,sale DECIMAL(14,2) NOT NULL,PRIMARY KEY(sales_employee,fiscal_year)
);INSERT INTO sales(sales_employee,fiscal_year,sale)
VALUES('Bob',2016,100),('Bob',2017,150),('Bob',2018,200),('Alice',2016,150),('Alice',2017,100),('Alice',2018,200),('John',2016,200),('John',2017,150),('John',2018,250);SELECT *
FROM sales;
+----------------+-------------+--------+
| sales_employee | fiscal_year | sale   |
+----------------+-------------+--------+
| Alice          |        2016 | 150.00 |
| Alice          |        2017 | 100.00 |
| Alice          |        2018 | 200.00 |
| Bob            |        2016 | 100.00 |
| Bob            |        2017 | 150.00 |
| Bob            |        2018 | 200.00 |
| John           |        2016 | 200.00 |
| John           |        2017 | 150.00 |
| John           |        2018 | 250.00 |
+----------------+-------------+--------+
9 rows in set (0.01 sec)

例如,以下sum()函数返回记录年份中所有员工的总销售额,通过group by分组查询每年度员工的销售总额,如下sql:

SELECT fiscal_year, SUM(sale)
FROMsales
GROUP BY fiscal_year;

查询结果如下:

+-------------+-----------+
| fiscal_year | SUM(sale) |
+-------------+-----------+
|        2016 |    450.00 |
|        2017 |    400.00 |
|        2018 |    650.00 |
+-------------+-----------+
3 rows in set (0.01 sec)

在上述示例中,聚合函数都会减少查询返回的行数。

与带有GROUP BY子句的聚合函数一样,窗口函数也对行的子集进行操作,但它们不会减少查询返回的行数。

例如,以下查询返回每个员工的销售额,以及按会计年度计算的员工总销售额:

SELECT fiscal_year, sales_employee,sale,SUM(sale) OVER (PARTITION BY fiscal_year) total_sales
FROMsales; 

查询结果如下:

+-------------+----------------+--------+-------------+
| fiscal_year | sales_employee | sale   | total_sales |
+-------------+----------------+--------+-------------+
|        2016 | Alice          | 150.00 |      450.00 |
|        2016 | Bob            | 100.00 |      450.00 |
|        2016 | John           | 200.00 |      450.00 |
|        2017 | Alice          | 100.00 |      400.00 |
|        2017 | Bob            | 150.00 |      400.00 |
|        2017 | John           | 150.00 |      400.00 |
|        2018 | Alice          | 200.00 |      650.00 |
|        2018 | Bob            | 200.00 |      650.00 |
|        2018 | John           | 250.00 |      650.00 |
+-------------+----------------+--------+-------------+
9 rows in set (0.02 sec)

在此示例中,SUM()函数用作窗口函数,函数对由OVER子句内容定义的一组行进行操作。SUM()应用函数的一组行称为窗口。

窗口函数语法

调用窗口函数的一般语法如下:

window_function_name(expression) OVER ([partition_defintion][order_definition][frame_definition])

在这个语法中:

  • 首先,指定窗口函数名称,后跟表达式。
  • 其次,指定OVER具有三个可能元素的子句:分区定义,顺序定义和帧定义。

OVER子句后面的开括号和右括号是强制性的,即使没有表达式,例如:

window_function_name(expression) OVER()

partition_clause 句法

partition_clause行分成块或分区。两个分区由分区边界分隔。

窗口函数在分区内执行,并在跨越分区边界时重新初始化。

partition_clause语法如下所示:

PARTITION BY <expression>[{,<expression>...}]

您可以在PARTITION BY子句中指定一个或多个表达式。多个表达式用逗号分隔。

order_by_clause 句法

order_by_clause语法如下:

ORDER BY <expression> [ASC|DESC], [{,<expression>...}]

ORDER BY子句指定行在分区中的排序方式。可以在多个键上的分区内对数据进行排序,每个键由表达式指定。多个表达式也用逗号分隔。

PARTITION BY子句类似ORDER BY,所有窗口函数也支持子句。但是,仅对ORDER BY顺序敏感的窗口函数使用子句才有意义。

frame_clause 句法

帧是当前分区的子集。要定义子集,请使用frame子句,如下所示:

frame_unit {<frame_start>|<frame_between>}

相对于当前行定义帧,这允许帧根据其分区内当前行的位置在分区内移动。

帧单位指定当前行和帧行之间的关系类型。它可以是ROWSRANGE。当前行和帧行的偏移量是行号,如果帧单位是ROWS行值,则行值是帧单位RANGE

所述frame_startframe_between定义帧边界。

frame_start包含下列之一:

  • UNBOUNDED PRECEDING:frame从分区的第一行开始。
  • N PRECEDING:第一个当前行之前的物理N行。N可以是文字数字或计算结果的表达式。
  • CURRENT ROW:当前计算的行

frame_between如下:

BETWEEN frame_boundary_1 AND frame_boundary_2

frame_boundary_1frame_boundary_2可各自含有下列之一:

  • frame_start:如前所述。
  • UNBOUNDED FOLLOWING:框架结束于分区的最后一行。
  • N FOLLOWING:当前行之后的物理N行。

如果未frame_definitionOVER子句中指定,则MySQL默认使用以下帧:

RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

MySQL窗口函数列表

聚合函数 + over()

常用聚合函数有:

函数名 作用
max 查询指定列的最大值
min 查询指定列的最小值
count 统计查询结果的行数
sum 求和,返回指定列的总和
avg 求平均值,返回指定列数据的平均值

排序函数 + over()

排序函数有row_number()rank()dense_rank()这三个函数,语法中排序字句(order_definition)是必填的,分组字句(partition_defintion)是非必填,不填表示整表排序,填写时表示组内排序。

  • row_number(): 为不重复的连续排序,从1开始,为查询到的数据依次生成不重复的序号进行排序,基本语法——row_number() over(order by 需要排序的字段 asc/desc);
  • rank(): 为跳跃排序,结果相同的两个数据并列,为下一个数据空出所占的名次,即相同排名会占位,基本语法——rank() over(order by 需要排序的字段 asc/desc);
  • dense_rank(): 为有重复的连续排序,结果相同的两个数据并列,不为下一个数据空出所占的名次,即相同排名不占位,基本语法——dense_rank() over(order by 需要排序的字段 asc/desc);

下面举例看一下:

假设有一个表employee保存了员工薪资和部门信息。

CREATE TABLE employee(`id` varchar(10) PRIMARY KEY NOT NULL COMMENT '主键',`name` varchar(50) NOT NULL COMMENT '姓名',`salary` int NOT NULL COMMENT '薪资',`department` varchar(50) NOT NULL COMMENT '部门'
);INSERT INTO employee(`id`,`name`,`salary`,`department`)
VALUES ('1','Joe',85000,'IT'),
('2','Henry',85000,'Sales'),
('3','Sam',60000,'Sales'),
('4','Max',90000,'IT'),
('5','Janet',69000,'IT'),
('6','Randy',85000,'IT'),
('7','Will',70000,'IT');SELECT * FROM employee;
+----+-------+--------+------------+
| id | name  | salary | department |
+----+-------+--------+------------+
| 1  | Joe   |  85000 | IT         |
| 2  | Henry |  85000 | Sales      |
| 3  | Sam   |  60000 | Sales      |
| 4  | Max   |  90000 | IT         |
| 5  | Janet |  69000 | IT         |
| 6  | Randy |  85000 | IT         |
| 7  | Will  |  70000 | IT         |
+----+-------+--------+------------+
7 rows in set (0.00 sec)

下面语句展示未分组进行排序:

SELECT`id`,`name`,`salary`,`department`,row_number() over(order by salary desc) as `row_number`,rank() over(order by salary desc) as `rank`,dense_rank() over(order by salary desc) as `dense_rank`
FROMemployee;

查询结果如下:

+----+-------+--------+------------+------------+------+------------+
| id | name  | salary | department | row_number | rank | dense_rank |
+----+-------+--------+------------+------------+------+------------+
| 4  | Max   |  90000 | IT         |          1 |    1 |          1 |
| 1  | Joe   |  85000 | IT         |          2 |    2 |          2 |
| 2  | Henry |  85000 | Sales      |          3 |    2 |          2 |
| 6  | Randy |  85000 | IT         |          4 |    2 |          2 |
| 7  | Will  |  70000 | IT         |          5 |    5 |          3 |
| 5  | Janet |  69000 | IT         |          6 |    6 |          4 |
| 3  | Sam   |  60000 | Sales      |          7 |    7 |          5 |
+----+-------+--------+------------+------------+------+------------+
7 rows in set (0.00 sec)

下面语句展示根据部门分组进行排序:

SELECT`id`,`name`,`salary`,`department`,row_number() over(partition by department  order by salary desc) as `row_number`,rank() over(partition by department  order by salary desc) as `rank`,dense_rank() over(partition by department  order by salary desc) as `dense_rank`
FROMemployee;

查询结果如下:

+----+-------+--------+------------+------------+------+------------+
| id | name  | salary | department | row_number | rank | dense_rank |
+----+-------+--------+------------+------------+------+------------+
| 4  | Max   |  90000 | IT         |          1 |    1 |          1 |
| 1  | Joe   |  85000 | IT         |          2 |    2 |          2 |
| 6  | Randy |  85000 | IT         |          3 |    2 |          2 |
| 7  | Will  |  70000 | IT         |          4 |    4 |          3 |
| 5  | Janet |  69000 | IT         |          5 |    5 |          4 |
| 2  | Henry |  85000 | Sales      |          1 |    1 |          1 |
| 3  | Sam   |  60000 | Sales      |          2 |    2 |          2 |
+----+-------+--------+------------+------------+------+------------+
7 rows in set (0.00 sec)

ntile()函数 + over()

基本语法: ntile(n) over(partition by…order by…),其中n表示被切分的段数。

含义: ntile(n)用于将分组数据平均切分成n块,如果切分的每组数量不均等,则第一组分得的数据更多。

举例: ntile()函数通常用于比如部门前33%高薪的员工,则n取值为3,用where筛选出第一组的数据。其sql如下:

SELECT temp.* FROM (
SELECT`id`,`name`,`salary`,`department`,row_number() over(partition by department  order by salary desc) as `row_number`,ntile(3) over(partition by department  order by salary desc) as `ntile`
FROMemployee) temp
WHERE temp.ntile <= 1;

执行结果如下:

+----+-------+--------+------------+------------+-------+
| id | name  | salary | department | row_number | ntile |
+----+-------+--------+------------+------------+-------+
| 4  | Max   |  90000 | IT         |          1 |     1 |
| 1  | Joe   |  85000 | IT         |          2 |     1 |
| 2  | Henry |  85000 | Sales      |          1 |     1 |
+----+-------+--------+------------+------------+-------+
3 rows in set (0.01 sec)

first_value()函数 + over()

基本语法: first_value(column) over(partition by…order by…),其中column为的列名

含义: 返回窗口第一行中列column对应的值

举例: 查询部门的年薪最高者姓名追加到新的一列

SELECT`id`,`name`,`salary`,`department`,first_value(name) over(partition by department  order by salary desc) as `max_salary_name`
FROMemployee;

查询结果如下:

+----+-------+--------+------------+-----------------+
| id | name  | salary | department | max_salary_name |
+----+-------+--------+------------+-----------------+
| 4  | Max   |  90000 | IT         | Max             |
| 1  | Joe   |  85000 | IT         | Max             |
| 6  | Randy |  85000 | IT         | Max             |
| 7  | Will  |  70000 | IT         | Max             |
| 5  | Janet |  69000 | IT         | Max             |
| 2  | Henry |  85000 | Sales      | Henry           |
| 3  | Sam   |  60000 | Sales      | Henry           |
+----+-------+--------+------------+-----------------+
7 rows in set (0.01 sec)

lag()函数 + over()

基本语法:

LAG(<expression>[,offset[, default_value]]) OVER (PARTITION BY expr,...ORDER BY expr [ASC|DESC],...
)
  • expression

LAG()函数返回expression当前行之前的行的值,其值为offset 其分区或结果集中的行数。

  • offset

offset是从当前行返回的行数,以获取值。offset必须是零或文字正整数。如果offset为零,则LAG()函数计算expression当前行的值。如果未指定offset,则LAG()默认情况下函数使用一个。

  • default_value

如果没有前一行,则LAG()函数返回default_value。例如,如果offset为2,则第一行的返回值为default_value。如果省略default_value,则默认LAG()返回函数NULL

  • PARTITION BY 子句

PARTITION BY子句将结果集中的行划分LAG()为应用函数的分区。如果省略PARTITION BY子句,LAG()函数会将整个结果集视为单个分区。

  • ORDER BY 子句

ORDER BY子句指定在LAG()应用函数之前每个分区中的行的顺序。

LAG()函数可用于计算当前行和上一行之间的差异。

含义: 返回分区中当前行之前的第N行的值。 如果不存在前一行,则返回NULL。。

举例: 查询部门中比当前员工年薪较高一位姓名追加到新的一列

SELECT`id`,`name`,`salary`,`department`,lag(name,1) over(partition by department  order by salary desc) as `higher_salary_name`
FROMemployee;

查询结果如下:

+----+-------+--------+------------+--------------------+
| id | name  | salary | department | higher_salary_name |
+----+-------+--------+------------+--------------------+
| 4  | Max   |  90000 | IT         | NULL               |
| 1  | Joe   |  85000 | IT         | Max                |
| 6  | Randy |  85000 | IT         | Joe                |
| 7  | Will  |  70000 | IT         | Randy              |
| 5  | Janet |  69000 | IT         | Will               |
| 2  | Henry |  85000 | Sales      | NULL               |
| 3  | Sam   |  60000 | Sales      | Henry              |
+----+-------+--------+------------+--------------------+
7 rows in set (0.00 sec)

last_value()函数 + over()

基本语法:

LAST_VALUE (expression) OVER ([partition_clause][order_clause][frame_clause]
)
  • expression:列名

查询部门的年薪最第者姓名追加到新的一列

 SELECT`id`,`name`,`salary`,`department`,last_value(name) over(partition by department  order by salary desc) as `min_salary_name`
FROMemployee;

查询结果如下,很显然结果和我们预期的不一样:

+----+-------+--------+------------+-----------------+
| id | name  | salary | department | min_salary_name |
+----+-------+--------+------------+-----------------+
| 4  | Max   |  90000 | IT         | Max             |
| 1  | Joe   |  85000 | IT         | Randy           |
| 6  | Randy |  85000 | IT         | Randy           |
| 7  | Will  |  70000 | IT         | Will            |
| 5  | Janet |  69000 | IT         | Janet           |
| 2  | Henry |  85000 | Sales      | Henry           |
| 3  | Sam   |  60000 | Sales      | Sam             |
+----+-------+--------+------------+-----------------+
7 rows in set (0.00 sec)

为什么min_salary_name查询是当前行数据呢?

原因在于这两个函数 可以用rows 指定作用域。 而默认的作用域是
RANGE UNBOUNDED PRECEDING AND CURRENT ROW就是说从窗口的第一行到当前行。 所以last_value 最后一行肯定是当前行了。知道原因后,只需要改掉行的作用域就可以了。

如下sql:

SELECT`id`,`name`,`salary`,`department`,last_value(name) over(partition by department  order by salary desc rows between UNBOUNDED PRECEDING AND UNBOUNDED following) as `min_salary_name`
FROMemployee;

查询结果如下,这回就正确了:

+----+-------+--------+------------+-----------------+
| id | name  | salary | department | min_salary_name |
+----+-------+--------+------------+-----------------+
| 4  | Max   |  90000 | IT         | Janet           |
| 1  | Joe   |  85000 | IT         | Janet           |
| 6  | Randy |  85000 | IT         | Janet           |
| 7  | Will  |  70000 | IT         | Janet           |
| 5  | Janet |  69000 | IT         | Janet           |
| 2  | Henry |  85000 | Sales      | Sam             |
| 3  | Sam   |  60000 | Sales      | Sam             |
+----+-------+--------+------------+-----------------+
7 rows in set (0.00 sec)

lead()函数 + over()

基本语法:

LEAD(<expression>[,offset[, default_value]]) OVER (PARTITION BY (expr)ORDER BY (expr)
)
  • expression

LEAD()函数返回的值expressionoffset-th有序分区排。

  • offset

offset是从当前行向前行的行数,以获取值。

offset必须是一个非负整数。如果offset为零,则LEAD()函数计算expression当前行的值。

如果省略 offset,则LEAD()函数默认使用一个。

  • default_value

如果没有后续行,则LEAD()函数返回default_value。例如,如果offset是1,则最后一行的返回值为default_value

如果您未指定default_value,则函数返回 NULL

  • PARTITION BY子句

PARTITION BY子句将结果集中的行划分LEAD()为应用函数的分区。

如果PARTITION BY未指定子句,则结果集中的所有行都将被视为单个分区。

  • ORDER BY子句

ORDER BY子句确定LEAD()应用函数之前分区中行的顺序。

含义: 返回分区中当前行之后的第N行的值。 如果不存在前一行,则返回NULL。。

举例: 查询部门中比当前员工年薪较低一位姓名追加到新的一列

SELECT`id`,`name`,`salary`,`department`,lead(name,1) over(partition by department  order by salary desc) as `lower_salary_name`
FROMemployee;

查询结果如下:

+----+-------+--------+------------+-------------------+
| id | name  | salary | department | lower_salary_name |
+----+-------+--------+------------+-------------------+
| 4  | Max   |  90000 | IT         | Joe               |
| 1  | Joe   |  85000 | IT         | Randy             |
| 6  | Randy |  85000 | IT         | Will              |
| 7  | Will  |  70000 | IT         | Janet             |
| 5  | Janet |  69000 | IT         | NULL              |
| 2  | Henry |  85000 | Sales      | Sam               |
| 3  | Sam   |  60000 | Sales      | NULL              |
+----+-------+--------+------------+-------------------+
7 rows in set (0.00 sec)

nth_value()函数 + over()

基本语法:

NTH_VALUE(expression, N)
FROM FIRST
OVER (partition_clauseorder_clauseframe_clause
)

NTH_VALUE()函数返回expression窗口框架第N行的值。如果第N行不存在,则函数返回NULL。N必须是正整数,例如1,2和3。

FROM FIRST指示NTH_VALUE()功能在窗口帧的第一行开始计算。

请注意,SQL标准支持FROM FIRSTFROM LAST。但是,MySQL只支持FROM FIRST。如果要模拟效果FROM LAST,则可以使用其中ORDER BYover_clause相反顺序对结果集进行排序。

含义: 返回窗口框架第N行的参数值。请注意默认边界问题加RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING与不加的区别。

举例: 查询部门中薪水第二高的员工姓名追加到新的一列

SELECT`id`,`name`,`salary`,`department`,nth_value(name,2) over(partition by department  order by salary desc RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as `second_salary_name`
FROMemployee;

查询结果如下:

+----+-------+--------+------------+--------------------+
| id | name  | salary | department | second_salary_name |
+----+-------+--------+------------+--------------------+
| 4  | Max   |  90000 | IT         | Joe                |
| 1  | Joe   |  85000 | IT         | Joe                |
| 6  | Randy |  85000 | IT         | Joe                |
| 7  | Will  |  70000 | IT         | Joe                |
| 5  | Janet |  69000 | IT         | Joe                |
| 2  | Henry |  85000 | Sales      | Sam                |
| 3  | Sam   |  60000 | Sales      | Sam                |
+----+-------+--------+------------+--------------------+
7 rows in set (0.00 sec)

cume_dist()函数 + over()

基本语法:

CUME_DIST() OVER (PARTITION BY expr, ...ORDER BY expr [ASC | DESC], ...
)

含义: 它返回一组值中值的累积分布。它表示值小于或等于行的值除以总行数的行数。

举例: 查询部门中员工薪资累积分布(即高于等于当前员工工资员工数量占员工总数的百分比)追加到新的一列

SELECT`id`,`name`,`salary`,`department`,cume_dist() over(partition by department  order by salary desc ) as `cume`
FROMemployee;

查询结果如下:

+----+-------+--------+------------+------+
| id | name  | salary | department | cume |
+----+-------+--------+------------+------+
| 4  | Max   |  90000 | IT         |  0.2 |
| 1  | Joe   |  85000 | IT         |  0.6 |
| 6  | Randy |  85000 | IT         |  0.6 |
| 7  | Will  |  70000 | IT         |  0.8 |
| 5  | Janet |  69000 | IT         |    1 |
| 2  | Henry |  85000 | Sales      |  0.5 |
| 3  | Sam   |  60000 | Sales      |    1 |
+----+-------+--------+------------+------+
7 rows in set (0.00 sec)

percent_rank()函数 + over()

基本语法:

PERCENT_RANK()OVER (PARTITION BY expr,...ORDER BY expr [ASC|DESC],...)

含义: PERCENT_RANK()函数返回一个从0到1的数字。

对于指定的行,PERCENT_RANK()计算行的等级减1,除以评估的分区或查询结果集中的行数减1:

(rank - 1) / (total_rows - 1)

在此公式中,rank是指定行的等级,total_rows是要计算的行数。

PERCENT_RANK()对于分区或结果集中的第一行,函数始终返回零。重复的列值将接收相同的PERCENT_RANK()值。

与其他窗口函数类似,PARTITION BY子句将行分配到分区中,ORDER BY子句指定每个分区中行的逻辑顺序。PERCENT_RANK()为每个有序分区独立计算函数。

两个PARTITION BYORDER BY子句都是可选项。但是,它PERCENT_RANK()是一个顺序敏感函数,因此,您应始终使用ORDER BY子句。

举例: 查询部门中员工薪资等级分布追加到新的一列

SELECT`id`,`name`,`salary`,`department`,percent_rank() over(partition by department  order by salary desc ) as `percent_rank`
FROMemployee;

查询结果如下:

+----+-------+--------+------------+--------------+
| id | name  | salary | department | percent_rank |
+----+-------+--------+------------+--------------+
| 4  | Max   |  90000 | IT         |            0 |
| 1  | Joe   |  85000 | IT         |         0.25 |
| 6  | Randy |  85000 | IT         |         0.25 |
| 7  | Will  |  70000 | IT         |         0.75 |
| 5  | Janet |  69000 | IT         |            1 |
| 2  | Henry |  85000 | Sales      |            0 |
| 3  | Sam   |  60000 | Sales      |            1 |
+----+-------+--------+------------+--------------+
7 rows in set (0.00 sec)

拓展:mysql 5.7怎么实现row_number函数呢?

mysql 8.0版本我们可以直接使用row_number实现部门薪资排名,如下sql:

SELECT`id`,`name`,`salary`,`department`,row_number() over(partition by department  order by salary desc) as `row_number`
FROMemployee;

查询结果如下:

+----+-------+--------+------------+------------+
| id | name  | salary | department | row_number |
+----+-------+--------+------------+------------+
| 4  | Max   |  90000 | IT         |          1 |
| 1  | Joe   |  85000 | IT         |          2 |
| 6  | Randy |  85000 | IT         |          3 |
| 7  | Will  |  70000 | IT         |          4 |
| 5  | Janet |  69000 | IT         |          5 |
| 2  | Henry |  85000 | Sales      |          1 |
| 3  | Sam   |  60000 | Sales      |          2 |
+----+-------+--------+------------+------------+
7 rows in set (0.00 sec)

mysql 5.7因为还没有窗口函数,所以我们实现其查询逻辑,下面给出查询sql:

SELECTa.*,@rn := ( IF ( @department = department, @rn + 1, 1 ) ) AS num, @department := department AS temp_department # 要点:分组字段必须要赋值,顺序一定在生成序号逻辑后面FROM( SELECT * FROM employee  ORDER BY department, salary ) a, ( SELECT @rn := 0, @department := '' ) b

查询结果如下:

+----+-------+--------+------------+------+-----------------+
| id | name  | salary | department | num  | temp_department |
+----+-------+--------+------------+------+-----------------+
| 5  | Janet |  69000 | IT         |    1 | IT              |
| 7  | Will  |  70000 | IT         |    2 | IT              |
| 1  | Joe   |  85000 | IT         |    3 | IT              |
| 6  | Randy |  85000 | IT         |    4 | IT              |
| 4  | Max   |  90000 | IT         |    5 | IT              |
| 3  | Sam   |  60000 | Sales      |    1 | Sales           |
| 2  | Henry |  85000 | Sales      |    2 | Sales           |
+----+-------+--------+------------+------+-----------------+
7 rows in set, 4 warnings (0.00 sec)

上面的逻辑实现也比较简单也比较巧妙,其思想是:

  • 使用排序来实现分组,其字段顺序也比较巧妙,要分组的字段放在前面,要排序的字段放在后面。
  • 需要定义一个变量记录生成的序号,需要定义一个或多个变量记录前一条记录的值,多个是指多个分组
  • 分组字段必须要赋值,顺序一定在生成序号逻辑后面

当然也能实现rank()dense_rank()函数,请读者思考自行实现。

经典题目

排名问题:每个部门按业绩来排名
topN问题:找出每个部门排名前N的员工进行奖励

leetcode 185. 部门工资前三高的所有员工(困难)

相信读者看完本篇,这道题简单闭着眼睛也能写出来,哈哈哈。

MySQL窗口函数(分组内排序、筛选)相关推荐

  1. MYSQL —(二)筛选、聚合和分组、查询

    MYSQL -(二)筛选.聚合和分组.查询 虚拟机清屏:Ctrl+l 筛选条件 比较运算符 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e9UNlksr-158046 ...

  2. mysql 连接 分组_MySQL 基础 (四) 分组查询及连接查询

    MySQL 基础 (四) 分组查询及连接查询 MySQL 基础(四) 进阶 5 分组查询 语法: SELECT 分组函数, 列(要求出现在 group by 的后面) FROM 表 [where 筛选 ...

  3. mysql 名字分组查询id_mysql进阶5:分组查询

    /*语法: select 分组函数 列(要求出现在group by的后面) from 表 [where 筛选条件] group by 分组的列表 [order by 子句] 注意:查询列表必须特殊,要 ...

  4. hive窗口函数分组排序并取第一个值_Hive(七)Hive分析窗口函数

    cookie1,2015-04-10,1 cookie1,2015-04-11,5 cookie1,2015-04-12,7 cookie1,2015-04-13,3 cookie1,2015-04- ...

  5. sql 排序 分组 层级 筛选 - God聚会啊

    前言: 以前做过2种列表,1是有排序,有筛选功能,但是没有层级和分组,2是有树形结构的层级和分组,但是数据是一下全部加载出来,虽然有点落后,没有用到分页加载,但是也是受制于大环境. 今天有1个需求是  ...

  6. MySQL窗口函数简介

    原文地址:https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html#function_last-value 译 ...

  7. mysql 数据分组_详解MySQL 数据分组

    创建分组 分组是在select语句中的group by 子句中建立的. 例: select vend_id, count(*) as num_prods from products group by ...

  8. mysql实现分组查询每个班级的前三名

    mysql实现分组查询每个班级的前三名set character_set_server = utf8mb4 ; DROP TABLE IF EXISTS student;CREATE TABLE IF ...

  9. SQL语法(三) 分组和筛选

    前言 本章将学习sql分组及筛选. 范例 1.分组查询 --关键字:group by 分组字段名,分组字段名....          --注意1:使用了分组后,在select语句中只允许出现分组字段 ...

  10. MySQL SQL分组查询

    一.group by 子句 语法: select 分组函数,列(要求出现在group by)的后面 from 表 [where 筛选条件] group by 分组的列表 [order by 子句] 注 ...

最新文章

  1. 深度学习经典案例解析:YOLO系列
  2. Absolute Uninstaller是类似于标准的Windows添加/删除卸载工具
  3. Spring AOP 的proxy详解
  4. 程序员必定会爱上的10款软件
  5. 【CodeForces - 271B 】Prime Matrix (素数,预处理打表,思维)
  6. web登录时候加入过滤器的用法
  7. MATLAB中移动平均滤波器smooth函数的用法
  8. java8 追加文字到文件_使用Stream-Java 8替换文件中的文本
  9. pytorch---模型加载与保存(6)通过设备保存/加载模型
  10. JSTL 核心标签库 使用(C标签)
  11. 【计量经济学导论】10. ARIMA模型
  12. tftp命令 – 上传及下载文件
  13. 目标检测之RFB-NET(论文翻译辅助阅读)
  14. idea中不显示代码下边的下划线
  15. js技巧收集(200多个)——2
  16. Win10运行程序提示不受信任的解决方法
  17. 十年的征程 - 人类探测器今日首次软着陆彗星:选定J点登陆
  18. 好书推荐之《嫌疑人X的献身》 隐私策略(Privacy policy)
  19. CentOS7.2 Django + uwsgi + Nginx 部(cǎi )署(kēng)指(zhī)南(lǜ)
  20. 里恩临床试验项目管理系统(RH-CTMS)介绍

热门文章

  1. 小米手机系统wifi服务器,手机时间变慢,小米高管科普:3招即可解决
  2. Cesium之【空间面积】测量
  3. iNeuOS工业互联网操作系统,设备振动状态监测、预警和分析应用案例
  4. 计算机术语翻译在线,拼音翻译在线
  5. EXCEL-批量下拉填充
  6. linux沙箱隔离_为容器提供更好的隔离:沙箱容器技术概览
  7. 如何批量删除word文章里的中文只保留英语字母
  8. 服务器系列和酷睿系列,至强cpu与酷睿两个系列之间有什么区别?
  9. 免费客户旅程(Customer Journey Mapping) 示例总汇
  10. 英伟达显卡虚拟化vGPU实践指南