15.1.20.8 CREATE TABLE 和生成的列
CREATE TABLE
支持指定生成的列。生成列的值来自于列定义中的表达式。
生成列也支持 NDB
存储引擎。
以下是一个简单的示例,展示了一个存储三角形边长的表格,在 sidea
和 sideb
列中存储边长,并计算 hypotenuse 在 sidec
(其他边长平方和的平方根) :
CREATE TABLE triangle (
sidea DOUBLE,
sideb DOUBLE,
sidec DOUBLE AS (SQRT(sidea * sidea + sideb * sideb))
);
INSERT INTO triangle (sidea, sideb) VALUES(1,1),(3,4),(6,8);
从表格选择结果:
mysql> SELECT * FROM triangle;
+-------+-------+--------------------+
| sidea | sideb | sidec |
+-------+-------+--------------------+
| 1 | 1 | 1.4142135623730951 |
| 3 | 4 | 5 |
| 6 | 8 | 10 |
+-------+-------+--------------------+
使用 triangle
表格的任何应用程序都可以访问 hypotenuse 值,而不需要指定计算它们的表达式。
生成列定义语法如下:
col_name data_type [GENERATED ALWAYS] AS (expr)
[VIRTUAL | STORED] [NOT NULL | NULL]
[UNIQUE [KEY]] [[PRIMARY] KEY]
[COMMENT 'string']
AS (
表示该列是生成的,定义用于计算列值的表达式。 expr
)AS
可以在前面加上 GENERATED ALWAYS
使生成列的性质更加明确。后续讨论允许或禁止在表达式中的构造。
关键字 VIRTUAL
或 STORED
指示列值的存储方式,这对列使用有重要影响:
-
VIRTUAL
: 行值不存储,但是在读取行时立即被评估,任何BEFORE
触发器后。虚拟列不占用存储空间。InnoDB
支持虚拟列的次要索引。见第15.1.20.9节,“次要索引和生成列”。 -
STORED
: 行值在插入或更新时被评估并存储。存储列需要占用存储空间且可以被索引。
如果不指定关键字,默认为VIRTUAL
。
可以在同一个表中混合使用VIRTUAL
和STORED
列。
还可以给出索引、是否可为NULL
或添加注释的其他属性。
生成列表达式必须遵守以下规则。如果表达式包含不允许的构造,会出现错误。
-
允许字面量、确定性内置函数和操作符。函数是确定性的,如果给定同样的数据表,多次调用结果相同,不依赖于连接用户。例如,
CONNECTION_ID()
、CURRENT_USER()
、NOW()
等函数都不是确定性的。 -
存储函数和可加载函数不允许使用。
-
存储过程和函数参数不允许。
-
变量(系统变量、用户定义变量和存储程序局部变量)不允许。
-
子查询不允许。
-
生成列定义可以引用其他在表定义中出现的先前的生成列,也可以引用任意一个非生成的基本列,无论其定义顺序如何。
-
生成列定义中不能使用
AUTO_INCREMENT
属性。 -
自动递增列不能用作生成列定义中的基本列。
-
CREATE TABLE
语句如果表达式计算导致截断或提供错误输入,终止执行并拒绝DDL操作。
如果表达式计算结果的数据类型与声明的列类型不同,将根据MySQL通常的类型转换规则隐式地将其转换到声明的类型。见第14.3节,“表达式求值中的类型转换”。
如果生成列使用TIMESTAMP
数据类型,那么设置explicit_defaults_for_timestamp
将被忽略。在这种情况下,如果该变量禁用,NULL
将不转换为CURRENT_TIMESTAMP
。如果列也声明为NOT NULL
NULL
将被明确地拒绝,返回ER_BAD_NULL_ERROR
。
表达式评估使用评估时间的SQL模式。如果表达式中的任何组件依赖于SQL模式,可能在不同的使用情况下产生不同结果,除非SQL模式在所有使用中保持一致。
CREATE TABLE ... LIKE
,目标表保留原始表的生成列信息。
CREATE TABLE ... SELECT
,目标表不保留所选表中的生成列信息。该SELECT
部分不能将值分配给目标表的生成列。
允许使用生成列进行分区。见表分区。
存储生成列的外键约束不能使用CASCADE
、SET NULL
或SET DEFAULT
作为ON UPDATE
引用操作,也不能使用SET NULL
或SET DEFAULT
作为ON DELETE
引用操作。
存储生成列的基础列上的外键约束不能使用CASCADE
、SET NULL
或SET DEFAULT
作为ON UPDATE
或ON DELETE
引用操作。
外键约束不能引用虚拟生成列。
触发器不能使用NEW.
或使用col_name
OLD.
来引用生成列。col_name
INSERT
、REPLACE
和UPDATE
,如果生成列被插入、替换或更新,唯一允许的值是DEFAULT
。
视图中的生成列被认为是可更新的,因为可以对其进行赋值。然而,如果这样一个列被更新,唯一允许的值也是DEFAULT
。
生成列有多种用途,例如:
-
虚拟生成列可以用来简化和统一查询。一个复杂的条件可以定义为生成列,并从表中多个查询中引用,以确保所有查询都使用相同的条件。
-
存储生成列可以用作复杂条件的物化缓存,避免在飞行计算中浪费时间。
-
生成列可以模拟函数索引:使用生成列定义一个函数表达式,并对其进行索引。这对于无法直接索引的列类型,例如
JSON
列非常有用;见为 JSON 列提供索引的生成列示例,了解详细信息。对于存储生成列,这种方法的缺点是值被存储两次;一次作为生成列的值,另一次在索引中。
-
如果生成列被索引,优化器就能识别匹配列定义的查询表达式,并在执行查询时使用该列的索引,即使查询不直接引用该列名称。了解详细信息,请见第10.3.11节,“生成列索引优化”。
示例:
假设表t1
包含first_name
和last_name
列,应用程序频繁地使用以下表达式构建全名:
SELECT CONCAT(first_name,' ',last_name) AS full_name FROM t1;
避免编写表达式的一种方法是创建一个视图v1
,它简化应用程序,使它们可以直接选择full_name
而不需要使用表达式:
CREATE VIEW v1 AS
SELECT *, CONCAT(first_name,' ',last_name) AS full_name FROM t1;
SELECT full_name FROM v1;
生成列也使应用程序可以直接选择full_name
而不需要定义视图:
CREATE TABLE t1 (
first_name VARCHAR(10),
last_name VARCHAR(10),
full_name VARCHAR(255) AS (CONCAT(first_name,' ',last_name))
);
SELECT full_name FROM t1;