MySQL8 使用 MySQL(高级)详解

在本章中,我们将介绍以下配方:

在本章中,您将了解 MySQL 新引入的特性。

如前一章所述,要在 MySQL 中存储数据,必须定义数据库和表结构(schema),这是一个主要限制。为了解决这个问题,从 MySQL 5.7 开始,MySQL 支持JavaScript 对象表示法JSON)数据类型。之前没有单独的数据类型,它存储为字符串。新的 JSON 数据类型提供 JSON 文档的自动验证和优化的存储格式。

JSON 文档以二进制格式存储,可实现以下功能:

  • 对文档元素的快速读取访问
  • 当服务器再次读取 JSON 时,不需要从文本表示中解析值
  • 直接按键或数组索引查找子对象或嵌套值,而不读取文档中它们之前或之后的所有值

假设您想存储有关员工的更多详细信息;您可以使用 JSON 保存它们:

CREATE TABLE emp_details( 
  emp_no int primary key, 
  details json 
);
INSERT INTO emp_details(emp_no, details)
VALUES ('1',
'{ "location": "IN", "phone": "+11800000000", "email": "abc@example.com", "address": { "line1": "abc", "line2": "xyz street", "city": "Bangalore", "pin": "560103"} }'
);

您可以使用->->>运算符检索 JSON 列的字段:

mysql> SELECT emp_no, details->'$.address.pin' pin FROM emp_details;
+--------+----------+
| emp_no | pin      |
+--------+----------+
| 1      | "560103" |
+--------+----------+
1 row in set (0.00 sec)

要检索不带引号的数据,请使用->>运算符:

mysql> SELECT emp_no, details->>'$.address.pin' pin FROM emp_details;
+--------+--------+
| emp_no | pin    |
+--------+--------+
| 1      | 560103 |
+--------+--------+
1 row in set (0.00 sec)

MySQL 提供了许多处理 JSON 数据的函数。让我们看看最常用的。

要以漂亮的格式显示 JSON 值,请使用JSON_PRETTY()函数:

mysql> SELECT emp_no, JSON_PRETTY(details) FROM emp_details \G
*************************** 1\. row ***************************
 emp_no: 1
JSON_PRETTY(details): {
 "email": "abc@example.com",
 "phone": "+11800000000",
 "address": {
 "pin": "560103",
 "city": "Bangalore",
 "line1": "abc",
 "line2": "xyz street"
 },
 "location": "IN"
}
1 row in set (0.00 sec)

没有美丽:

mysql> SELECT emp_no, details FROM emp_details \G
*************************** 1\. row ***************************
 emp_no: 1
details: {"email": "abc@example.com", "phone": "+11800000000", "address": {"pin": "560100", "city": "Bangalore", "line1": "abc", "line2": "xyz street"}, "location": "IN"}
1 row in set (0.00 sec)

您可以使用WHERE子句中的col->>path运算符引用 JSON 列:

mysql> SELECT emp_no FROM emp_details WHERE details->>'$.address.pin'="560103";
+--------+
| emp_no |
+--------+
| 1      |
+--------+
1 row in set (0.00 sec)

您也可以使用JSON_CONTAINS功能来搜索数据。如果找到数据则返回1,否则返回0

mysql> SELECT JSON_CONTAINS(details->>'$.address.pin', "560103") FROM emp_details;
+----------------------------------------------------+
| JSON_CONTAINS(details->>'$.address.pin', "560103") |
+----------------------------------------------------+
| 1                                                  |
+----------------------------------------------------+
1 row in set (0.00 sec)

如何搜索密钥?假设您要检查address.line1是否存在:

mysql> SELECT JSON_CONTAINS_PATH(details, 'one', "$.address.line1") FROM emp_details;
+--------------------------------------------------------------------------+
| JSON_CONTAINS_PATH(details, 'one', "$.address.line1")                    |
+--------------------------------------------------------------------------+
| 1                                                                        |
+--------------------------------------------------------------------------+
1 row in set (0.01 sec)

这里,one表示至少应该存在一个密钥。假设您要检查address.line1address.line2是否存在:

mysql> SELECT JSON_CONTAINS_PATH(details, 'one', "$.address.line1", "$.address.line5") FROM emp_details;
+--------------------------------------------------------------------------+
| JSON_CONTAINS_PATH(details, 'one', "$.address.line1", "$.address.line2") |
+--------------------------------------------------------------------------+
| 1                                                                        |
+--------------------------------------------------------------------------+

如果您想检查address.line1address.line5是否同时存在,可以使用and代替one

mysql> SELECT JSON_CONTAINS_PATH(details, 'all', "$.address.line1", "$.address.line5") FROM emp_details;
+--------------------------------------------------------------------------+
| JSON_CONTAINS_PATH(details, 'all', "$.address.line1", "$.address.line5") |
+--------------------------------------------------------------------------+
| 0                                                                        |
+--------------------------------------------------------------------------+
1 row in set (0.00 sec)

您可以使用三种不同的功能修改数据:JSON_SET()JSON_INSERT()JSON_REPLACE()。在 MySQL 8 之前,我们需要对整个列进行完整更新,这不是最佳方式:

  • JSON_SET:替换现有值并添加不存在的值。 假设您要替换员工的 pin 码,并添加昵称的详细信息:
mysql> UPDATE 
    emp_details 
SET 
    details = JSON_SET(details, "$.address.pin", "560100", "$.nickname", "kai")
WHERE 
    emp_no = 1;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0
  • JSON_INSERT():插入值而不替换现有值

假设要添加新列而不更新现有值;您可以使用JSON_INSERT()

mysql> UPDATE emp_details SET details=JSON_INSERT(details, "$.address.pin", "560132", "$.address.line4", "A Wing") WHERE emp_no = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

在这种情况下,pin不会被更新;只会添加一个新的address.line4字段。

  • JSON_REPLACE():仅替换已有值

假设只替换字段而不添加新字段:

mysql> UPDATE emp_details SET details=JSON_REPLACE(details, "$.address.pin", "560132", "$.address.line5", "Landmark") WHERE 
emp_no = 1;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1 Changed: 1 Warnings: 0

在这种情况下,line5不会被添加。只有pin将被更新。

JSON_REMOVE从 JSON 文档中删除数据。

假设您不再需要地址中的line5

mysql> UPDATE emp_details SET details=JSON_REMOVE(details, "$.address.line5") WHERE emp_no = 1;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1 Changed: 1 Warnings: 0

其他一些功能如下:

  • JSON_KEYS():获取 JSON 文档中的所有键:
mysql> SELECT JSON_KEYS(details) FROM emp_details WHERE emp_no = 1;
*************************** 1\. row ***************************
JSON_KEYS(details): ["email", "phone", "address", "nickname", "locatation"]
  • JSON_LENGTH():给出 JSON 文档中元素的数量:
mysql> SELECT JSON_LENGTH(details) FROM emp_details WHERE emp_no = 1;
*************************** 1\. row ***************************
JSON_LENGTH(details): 5

您可以在查看完整的功能列表 https://dev.mysql.com/doc/refman/8.0/en/json-function-reference.html

MySQL 8 支持非递归和递归的通用表表达式。

公共表表达式允许使用命名的临时结果集,通过允许在SELECT语句和某些其他语句之前使用WITH子句来实现。

Why do you need CTEs? It is not possible to refer to a derived table twice in the same query. So the derived tables are evaluated twice or as many times as referred, which indicates a serious performance problem. Using CTE, the subquery is evaluated only once.

递归和非递归 CTE 将在以下章节中进行研究。

一个公共表表达式CTE)就像一个派生表,但它的声明放在查询块之前,而不是放在FROM子句中。

派生表

SELECT... FROM (subquery) AS derived, t1 ...

CTE

SELECT... WITH derived AS (subquery) SELECT ... FROM derived, t1 ...

CTE 可以在SELECT/UPDATE/DELETE之前,包括子查询WITH派生的AS(子查询),例如:

DELETE FROM t1 WHERE t1.a IN (SELECT b FROM derived);

假设你想知道每年的工资相对于上一年的增长百分比。没有 CTE,您需要编写两个子查询,它们基本上是相同的。MySQL 不够聪明,无法检测到这一点,子查询执行两次:

mysql> SELECT 
    q1.year, 
    q2.year AS next_year, 
    q1.sum, 
    q2.sum AS next_sum, 
    100*(q2.sum-q1.sum)/q1.sum AS pct 
FROM 
    (SELECT year(from_date) as year, sum(salary) as sum FROM salaries GROUP BY year) AS q1,             (SELECT year(from_date) as year, sum(salary) as sum FROM salaries GROUP BY year) AS q2 
WHERE q1.year = q2.year-1;
+------+-----------+-------------+-------------+----------+
| year | next_year | sum         | next_sum    | pct      |
+------+-----------+-------------+-------------+----------+
| 1985 |      1986 | 972864875   | 2052895941  | 111.0155 |
| 1986 |      1987 | 2052895941  | 3156881054  | 53.7770  |
| 1987 |      1988 | 3156881054  | 4295598688  | 36.0710  |
| 1988 |      1989 | 4295598688  | 5454260439  | 26.9732  |
| 1989 |      1990 | 5454260439  | 6626146391  | 21.4857  |
| 1990 |      1991 | 6626146391  | 7798804412  | 17.6974  |
| 1991 |      1992 | 7798804412  | 9027872610  | 15.7597  |
| 1992 |      1993 | 9027872610  | 10215059054 | 13.1502  |
| 1993 |      1994 | 10215059054 | 11429450113 | 11.8882  |
| 1994 |      1995 | 11429450113 | 12638817464 | 10.5812  |
| 1995 |      1996 | 12638817464 | 13888587737 | 9.8883   |
| 1996 |      1997 | 13888587737 | 15056011781 | 8.4056   |
| 1997 |      1998 | 15056011781 | 16220495471 | 7.7343   |
| 1998 |      1999 | 16220495471 | 17360258862 | 7.0267   |
| 1999 |      2000 | 17360258862 | 17535667603 | 1.0104   |
| 2000 |      2001 | 17535667603 | 17507737308 | -0.1593  |
| 2001 |      2002 | 17507737308 | 10243358658 | -41.4924 |
+------+-----------+-------------+-------------+----------+
17 rows in set (3.22 sec)

对于非递归 CTE,派生查询只执行一次并重复使用:

mysql> 
WITH CTE AS 
    (SELECT year(from_date) AS year, SUM(salary) AS sum FROM salaries GROUP BY year) 
SELECT 
    q1.year, q2.year as next_year, q1.sum, q2.sum as next_sum, 100*(q2.sum-q1.sum)/q1.sum as pct FROM 
    CTE AS q1, 
    CTE AS q2 
WHERE 
    q1.year = q2.year-1;
+------+-----------+-------------+-------------+----------+
| year | next_year | sum         | next_sum    | pct      |
+------+-----------+-------------+-------------+----------+
| 1985 |      1986 | 972864875   | 2052895941  | 111.0155 |
| 1986 |      1987 | 2052895941  | 3156881054  | 53.7770  |
| 1987 |      1988 | 3156881054  | 4295598688  | 36.0710  |
| 1988 |      1989 | 4295598688  | 5454260439  | 26.9732  |
| 1989 |      1990 | 5454260439  | 6626146391  | 21.4857  |
| 1990 |      1991 | 6626146391  | 7798804412  | 17.6974  |
| 1991 |      1992 | 7798804412  | 9027872610  | 15.7597  |
| 1992 |      1993 | 9027872610  | 10215059054 | 13.1502  |
| 1993 |      1994 | 10215059054 | 11429450113 | 11.8882  |
| 1994 |      1995 | 11429450113 | 12638817464 | 10.5812  |
| 1995 |      1996 | 12638817464 | 13888587737 | 9.8883   |
| 1996 |      1997 | 13888587737 | 15056011781 | 8.4056   |
| 1997 |      1998 | 15056011781 | 16220495471 | 7.7343   |
| 1998 |      1999 | 16220495471 | 17360258862 | 7.0267   |
| 1999 |      2000 | 17360258862 | 17535667603 | 1.0104   |
| 2000 |      2001 | 17535667603 | 17507737308 | -0.1593  |
| 2001 |      2002 | 17507737308 | 10243358658 | -41.4924 |
+------+-----------+-------------+-------------+----------+
17 rows in set (1.63 sec)

您可能会注意到,使用 CTE,结果相同,查询时间提高了 50%;可读性好,可以多次引用。

派生查询不能引用其他派生查询:

SELECT ...
 FROM (SELECT ... FROM ...) AS d1, (SELECT ... FROM d1 ...) AS d2 ...
ERROR: 1146 (42S02): Table ‘db.d1’ doesn’t exist 

CTE 可参考其他 CTE:

WITH d1 AS (SELECT ... FROM ...), d2 AS (SELECT ... FROM d1 ...) 
SELECT
 FROM d1, d2 ... 

递归 CTE 是具有引用其自身名称的子查询的 CTE。WITH子句必须以WITH RECURSIVE开头。递归 CTE 子查询有两部分,种子查询和递归查询,由UNION [ALL]UNION DISTINCT分隔。

种子SELECT执行一次,创建初始数据子集;递归SELECT被重复执行以返回数据子集,直到获得完整的结果集。当迭代不生成任何新行时,递归停止。这有助于深入了解层次结构(父/子或部分/子部分):

WITH RECURSIVE cte AS
(SELECT ... FROM table_name /* seed SELECT */ 
UNION ALL 
SELECT ... FROM cte, table_name) /* "recursive" SELECT */ 
SELECT ... FROM cte;

假设您要打印从15的所有数字:

mysql> WITH RECURSIVE cte (n) AS 
( SELECT 1 /* seed query */
  UNION ALL 
  SELECT n + 1 FROM cte WHERE n < 5 /* recursive query */
) 
SELECT * FROM cte;
+---+
| n |
+---+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
+---+
5 rows in set (0.00 sec)

在每次迭代中,SELECT生成一行,该行的新值比上一行集合中n的值大一个。第一次迭代对初始行集(1)进行运算,产生1+1=2;第二次迭代对第一次迭代的行集(2)进行操作,产生2+1=3;等等等等。这一直持续到递归结束,此时n不再小于5

假设您希望进行分层数据遍历,以生成一个组织结构图,其中包含每个员工的管理链(即从 CEO 到员工的路径)。使用递归 CTE!

使用manager_id创建一个测试表:

mysql> CREATE TABLE employees_mgr (
 id INT PRIMARY KEY NOT NULL,
 name VARCHAR(100) NOT NULL,
 manager_id INT NULL,
 INDEX (manager_id),
FOREIGN KEY (manager_id) REFERENCES employees_mgr (id)
);

插入样本数据:

mysql> INSERT INTO employees_mgr VALUES
(333, "Yasmina", NULL), # Yasmina is the CEO (manager_id is NULL)
(198, "John", 333), # John has ID 198 and reports to 333 (Yasmina)
(692, "Tarek", 333),
(29, "Pedro", 198),
(4610, "Sarah", 29),
(72, "Pierre", 29),
(123, "Adil", 692);

执行递归 CTE:

mysql> WITH RECURSIVE employee_paths (id, name, path) AS
(
 SELECT id, name, CAST(id AS CHAR(200))
 FROM employees_mgr
 WHERE manager_id IS NULL
 UNION ALL
 SELECT e.id, e.name, CONCAT(ep.path, ',', e.id)
 FROM employee_paths AS ep JOIN employees_mgr AS e
 ON ep.id = e.manager_id
)
SELECT * FROM employee_paths ORDER BY path;

它产生以下结果:

+------+---------+-----------------+
| id   | name    | path            |
+------+---------+-----------------+
| 333  | Yasmina | 333             |
| 198  | John    | 333,198         |
| 29   | Pedro   | 333,198,29      |
| 4610 | Sarah   | 333,198,29,4610 |
| 72   | Pierre  | 333,198,29,72   |
| 692  | Tarek   | 333,692         |
| 123  | Adil    | 333,692,123     |
+------+---------+-----------------+
7 rows in set (0.00 sec)

WITH RECURSIVE employee_paths (id, name, path) AS是 CTE 的名称,列为(idnamepath

SELECT id, name, CAST(id AS CHAR(200)) FROM employees_mgr WHERE manager_id IS NULL是选择 CEO 的种子查询(CEO 没有经理)。

SELECT e.id, e.name, CONCAT(ep.path, ',', e.id) FROM employee_paths AS ep JOIN employees_mgr AS e ON ep.id = e.manager_id是递归查询。

递归查询生成的每一行查找直接向上一行生成的员工报告的所有员工。对于每个这样的员工,该行包括员工 ID、姓名和员工管理链。链是经理的链,在末尾添加了员工 ID。

生成的列也称为虚拟列或计算列。根据列定义中包含的表达式计算生成列的值。生成的列有两种类型:

  • :从表中读取记录时,会动态计算列
  • 存储:在表中写入新记录时计算该列,并作为常规列存储在表中

虚拟生成列比存储生成列更有用,因为虚拟列不占用任何存储空间。可以使用触发器模拟存储的生成列的行为。

假设您的应用程序在从employees表检索数据时使用full_name作为concat('first_name', ' ', 'last_name');不使用表达式,您可以使用虚拟列,动态计算full_name。可以添加另一列,后跟表达式:

mysql> CREATE TABLE `employees` (
  `emp_no` int(11) NOT NULL,
  `birth_date` date NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name` varchar(16) NOT NULL,
  `gender` enum('M','F') NOT NULL,
  `hire_date` date NOT NULL,
 `full_name` VARCHAR(30) AS (CONCAT(first_name,' ',last_name)),
  PRIMARY KEY (`emp_no`),
  KEY `name` (`first_name`,`last_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

请注意,您应该根据虚拟列修改 insert 语句。您可以选择如下方式使用完整插入:

mysql> INSERT INTO employees (emp_no, birth_date, first_name, last_name, gender, hire_date) VALUES (123456, '1987-10-02', 'ABC' , 'XYZ', 'F', '2008-07-28');

如果您想在INSERT语句中包含full_name,可以将其指定为DEFAULT。所有其他值都会抛出一个ERROR 3105 (HY000):错误。不允许为employees表中生成的列full_name指定的值:

mysql> INSERT INTO employees (emp_no, birth_date, first_name, last_name, gender, hire_date, full_name) VALUES (123456, '1987-10-02', 'ABC' , 'XYZ', 'F', '2008-07-28', DEFAULT);

您可以直接从employees表中选择full_name

mysql> SELECT * FROM employees WHERE emp_no=123456;
+--------+------------+------------+-----------+--------+------------+-----------+
| emp_no | birth_date | first_name | last_name | gender | hire_date  | full_name |
+--------+------------+------------+-----------+--------+------------+-----------+
| 123456 | 1987-10-02 | ABC        | XYZ       | F      | 2017-11-23 | ABC XYZ   |
+--------+------------+------------+-----------+--------+------------+-----------+
1 row in set (0.00 sec)

如果您已经创建了表,并且希望添加新生成的列,请执行 ALTER table 语句,该语句将在*第 10 章*表维护 中详细介绍

例子:

mysql> ALTER TABLE employees ADD hire_date_year YEAR AS (YEAR(hire_date)) VIRTUAL;

参见https://dev.mysql.com/doc/refman/8.0/en/create-table-generated-columns.html 了解更多关于生成列的信息。您将在第 13 章性能调整以及使用生成的列为 JSON 添加索引和索引部分了解虚拟列的其他用途。

通过使用窗口函数,对于查询中的每一行,可以使用与该行相关的行执行计算。这是通过使用OVERWINDOW子句实现的。

以下是您可以执行的计算列表:

  • CUME_DIST():累计分布值
  • DENSE_RANK():当前行在其分区内的排名,无空格
  • FIRST_VALUE():来自窗框第一行的参数值
  • LAG():分区内滞后于当前行的行的参数值
  • LAST_VALUE():来自窗框第一行的参数值
  • LEAD():来自分区内当前行前面的行的参数值
  • NTH_VALUE():来自n的参数值-窗框第四行
  • NTILE():当前行在其分区内的桶号
  • PERCENT_RANK():百分位值
  • RANK():当前行在其分区内的排名,带间隙
  • ROW_NUMBER():当前行在其分区内的编号

窗口函数可以以多种方式使用。让我们在下面的部分中逐一了解它们。为了使这些示例起作用,您需要添加 hire\u date\u year 虚拟列

mysql> ALTER TABLE employees ADD hire_date_year YEAR AS (YEAR(hire_date)) VIRTUAL;

您可以获取每行的行号以对结果进行排序:

mysql> SELECT CONCAT(first_name, " ", last_name) AS full_name, salary, ROW_NUMBER() OVER(ORDER BY salary DESC) AS 'Rank'  FROM employees JOIN salaries ON salaries.emp_no=employees.emp_no LIMIT 10;
+-------------------+--------+------+
| full_name         | salary | Rank |
+-------------------+--------+------+
| Tokuyasu Pesch    | 158220 |    1 |
| Tokuyasu Pesch    | 157821 |    2 |
| Honesty Mukaidono | 156286 |    3 |
| Xiahua Whitcomb   | 155709 |    4 |
| Sanjai Luders     | 155513 |    5 |
| Tsutomu Alameldin | 155377 |    6 |
| Tsutomu Alameldin | 155190 |    7 |
| Tsutomu Alameldin | 154888 |    8 |
| Tsutomu Alameldin | 154885 |    9 |
| Willard Baca      | 154459 |   10 |
+-------------------+--------+------+
10 rows in set (6.24 sec)

您可以在OVER子句中对结果进行分区。假设你想知道每年的工资等级;可按如下方式进行:

mysql> SELECT hire_date_year, salary, ROW_NUMBER() OVER(PARTITION BY hire_date_year ORDER BY salary DESC) AS 'Rank' FROM employees JOIN salaries ON salaries.emp_no=employees.emp_no ORDER BY salary DESC LIMIT 10;
+----------------+--------+------+
| hire_date_year | salary | Rank |
+----------------+--------+------+
|           1985 | 158220 |    1 |
|           1985 | 157821 |    2 |
|           1986 | 156286 |    1 |
|           1985 | 155709 |    3 |
|           1987 | 155513 |    1 |
|           1985 | 155377 |    4 |
|           1985 | 155190 |    5 |
|           1985 | 154888 |    6 |
|           1985 | 154885 |    7 |
|           1985 | 154459 |    8 |
+----------------+--------+------+
10 rows in set (8.04 sec)

您可以注意到,19861987年的排名发生了变化,但1985年的排名仍在继续。

您可以根据需要命名窗口并多次使用它,而不是每次都重新定义它:

mysql> SELECT hire_date_year, salary, RANK() OVER w AS 'Rank' FROM employees join salaries ON salaries.emp_no=employees.emp_no WINDOW w AS (PARTITION BY hire_date_year ORDER BY salary DESC) ORDER BY salary DESC LIMIT 10;
+----------------+--------+------+
| hire_date_year | salary | Rank |
+----------------+--------+------+
|           1985 | 158220 |    1 |
|           1985 | 157821 |    2 |
|           1986 | 156286 |    1 |
|           1985 | 155709 |    3 |
|           1987 | 155513 |    1 |
|           1985 | 155377 |    4 |
|           1985 | 155190 |    5 |
|           1985 | 154888 |    6 |
|           1985 | 154885 |    7 |
|           1985 | 154459 |    8 |
+----------------+--------+------+
10 rows in set (8.52 sec)

您可以在窗口结果中选择第一个、最后一个和nth 值。如果该行不存在,则返回一个NULL值。

假设要从窗口中查找第一个、最后一个和第三个值:

mysql> SELECT hire_date_year, salary, RANK() OVER w AS 'Rank', 
FIRST_VALUE(salary) OVER w AS 'first', 
NTH_VALUE(salary, 3) OVER w AS 'third', 
LAST_VALUE(salary) OVER w AS 'last' 
FROM employees join salaries ON salaries.emp_no=employees.emp_no 
WINDOW w AS (PARTITION BY hire_date_year ORDER BY salary DESC) 
ORDER BY salary DESC LIMIT 10;
+----------------+--------+------+--------+--------+--------+
| hire_date_year | salary | Rank | first  | third  | last   |
+----------------+--------+------+--------+--------+--------+
|           1985 | 158220 |    1 | 158220 |   NULL | 158220 |
|           1985 | 157821 |    2 | 158220 |   NULL | 157821 |
|           1986 | 156286 |    1 | 156286 |   NULL | 156286 |
|           1985 | 155709 |    3 | 158220 | 155709 | 155709 |
|           1987 | 155513 |    1 | 155513 |   NULL | 155513 |
|           1985 | 155377 |    4 | 158220 | 155709 | 155377 |
|           1985 | 155190 |    5 | 158220 | 155709 | 155190 |
|           1985 | 154888 |    6 | 158220 | 155709 | 154888 |
|           1985 | 154885 |    7 | 158220 | 155709 | 154885 |
|           1985 | 154459 |    8 | 158220 | 155709 | 154459 |
+----------------+--------+------+--------+--------+--------+
10 rows in set (12.88 sec)

有关窗口功能的其他用例的更多信息,请参阅https://mysqlserverteam.com/mysql-8-0-2-introducing-window-functionshttps://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html#function_row-编号

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

微服务架构实战160讲 -〔杨波〕

深入拆解Java虚拟机 -〔郑雨迪〕

白话法律42讲 -〔周甲徳〕

从0开始做增长 -〔刘津〕

安全攻防技能30讲 -〔何为舟〕

动态规划面试宝典 -〔卢誉声〕

云计算的必修小课 -〔吕蕴偲〕

B端产品经理入门课 -〔董小圣〕

结构会议力 -〔李忠秋〕