ANSI SQL 及实现调研 (一)

本贴最后更新于 2545 天前,其中的信息可能已经物是人非

背景

  • 经常看到 SQL-92,SQL-03 的词汇,但是不了解其中的差异,比如 SQL-03 比 SQL-92 多了些什么?
  • 知道不同的数据库有不同的方言,但是不了解哪些是 SQL 标准中定义的大家都遵守的。
  • 开发一个类 SQL 语言时,选择支持哪些语法好呢?

本文从 ANSI SQL 切入,来调研一下 ANSI SQL 标准(下面简称为 SQL 标准)的演变,以及不同数据库对 SQL 标准的支持程度。

历程

通过 SQL Wikipedia 上了解到:

由于不同的数据库产商的 SQL 语法不兼容,因此 1986 年开始,ANSI 和 ISO 标准化组织采纳了 SQL 语言标准。并且在随后的年份陆续公布了新的标准。

具体年份和名称如下:

年份 名称 备注
1986 SQL-86 第一版
1989 SQL-89 小修改
1992 SQL-92 大修改
1999 SQL:1999 加入嵌套表(nested table[4]), 加入正则表达式, 递归查询,触发器,过程和控制流,数组类型和一些结构化类型
2003 SQL:2003 加入 XML 相关特征,窗口函数,标准化序列(standardized sequences),自动生成值的列
2006 SQL:2006 支持导入,存储和操作 XML 数据,加入 XQuery
2008 SQL:2008 支持 cursor 定义外的 ORDER BY?, 增加 INSTEAD OF 触发器,增加 TRUNCATE,增加 FETCH
2011 SQL:2011 增加 temporal data,增强 window functions 和 FETCH
2016 SQL:2016 增加列特征匹配(row pattern matching), 多态表函数(polymorphic table functions), JSON

标准公开出来后,不同的产商的实现情况也是不兼容的,即不完全遵守标准。主要是日期类型,时间类型,字符串拼接,NULL 和大小写敏感。

只有 PostgreSQL 和 Mimer SQL 是紧跟标准的。

变化

请找一个充足的时间,打开 Modern SQL: Slides,从头到尾看一遍。

或者打开 Modern SQL in Open Source and Commercial Databases,下载下来看。

这里做一下读书笔记

LATERAL

来自 SQL:1999

依旧拿 PostgreSQL 初体验中的示例数据举例,现在有两张表 citiesweather 如下:

mydb=# select * from cities; name | location ---------------+----------- San Francisco | (-194,53) (1 row)
mydb=# select * from weather; city | temp_lo | temp_hi | prcp | date ---------------+---------+---------+------+------------ San Francisco | 46 | 50 | 0.25 | 1994-11-27 San Francisco | 43 | 57 | 0 | 1994-11-29 Hayward | 37 | 54 | | 1994-11-29 (3 rows)

JOIN LATERAL

现在两张表要做一个 JOIN

在 SQL:1999 之前,只能这么写:

SELECT * FROM cities JOIN ( SELECT * FROM weather ) inline_view ON inline_view.city = cities.name

执行结果

name | location | city | temp_lo | temp_hi | prcp | date ---------------+-----------+---------------+---------+---------+------+------------ San Francisco | (-194,53) | San Francisco | 43 | 57 | 0 | 1994-11-29 San Francisco | (-194,53) | San Francisco | 46 | 50 | 0.25 | 1994-11-27 (2 rows)

在 SQL:1999 之后,可以这样写:

SELECT * FROM cities JOIN lateral( SELECT * FROM weather WHERE weather.city = cities.name ) inline_view ON true

结果

name | location | city | temp_lo | temp_hi | prcp | date ---------------+-----------+---------------+---------+---------+------+------------ San Francisco | (-194,53) | San Francisco | 43 | 57 | 0 | 1994-11-29 San Francisco | (-194,53) | San Francisco | 46 | 50 | 0.25 | 1994-11-27 (2 rows)

注意两点:

  • inline_view 中,引用了外部的 cities
  • JOIN 使用了 join lateral 关键字,而不仅仅是 join

好奇一下内部的执行过程是不是一样的,使用 explain 来看一下

EXPLAIN SELECT * FROM cities JOIN ( SELECT * FROM weather ) inline_view ON inline_view.city = cities.name;

结果

QUERY PLAN ------------------------------------------------------------------------ Hash Join (cost=18.10..55.28 rows=648 width=388) Hash Cond: ((cities.name)::text = (weather.city)::text) -> Seq Scan on cities (cost=0.00..13.60 rows=360 width=194) -> Hash (cost=13.60..13.60 rows=360 width=194) -> Seq Scan on weather (cost=0.00..13.60 rows=360 width=194) (5 rows)
EXPLAIN SELECT * FROM cities JOIN lateral( SELECT * FROM weather WHERE weather.city = cities.name ) inline_view ON true;

结果

QUERY PLAN ------------------------------------------------------------------------ Hash Join (cost=18.10..55.28 rows=648 width=388) Hash Cond: ((cities.name)::text = (weather.city)::text) -> Seq Scan on cities (cost=0.00..13.60 rows=360 width=194) -> Hash (cost=13.60..13.60 rows=360 width=194) -> Seq Scan on weather (cost=0.00..13.60 rows=360 width=194) (5 rows)

这两个的实际执行过程是一样的。

CROSS JOIN LATERAL

使用 cross join lateral 也可以实际同样的效果,同时可以不加末尾的 on true

SELECT * FROM cities CROSS JOIN lateral( SELECT * FROM weather WHERE weather.city = cities.name ) inline_view;

结果

name | location | city | temp_lo | temp_hi | prcp | date ---------------+-----------+---------------+---------+---------+------+------------ San Francisco | (-194,53) | San Francisco | 43 | 57 | 0 | 1994-11-29 San Francisco | (-194,53) | San Francisco | 46 | 50 | 0.25 | 1994-11-27 (2 rows)

内部执行过程仍然是相同的

EXPLAIN SELECT * FROM cities CROSS JOIN lateral( SELECT * FROM weather WHERE weather.city = cities.name ) inline_view;

结果

QUERY PLAN ------------------------------------------------------------------------ Hash Join (cost=18.10..55.28 rows=648 width=388) Hash Cond: ((cities.name)::text = (weather.city)::text) -> Seq Scan on cities (cost=0.00..13.60 rows=360 width=194) -> Hash (cost=13.60..13.60 rows=360 width=194) -> Seq Scan on weather (cost=0.00..13.60 rows=360 width=194) (5 rows)

意义

这种写法有什么意义呢?比原来的写法的优越性在哪里呢?

一方面是 LATERAL 和 table functions 可以联合使用。这个目前是 PostgreSQL 中特有的,本文略过。

LATERAL & LIMIT

另一方面是 可以在 inline view 中加入 LIMIT 操作,比如:

SELECT * FROM cities CROSS JOIN lateral( SELECT * FROM weather WHERE weather.city = cities.name ORDER BY temp_lo LIMIT 1 ) inline_view;

结果

name | location | city | temp_lo | temp_hi | prcp | date ---------------+-----------+---------------+---------+---------+------+------------ San Francisco | (-194,53) | San Francisco | 43 | 57 | 0 | 1994-11-29 (1 row)

如果不使用 LATERAL 的话,是无法实现只取某一列的前 LIMIT 行的。

LATERAL & Multi-Source Top-N

场景:用户找出每个分类下最新的 10 条新闻。

SELECT n.* FROM news n JOIN subscriptions s ON n.topic = s.topic WHERE s.user = ? ORDER BY n.created DESC LIMIT 10

这样会把每个 subscriptions 下的所有的 news 都找出来,但是是没有必要的。

使用 LATERAL 的写法如下:

SELECT n.* FROM subscriptions s JOIN LATERAL( SELECT * FROM news n WHERE n.topic = s.topic ORDER BY n.created DESC LIMIT 10 ) top_news ON true WHERE s.user_id = ? ORDER BY n.created DESC LIMIT 10;

这样可以做到只取每个 news 下的前 10 条新闻.

小结

  • SQL 中的"FOR EACH"
  • 适合与 OUTER JOINS 联用
  • 适合取子查询中的 Top-N
  • 可以与 table functions 作 join

支持的数据库

  • DB2 LUW 9.1+
  • Oracle 12c+
  • PostgreSQL 9.3+
  • SQL Server 2005 部分支持
  • MySQL 不支持
  • SQLite 不支持

WITH (Common Table Expressions)

来自 SQL:1999

WITH 是解决什么问题的呢?

背景

OK,先来看一个在 SQL:1999 之前的场景:

当遇到多层嵌套的 SQL 时,我们会这样写。

SELECT ... FROM (SELECT ... FROM t1 JOIN (SELECT ... FROM ... ) a ON (...) ) b JOIN (SELECT ... FROM ... ) c ON (...)

下面的 SQL 是为了示例而示例:

SELECT * FROM ( SELECT * FROM cities ) a JOIN ( SELECT * FROM weather ) b ON a.name = b.city;

结果

name | location | city | temp_lo | temp_hi | prcp | date ---------------+-----------+---------------+---------+---------+------+------------ San Francisco | (-194,53) | San Francisco | 43 | 57 | 0 | 1994-11-29 San Francisco | (-194,53) | San Francisco | 46 | 50 | 0.25 | 1994-11-27 (2 rows)

当去理解这样的 SQL 语句时,需要先理解最里面的 SELECT,再理解次里面的 SELECT,一层一层的向外看,最后再理解最外层的 SELECT。在 SQL92 时,就是这样来做的。

如果有了 WITH CTEs,就可以用更易理解的方式来写 SQL 了。

语法

一个 CTE

WITH a (c1, c2, c3) AS (SELECT c1, c2, c3 FROM ...) SELECT ...

两个 CTEs

WITH a (c1, c2, c3) AS (SELECT c1, c2, c3 FROM ...), b (c4, ...) AS (SELECT c4, ... FROM t1 JOIN a ON(...) ) SELECT ...
  • 多个 CTE 之间由逗号连接,只有第一个 CTE 前有 WITH 关键字,后面的 CTE 不需要 WITH 关键字。
  • 在第二个 CTE 中可以引用第一个 CTE,即在后面的 CTE 可以引用前面的 CTE。
  • 最后一个 CTE 后没有逗号,表明后面跟着的是查询语句。

这样的话,看 SQL 时就可以从上往下来看了。

将上面的示例重写一下:

WITH a (name, location) AS ( SELECT name, location FROM cities ), b (city, temp_lo, temp_hi, prcp, date) AS ( SELECT city, temp_lo, temp_hi, prcp, date FROM weather ) SELECT * FROM a JOIN b ON a.name = b.city;

结果如下:

name | location | city | temp_lo | temp_hi | prcp | date ---------------+-----------+---------------+---------+---------+------+------------ San Francisco | (-194,53) | San Francisco | 43 | 57 | 0 | 1994-11-29 San Francisco | (-194,53) | San Francisco | 46 | 50 | 0.25 | 1994-11-27 (2 rows)

小结

  • WITH 是 SQL 中的"私有方法 private methods"
  • WITH 视图可以多次被引用
  • WITH 通过定义多个 CTE,来实现嵌套
  • 可以用 SELECT 的地方,就可以用 WITH,如 INSERT INTO tbl WITH ... SELECT

下推(push down)

需要注意的是,在 PostgreSQL 中,WITH 视图更像是一个物化视图,没有把查询中的过滤条件下推到视图中。

在数据量大时,可能会有性能问题。使用时请用 EXPLAIN 来仔细检查一下。

但是在 PostgreSQL 中使用 inline 视图时,会有下推的操作的。

上推(push up)

PostgreSQL 9.1+ 中,INSERT, UPDATE 和 DELETE 中也支持 WITH。

如将指定行从一个表移动到另一个表

WITH deleted_rows AS( DELETE FROM source_tbl RETURNING * ) INSERT INTO destination_tbl SELECT * FROM deleted_rows

支持的数据库

  • DB2 LUW 7+
  • Oracle 9iR2+
  • PostgreSQL 8.4+
  • SQL Server 2005+
  • SQLite 3.8.3+
  • MySQL 8.0+

WITH RECURSIVE (Common Table Expressions)

来自 SQL:1999

从一个例子来看起。

生成 1 到 3 的数字

WITH RECURSIVE cte (n) AS (SELECT 1 UNION ALL SELECT n+1 FROM cte WHERE n < 3) SELECT * FROM cte;

结果

n --- 1 2 3 (3 rows)

这个是怎么实现的呢?

首先其中的 SELECT 中有两项,由 UNION (ALL)分开: 初始项和递归项,见 SELECT 初始项 UNION ALL 递归项

初始时,由初始项产生临时表
随时,临时表做为递归项的输入表,再生产新的临时表
直到新的临时表为空时结束

这样,就形成了一个循环。

来看更多的例子

生成 2 到 4 的数字

WITH RECURSIVE cte (n) AS (SELECT 2 UNION ALL SELECT n + 1 FROM cte WHERE n < 4) SELECT * FROM cte;

结果

n --- 2 3 4 (3 rows)

生成 1,3,5 的数字

WITH RECURSIVE cte (n) AS (SELECT 1 UNION ALL SELECT n + 2 FROM cte WHERE n < 5) SELECT * FROM cte;

结果

n --- 1 3 5 (3 rows)

阶乘

再看一个求阶乘的例子

WITH RECURSIVE factorial(F,n) AS ( SELECT 1 F, 4 n UNION ALL SELECT F*n F, n-1 n FROM factorial WHERE n > 1) SELECT * FROM factorial;

结果

f | n ----+--- 1 | 4 4 | 3 12 | 2 24 | 1 (4 rows)

即 4 的阶乘为 24。

求树的子节点

构造一颗树,每一个节点有自己编号 id, 和其父节点的编号 parent_id

建表

CREATE TABLE t ( id NUMERIC NOT NULL, parent_id NUMERIC, PRIMARY KEY (id) )

构造数据

INSERT INTO t VALUES (1, null); INSERT INTO t VALUES (2, 1); INSERT INTO t VALUES (3, 1); INSERT INTO t VALUES (4, 1); INSERT INTO t VALUES (5, 1); INSERT INTO t VALUES (6, 2); INSERT INTO t VALUES (7, 3); INSERT INTO t VALUES (8, 3); INSERT INTO t VALUES (9, 4); INSERT INTO t VALUES (10, 5); INSERT INTO t VALUES (11, 6); INSERT INTO t VALUES (12, 8); INSERT INTO t VALUES (13, 8); INSERT INTO t VALUES (14, 8); INSERT INTO t VALUES (15, 8); INSERT INTO t VALUES (16, 10);

查看一下表中的数据

mydb=# select * from t; id | parent_id ----+----------- 1 | 2 | 1 3 | 1 4 | 1 5 | 1 6 | 2 7 | 3 8 | 3 9 | 4 10 | 5 11 | 6 12 | 8 13 | 8 14 | 8 15 | 8 16 | 10 (16 rows)

现在来找到节点 3 其及所有子节点。

传统写法

可以使用多个 LEFT JOIN 来写成,同时自己的结果再进行二次处理。

SELECT d0.*, d1.*, d2.* FROM t AS d0 LEFT JOIN t AS d1 ON (d1.parent_id = d0.id) LEFT JOIN t AS d2 ON (d2.parent_id = d1.id) WHERE d0.id = 3

结果如下:

id | parent_id | id | parent_id | id | parent_id ----+-----------+----+-----------+----+----------- 3 | 1 | 8 | 3 | 12 | 8 3 | 1 | 8 | 3 | 13 | 8 3 | 1 | 8 | 3 | 14 | 8 3 | 1 | 8 | 3 | 15 | 8 3 | 1 | 7 | 3 | | (5 rows)

WITH RECURSIVE CTEs 写法

WITH RECURSIVE subtree (id, parent_id) AS ( SELECT id, parent_id FROM t WHERE id = 3 UNION ALL SELECT t.id, t.parent_id FROM subtree LEFT JOIN t ON (t.parent_id = subtree.id) WHERE t.id <= 20 ) SELECT * FROM subtree;

结果

id | parent_id ----+----------- 3 | 1 7 | 3 8 | 3 12 | 8 13 | 8 14 | 8 15 | 8 (7 rows)

小结

  • SQL 中的 while 循环
  • 列生成器
  • 图处理器
  • 可以将数据传递到下一个迭代中
  • 需要有终止条件,否则容易死循环

支持的数据库

  • DB2 LUV 7+
  • Oracle 11.gR2+
  • PostgreSQL 8.4+
  • SQL Server 2005+
  • SQLite 3.8.3+
  • MySQL 不支持

OVER/PARTITION BY

来自 SQL:2003

先从一个场景来说起,如何实现部分收入百分比呢?

构造数据

CREATE TABLE empsalary( depname varchar, empno bigint, salary int, enroll_date date ); INSERT INTO empsalary VALUES('develop',10, 5200, '2007/08/01'); INSERT INTO empsalary VALUES('sales', 1, 5000, '2006/10/01'); INSERT INTO empsalary VALUES('personnel', 5, 3500, '2007/12/10'); INSERT INTO empsalary VALUES('sales', 4, 4800, '2007/08/08'); INSERT INTO empsalary VALUES('sales', 6, 5500, '2007/01/02'); INSERT INTO empsalary VALUES('personnel', 2, 3900, '2006/12/23'); INSERT INTO empsalary VALUES('develop', 7, 4200, '2008/01/01'); INSERT INTO empsalary VALUES('develop', 9, 4500, '2008/01/01'); INSERT INTO empsalary VALUES('sales', 3, 4800, '2007/08/01'); INSERT INTO empsalary VALUES('develop', 8, 6000, '2006/10/01'); INSERT INTO empsalary VALUES('develop', 11, 5200, '2007/08/15');

SQL:2003 之前解法

在 SQL:2003 之前

WITH total_salary_by_department AS (SELECT depname, SUM(salary) total FROM empsalary GROUP BY depname) SELECT ts.depname, empno, salary, 100 * salary/ts.total "% of dep" FROM empsalary JOIN total_salary_by_department ts ON (empsalary.depname = ts.depname) ORDER BY depname, "% of dep" desc;

结果

develop | 8 | 6000 | 23 develop | 10 | 5200 | 20 develop | 11 | 5200 | 20 develop | 9 | 4500 | 17 develop | 7 | 4200 | 16 personnel | 2 | 3900 | 52 personnel | 5 | 3500 | 47 sales | 6 | 5500 | 27 sales | 1 | 5000 | 24 sales | 3 | 4800 | 23 sales | 4 | 4800 | 23

如果只看某一个部门的呢?

WITH total_salary_by_department AS (SELECT depname, SUM(salary) total FROM empsalary GROUP BY depname) SELECT ts.depname, empno, salary, 100 * salary/ts.total "% of dep" FROM empsalary JOIN total_salary_by_department ts ON (empsalary.depname = ts.depname) WHERE empsalary.depname = 'develop' ORDER BY depname, "% of dep" desc;

结果

develop | 8 | 6000 | 23 develop | 10 | 5200 | 20 develop | 11 | 5200 | 20 develop | 9 | 4500 | 17 develop | 7 | 4200 | 16

好吧,这个又是上面提到的 PostgreSQL 的性能问题。

在 SQL:2003 以前,想实现 Aggregation 的话,只有通过 DISTINCT 和 GROUP BY 这两种方法。如果不想选项被合并的话,那就只有 GROUP BY 一种方法。

SQL:2003

在 SQL:2003 中,引入了 OVER(PARTITION BY)语法,来解法此类问题。

如上面的 SQL,可以写为:

SELECT depname, empno, salary, 100 * salary / SUM(salary) OVER(PARTITION BY depname) "% of depname" FROM empsalary ORDER BY depname, "% of depname" DESC;

结果

develop | 8 | 6000 | 23 develop | 10 | 5200 | 20 develop | 11 | 5200 | 20 develop | 9 | 4500 | 17 develop | 7 | 4200 | 16 personnel | 2 | 3900 | 52 personnel | 5 | 3500 | 47 sales | 6 | 5500 | 27 sales | 1 | 5000 | 24 sales | 3 | 4800 | 23 sales | 4 | 4800 | 23

这里先选择了 salary 列,然后选择对 salary 列做 SUM,同时通过 PARTIITON BY 关键字指定了 SUM 的窗口是针对 dep 列。

小结

  • OVER 可以与任何聚合函数搭配作用
  • OVER (PARTITION BY X)可以实现类似 GROUP BY 的效果

OVER/ORDER BY

收支场景:

有一张表 transactions 记录着自己的收支情况。现在需要显示收支的明细和对应的余额。

构造数据

CREATE TABLE transactions( txid int, value numeric ); INSERT INTO transactions VALUES(1, +10); INSERT INTO transactions VALUES(2, +20); INSERT INTO transactions VALUES(3, -10); INSERT INTO transactions VALUES(4, +50); INSERT INTO transactions VALUES(5, -30); INSERT INTO transactions VALUES(6, -20);

查看如下

mydb=# select * from transactions; 1 | 10 2 | 20 3 | -10 4 | 50 5 | -30 6 | -20

SQL:2003 之前

SELECT txid, value, (SELECT SUM(value) FROM transactions tx2 WHERE tx2.txid <= tx1.txid) balance FROM transactions tx1 ORDER BY txid;

结果

1 | 10 | 10 2 | 20 | 30 3 | -10 | 20 4 | 50 | 70 5 | -30 | 40 6 | -20 | 20

SQL:2003

SELECT txid, value, SUM(value) OVER(ORDER BY txid ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) balance FROM transactions tx1 ORDER BY txid;

结果

1 | 10 | 10 2 | 20 | 30 3 | -10 | 20 4 | 50 | 70 5 | -30 | 40 6 | -20 | 20

新的方法

  • ROW_NUMBER

排序方法

  • RANK
  • DENSE_RANK
  • PERCENT_RANK
  • CUME_DIST

支持的数据库

  • DB2 LUW: 7+
  • Oracle: 8i+
  • PostgreSQL: 8.4+
  • SQL Server: 2005+
  • MySQL: 不支持
  • SQLite: 不支持

OVER/LAG

SQL:2008

场景:显示与前一项的差值。和上面的场景正好相反。

SQL:2008 前

显示 ROW_NUMBER

SELECT *, ROW_NUMBER() OVER (ORDER BY txid) rn FROM transactions; 1 | 10 | 1 2 | 20 | 2 3 | -10 | 3 4 | 50 | 4 5 | -30 | 5 6 | -20 | 6

然后使用上面提到的 SQL:2003 的 WITH 方法,来求差值。

WITH numbered_data AS ( SELECT *, ROW_NUMBER() OVER (ORDER BY txid) rn FROM transactions ) SELECT cur.txid, cur.value, cur.value - prev.value FROM numbered_data cur LEFT JOIN numbered_data prev ON (cur.rn - 1 = prev.rn);

结果

1 | 10 | 2 | 20 | 10 3 | -10 | -30 4 | 50 | 60 5 | -30 | -80 6 | -20 | 10

SQL:2008

SELECT *, value - LAG(value) OVER (ORDER BY txid) FROM transactions;

结果:

1 | 10 | 2 | 20 | 10 3 | -10 | -30 4 | 50 | 60 5 | -30 | -80 6 | -20 | 10

其它

同样的还有:

LEAD / LAG FIRST_VALUE / LAST_VALUE MIN_VALUE(col, n) FROM FIRST/LAST RESPECT/IGNORE NULLS

支持的数据库

  • DB2 LUW 9.5+
  • Oracle: 11gR2+
  • PostgreSQL 8.4+
  • SQL Server 2012+
  • MySQL: 不支持

FETCH FIRST

SQL:2008

场景:找到选定行的前有限列

SQL:2008 之前

SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY txid) rn FROM transactions) numbered_data WHERE rn <= 3;

结果

1 | 10 | 1 2 | 20 | 2 3 | -10 | 3

当然也可以使用非标准的语法,如 LIMIT, TOP

SQL:2008 之后

SELECT * FROM transactions ORDER BY txid FETCH FIRST 3 ROWS ONLY;

结果

1 | 10 2 | 20 3 | -10

支持的数据库

  • DB2 LUW 7+
  • MySQL 3.19.3+
  • Oracle 12c
  • PostgreSQL 8.4+
  • SQL Server 2012+
  • SQLite 2.1.0+

OFFSET

SQL: 2011

这个很常见了,但是只是在 SQL:2011 中才引入。

支持的数据库

  • DB2 LUW 9.7+
  • MySQL 4.0.6+
  • Oracle 12c+
  • PostgreSQL 6.5+
  • SQL Server 2012+
  • SQLite 2.1.0+

AS OF

场景:记录数据记录的产生时间

SQL:2011 中

CREATE TABLE t( ..., start_ts TIMESTAMP(9) GENERATED ALWAYS AS ROW START, end_ts TIMESTAMP(9) GENERATED ALWAYS AS ROW END, PERIOD FOR SYSTEM TIME (start_ts, end_ts) ) WITH SYSTEM VERSIONING

写入

INSERT ... (ID, DATA) VALUES (1, 'X')

SQL:2011

其中的 start_tsend_ts 会自动修改,对应用透明。

支持的数据库

  • DB2 LUW 10.1+
  • Oracle 10gR1+
  • MySQL 不支持
  • PostgreSQL 不支持
  • SQL Server 不支持
  • SQLite 不支持

WITHOUT OVERLAPS

场景:每行日志有起止时间,要求约束起止时间不重叠。

SQL:2011

SQL:2011 中引入了 temporal 类型和 bi-temporal 类型,如:

PRIMARY KEY (id, period WITHOUT OVERLAPS)

支持的数据库

  • DB2 LUW 10.1+
  • PostgreSQL 9.2+
  • MySQL 不支持
  • Oracle 不支持
  • SQL Server 不支持
  • SQLite 不支持

参考

  1. One Giant Leap For SQL: MySQL 8.0 Released
  2. SQL Wikipedia
  3. Modern SQL: Slides
  4. Modern SQL: Video
  5. Comparison of different SQL implementations
  6. How to select using with recursive clause
  7. Early History of SQL
  8. What's new in SQL:2011
  9. What's New in SQL:2016
  10. sql 在线美化/格式化/压缩

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • canghai118 via macOS

    楼主写得不错。 这篇文章后后续吗,我看标题里面写了(一)

  • green

    反复观看哦