跳转至

概述

SQL虽是一门古老的技术,但却并非停滞不前。看下历代SQL标准年表:

SQL-86 SQL-89 SQL-92 SQL:1999 SQL:2003 SQL:2006 SQL:2008 SQL:2011 SQL:2016 SQL:2023

标准这种东西迭代太快也不行,不然就算程序员们的头发能受了,数据库厂家也受不了,所以目前SQL标准这种稳健的迭代是比较正确的做法。

但即使这样,数据库厂家们也没能跟上节奏,至今没有任何一家数据库产品声称完全符合SQL:2016的核心特性。

SQL标准每隔几年就会发布一次,国际标准化组织(ISO)于2023年6月1日正式发布了最新SQL标准,也就是SQL:2023。Peter Eisentraut在这篇文章有详细的示范说明:

https://peter.eisentraut.org/blog/2023/04/04/sql-2023-is-finished-here-is-whats-new

文章对SQL:2023标准进行了分析,并归类了三大类别方面的变化:

  • 当前SQL语言的一些小变更
  • JSON相关的功能特性
  • 新引入图查询的接口

文章围绕这三方面进行了示例说明,同时作者又紧接着分析了PostgreSQL数据库对SQL:2023标准的支持情况,参考文章如下:

https://peter.eisentraut.org/blog/2023/04/18/postgresql-and-sql-2023

PostgreSQL supports most of the major features of SQL:2023. Out of 177 mandatory features required for full Core conformance, PostgreSQL conforms to at least 170. In addition, there is a long list of supported optional features. It might be worth noting that at the time of writing, no current version of any database management system claims full conformance to Core SQL:2023.

UNIQUE null treatment (F292) PostgreSQL 15
ORDER BY in grouped table (F868) ancient
GREATEST and LEAST (T054) ancient
String padding functions (T055) ancient
Multi-character TRIM functions (T056) ancient
Optional string types maximum length (T081) ancient
Enhanced cycle mark values (T133) PostgreSQL 14
ANY_VALUE (T626) PostgreSQL 16
Non-decimal integer literals (T661) PostgreSQL 16
Underscores in numeric literals (T662) PostgreSQL 16
JSON data type (T801) PostgreSQL 9.2/9.4
Enhanced JSON data type (T802) future
String-based JSON (T803) not planned
Hex integer literals in SQL/JSON path language (T840) PostgreSQL 16
SQL/JSON simplified accessor (T860–T864) future
SQL/JSON item methods (T865–T878) future
JSON comparison (T879–T882) PostgreSQL 9.4
Property Graph Queries future

Notes:

  • "ancient" means sometime before PostgreSQL 10. These are features that were already widely available in SQL implementations before they were added to the standard.
  • As of this writing, PostgreSQL 16 is in feature freeze but not released yet. So those entries could in theory change, but it’s pretty unlikely. Conformance information for PostgreSQL 16 relative to SQL:2023 can also be found in the documentation.
  • For the T801 entry, the json type was added in PostgreSQL 9.2 and the jsonb type was added in PostgreSQL 9.4. Most of the JSON functionality in SQL:2016 and SQL:2023 (including features T879 ff.) maps more readily to the jsonb type, so this information should be interpreted that way. Whether and how the json and jsonb types should be consolidated in PostgreSQL is an open question.
  • “not planned” is my opinion that the feature is essentially obsolete (see previous article) and not worth implementing.
  • “future” is my opinion that the feature could be a sensible addition to PostgreSQL, but there is no concrete work in progress.

SQL 增强

UNIQUE 约束中的空值(F292)

这个功能用于定义唯一约束中是否允许多个空值。例如:

CREATE TABLE t1 (
    a int,
    b int,
    c int,
    UNIQUE (a, b, c)
);

INSERT INTO t1 VALUES (1, NULL, NULL);
INSERT INTO t1 VALUES (1, NULL, NULL);  -- ?

问题在于第二个插入语句是否会违法唯一约束。

显然,已有的标准没有明确的定义。为了解决这个问题,新标准增加了一个选项。例如:

CREATE TABLE t2 (
    a int,
    b int,
    c int,
    UNIQUE NULLS DISTINCT (a, b, c)    -- NULLS DISTINCT 选项将多个空值看作不同的数据,因此不会违反唯一约束
);

INSERT INTO t2 VALUES (1, NULL, NULL);
INSERT INTO t2 VALUES (1, NULL, NULL);  -- 正确

CREATE TABLE t3 (
    a int,
    b int,
    c int,
    UNIQUE NULLS NOT DISTINCT (a, b, c)  -- NULLS NOT DISTINCT 选项将多个空值看作相同的数据,因此违反了唯一约束。
);

INSERT INTO t3 VALUES (1, NULL, NULL);  
INSERT INTO t3 VALUES (1, NULL, NULL);  -- 错误

该选项的默认值取决于数据库产品的具体实现,这样可以兼容已有的行为。

分组表中的 ORDER BY(F868)

我们来看一个示例:

CREATE TABLE product (
    product_id int PRIMARY KEY,
    product_name varchar,
    product_code varchar UNIQUE
);

CREATE TABLE product_part (
    product_id int,
    part_id int,
    num int,
    PRIMARY KEY (product_id, part_id)
);

SELECT product.product_id, sum(product_part.num)
FROM product JOIN product_part ON product.product_id = product_part.product_id
GROUP BY product.product_id
ORDER BY product.product_code;

以上示例在大多数 SQL 实现中都能正常工作,但是标准却不允许这种查询方式。也就是说,SQL标准不允许使用没有出现在SELECT列表中的分组表字段(product.product_code)进行排序。新标准则允SQL实现明确支持这种行为。

作为用户,我们并不需要关心这个问题。

GREATEST 和 LEAST 函数(T054)

这个功能增加了两个新的标量函数:GREATEST(最大值)以及 LEAST(最小值)。它们不是聚合函数 MAXMIN,而是返回一组参数中的最大值和最小值。实际上很多数据库已经实现了这两个函数:

SELECT greatest(1, 2);  --> 2
SELECT least(1, 2);     --> 1

SELECT greatest(1, 2, 3);  --> 3
SELECT least(1, 2, 3);     --> 1

字符串填充函数(T055)

这个功能增加了两个新的字符串函数:LPAD(左侧填充)以及 RPAD(右侧填充)。这两个函数同样在很多数据库中已经有了实现:

SELECT lpad(cast(12345.67 as varchar), 12, '*');

-- ****12345.67

默认使用的填充字符为空格。

多字符 TRIM 函数(T056)

已有的单字符截断函数(TRIM({LEADING|TRAILING|BOTH} 'x' FROM val))只能指定要截断的单个字符,新函数则可以指定多个需要截断的字符,而且语法更加简洁。例如:

SELECT ltrim('****12345.67', '*');

可选的字符串类型最大长度(T081)

这个功能允许不指定 VARCHAR 类型的最大长度。在此之前,我们经常会看到为了支持任意长度而指定一个很大的长度值:

CREATE TABLE t1 (
    a VARCHAR(1000),
    b VARCHAR(4000),
    -- ...
);

现在可以简单的指定数据类型:

CREATE TABLE t1 (
    a VARCHAR,
    b VARCHAR,
    -- ...
);

这种情况下,字段的最大长度取决于数据库实现。

增强的递归循环标识(T133)

CYCLE 子句是鲜为人知的一个递归查询功能,可以用于检测循环递归:

WITH RECURSIVE ... (
    SELECT ...
      UNION ALL
    SELECT ...
)
CYCLE id SET is_cycle TO 'Y' DEFAULT 'N' USING path;

这种方式可以基于 id 字段检测是否出现循环递归,并且将 is_cycle 字段设置为指定值。

SQL 标准增加递归循环时,还没有布尔类型,因此使用了一个字符串。如今 SQL 标准提供了布尔类型,因此上面的查询可以改进为:

WITH RECURSIVE ... (
    SELECT ...
      UNION ALL
    SELECT ...
)
CYCLE id SET is_cycle USING path;

ANY_VALUE 函数(T626)

这个新的聚合函数可以从一组数据中返回任意一个非空值。例如:

CREATE TABLE t1 (
    a int,
    b int
);

INSERT INTO t1 VALUES (1, 11), (1, 22), (1, 33);

SELECT a, any_value(b) 
FROM t1 
GROUP BY a;

以上查询可能会返回(1 | 11)、(1 | 22)或者(1 | 33)。

非十进制的整数常量(T661)

这个新功能支持十六进制、八进制以及二进制的整数常量。

SELECT 0xFFFF, 0o755, 0b11001111;

数字常量中的下划线(T662)

这个功能允许在数字常量中使用下划线,提高可读性。

SELECT ... WHERE a > 1_000_000;
UPDATE ... SET x = 0x_FFFF_FFFF ...

JSON 新功能

JSON 数据类型(T801)

新标准增加了 JSON 数据类型。许多数据库产品实际上已经提供了这个类型。

SQL:2016 只是支持了 JSON 数据的操作,数据仍然使用字符串存储。新的功能同时还增加了一些 JSON 相关的函数,例如 JSON_SERIALIZEJSON_SCALAR 以及 IS JSON

增强的 JSON 数据类型(T802)

这个功能可以为 JSON 类型指定额外的选项。具体来说,可以指定一个唯一键检测,例如 JSON('...text...' WITH UNIQUE KEYS)

基于字符串的 JSON(T803)

这个功能代表了 SQL:2016 中的字符串 JSON。数据库提供的 JSON 函数(例如 JSON_OBJECTJSON_OBJECTAGGJSON_TABLE 等)可以选择支持字符串格式的 JSON 数据还是原生的 JSON 数据类型,也可以支持两者。

SQL/JSON 路径中的十六进制整数常量(T840)

这个功能允许在 SQL/JSON 路径语言中使用十六进制整数常量。

虽然不属于这个功能,但是 SQL 标准现在允许以扩展插件的形式支持 JavaScript(ECMAScript),因此不需要每次 ECMAScript 更新时更新 SQL 标准。所以 SQL/JSON 路径现在也可以支持最新 ECMAScript 中的其他数字常量,例如二进制整数和下划线分隔符。

SQL/JSON 简化访问符(T860-T864)

"简化访问符"功能允许使用点号和数组语法访问 JSON 数据中的复合类型和数组类型。例如,假设字段 j 是一个 JSON 类型,包含以下数据:

{"foo": {"bar": [100, 200, 300]}, ...}

我们可以使用简化访问符获取字段中的信息:

SELECT t.j.foo.bar[2], ... 
FROM tbl t ...

SQL/JSON 元素方法(T865-T878)

SQL/JSON 元素方法或者函数可以在 SQL/JSON 路径表达式中使用,对元素值进行处理。SQL:2016 已经定义了一些这类方法,例如 abs()、floor()、size()。新增的方法主要用于数据类型转换:

  • T865: SQL/JSON item method: bigint()
  • T866: SQL/JSON item method: boolean()
  • T867: SQL/JSON item method: date()
  • T868: SQL/JSON item method: decimal()
  • T869: SQL/JSON item method: decimal() with precision and scale
  • T870: SQL/JSON item method: integer()
  • T871: SQL/JSON item method: number()
  • T872: SQL/JSON item method: string()
  • T873: SQL/JSON item method: time()
  • T874: SQL/JSON item method: time_tz()
  • T875: SQL/JSON item method: time precision
  • T876: SQL/JSON item method: timestamp()
  • T877: SQL/JSON item method: timestamp_tz()
  • T878: SQL/JSON item method: timestamp precision

JSON 数据比较功能(T879-T882)

这个功能允许针对 JSON 数据类型的比较、排序以及分组操作。

属性图查询语言

这是 SQL 标准中增加的一个全新部分:Property Graph Queries (SQL/PGQ)。这个新功能支持使用图数据库的方式查询表中的数据。

完整的属性图查询语言比较复杂,以下是一个简单的示例:

CREATE TABLE person (...);
CREATE TABLE company (...);
CREATE TABLE ownerof (...);
CREATE TABLE transaction (...);
CREATE TABLE account (...);

CREATE PROPERTY GRAPH financial_transactions
    VERTEX TABLES (person, company, account)
    EDGE TABLES (ownerof, transaction);

SELECT owner_name,
       SUM(amount) AS total_transacted
FROM financial_transactions GRAPH_TABLE (
  MATCH (p:person WHERE p.name = 'Alice')
        -[:ownerof]-> (:account)
        -[t:transaction]- (:account)
        <-[:ownerof]- (owner:person|company)
  COLUMNS (owner.name AS owner_name, t.amount AS amount)
) AS ft
GROUP BY owner_name;

以上示例中,表之间还需要定义外键,这样属性图定义才能确定它们之间的联系;或者也可以在属性图定义时指定表之间的联系。