#### 13.1.20.8 创建表和生成的列

[](<>)

[`创建表`](create-table.html)支持生成列的规范。生成列的值是根据列定义中包含的表达式计算的。

生成的列也支持[`新开发银行`](mysql-cluster.html)存储引擎。

下面的简单示例显示了一个表格，该表格将直角三角形的边长存储在`侧边`和`侧边栏`列，并计算斜边的长度`侧边`（其他边的平方和的平方根）：

```
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 |
+-------+-------+--------------------+
```

任何使用`三角形`table 可以访问斜边值，而无需指定计算它们的表达式。

生成的列定义具有以下语法：

```
col_name data_type [GENERATED ALWAYS] AS (expr)
  [VIRTUAL | STORED] [NOT NULL | NULL]
  [UNIQUE [KEY]] [[PRIMARY] KEY]
  [COMMENT 'string']
```

`作为 （*`表达式`*)`指示生成列并定义用于计算列值的表达式。`作为`可以在前面`始终生成`使生成的列的性质更加明确。表达式中允许或禁止的构造将在后面讨论。

这`虚拟的`要么`已储存`关键字指示如何存储列值，这对列的使用有影响：

-   `虚拟的`: 不存储列值，而是在读取行时评估，紧随任何`前`触发器。虚拟列不占用存储空间。

    `InnoDB`支持虚拟列的二级索引。看[第 13.1.20.9 节，“二级索引和生成的列”](create-table-secondary-indexes.html).

-   `已储存`：在插入或更新行时评估和存储列值。存储列确实需要存储空间并且可以被索引。

    默认是`虚拟的`如果没有指定关键字。

    允许混合`虚拟的`和`已储存`表中的列。

    可以给出其他属性来指示该列是否被索引或可以被索引`空值`，或提供评论。

    生成的列表达式必须遵守以下规则。如果表达式包含不允许的构造，则会发生错误。

-   允许使用文字、确定性内置函数和运算符。如果给定表中的相同数据，则函数是确定性的，多次调用产生相同的结果，与连接的用户无关。非确定性且不符合此定义的函数示例：[`CONNECTION_ID()`](information-functions.html#function_connection-id),[`当前用户（）`](information-functions.html#function_current-user),[`现在（）`](date-and-time-functions.html#function_now).

-   不允许存储函数和可加载函数。

-   不允许使用存储过程和函数参数。

-   不允许使用变量（系统变量、用户定义变量和存储的程序局部变量）。

-   不允许子查询。

-   生成的列定义可以引用其他生成的列，但只能引用表定义中较早出现的列。生成的列定义可以引用表中的任何基本（非生成）列，无论其定义发生在较早还是较晚。

-   这`自动递增`属性不能在生成的列定义中使用。

-   一个`自动递增`列不能用作生成的列定义中的基列。

-   如果表达式求值导致截断或向函数提供不正确的输入，则[`创建表`](create-table.html)语句以错误终止并且 DDL 操作被拒绝。

    如果表达式计算的数据类型与声明的列类型不同，则根据通常的 MySQL 类型转换规则对声明的类型进行隐式强制。看[第 12.3 节，“表达式求值中的类型转换”](type-conversion.html).

    如果生成的列使用[`时间戳`](datetime.html)数据类型，设置为[`显式默认值for_timestamp`](server-system-variables.html#sysvar_explicit_defaults_for_timestamp)被忽略。在这种情况下，如果禁用此变量，则`空值`不转换为[`CURRENT_TIMESTAMP`](date-and-time-functions.html#function_current-timestamp).在 MySQL 8.0.22 及更高版本中，如果该列也被声明为`非空`, 试图插入`空值`被明确拒绝[`ER_BAD_NULL_ERROR`](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_bad_null_error).

笔记

表达式评估使用在评估时有效的 SQL 模式。如果表达式的任何组件依赖于 SQL 模式，则表的不同用途可能会出现不同的结果，除非在所有使用过程中 SQL 模式都相同。

为了[`创建表...喜欢`](create-table-like.html)，目标表保留从原始表生成的列信息。

为了[`创建表...选择`](create-table-select.html)，目标表不保留有关选定表中的列是否为生成列的信息。这[`选择`](select.html)语句的一部分不能将值分配给目标表中生成的列。

允许按生成的列进行分区。看[表分区](create-table.html#create-table-partitioning).

存储生成列上的外键约束不能使用`级联`,`设置为空`， 要么`默认设置`作为`在更新`引用动作，也不能使用`设置为空`要么`默认设置`作为`删除时`参考动作。

存储生成列的基列上的外键约束不能使用`级联`, `设置为空`， 要么`默认设置`作为`在更新`要么`删除时`参考动作。

外键约束不能引用虚拟生成的列。

触发器不能使用`新的。*`col_name`*`或使用`老的。*`col_name`*`引用生成的列。

为了[`插入`](insert.html),[`代替`](replace.html)， 和[`更新`](update.html)，如果生成的列被显式插入、替换或更新，则唯一允许的值是`默认`.

视图中生成的列被认为是可更新的，因为它可以分配给它。但是，如果这样的列被显式更新，则唯一允许的值是`默认`.

生成的列有几个用例，例如：

-   虚拟生成的列可用作简化和统一查询的一种方式。一个复杂的条件可以定义为一个生成的列，并从表上的多个查询中引用，以确保它们都使用完全相同的条件。

-   存储的生成列可以用作复杂条件的物化缓存，这些条件在运行时计算成本很高。

-   生成列可以模拟函数索引：使用生成列定义函数表达式并对其进行索引。这对于处理无法直接索引的类型的列很有用，例如[`JSON`](json.html)列;看[索引生成的列以提供 JSON 列索引](create-table-secondary-indexes.html#json-column-indirect-index), 举个详细的例子。

    对于存储生成的列，这种方法的缺点是值被存储了两次；一次作为生成列的值，一次在索引中。

-   如果生成的列被索引，优化器会识别与列定义匹配的查询表达式，并在查询执行期间适当地使用列中的索引，即使查询不直接按名称引用列。有关详细信息，请参阅[第 8.3.11 节，“生成的列索引的优化器使用”](generated-column-index-optimizations.html).

    例子：

    假设一个表`t1`包含`名`和`姓`列，并且应用程序经常使用如下表达式构造全名：


```
SELECT CONCAT(first_name,' ',last_name) AS full_name FROM t1;
```

避免写出表达式的一种方法是创建一个视图`v1`在`t1`，通过使他们能够选择简化应用程序`全名`直接不使用表达式：

```
CREATE VIEW v1 AS
SELECT *, CONCAT(first_name,' ',last_name) AS full_name FROM t1;

SELECT full_name FROM v1;
```

生成的列还允许应用程序选择`全名`直接不需要定义视图：

```
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;
```
