生成的列
自 v3.1 起,StarRocks 支持生成列。生成列可用于加速具有复杂表达式的查询。此功能支持预计算和存储表达式的结果以及查询重写,从而显著加速具有相同复杂表达式的查询。
您可以定义一个或多个生成列,以在创建表时存储表达式的结果。 这样,在执行包含表达式的查询时,CBO 会重写查询以直接从生成的列读取数据。 或者,您可以直接查询生成列中的数据。
还建议评估生成列对加载性能的影响,因为计算表达式需要一些时间。 此外,建议在创建表时创建生成列,而不是在创建表后添加或修改它们。 因为在创建表后添加或修改生成列既耗时又昂贵。
但是,请注意,当数据加载到具有生成列的表中时,时间和开销可能会增加,因为 StarRocks 需要执行额外的计算以基于表达式计算结果,并将结果写入生成列中。
从 v3.5.0 开始,StarRocks 共享数据集群支持生成列。
基本操作
创建生成列
语法
<col_name> <data_type> [NULL] AS <expr> [COMMENT 'string']
在创建表时创建生成列
创建一个名为 test_tbl1
的表,其中包含五列,其中列 newcol1
和 newcol2
是生成列,它们的值通过使用指定的表达式并分别引用常规列 data_array
和 data_json
的值来计算。
CREATE TABLE test_tbl1
(
id INT NOT NULL,
data_array ARRAY<int> NOT NULL,
data_json JSON NOT NULL,
newcol1 DOUBLE AS array_avg(data_array),
newcol2 String AS json_string(json_query(data_json, "$.a"))
)
PRIMARY KEY (id)
DISTRIBUTED BY HASH(id);
注意:
- 生成列必须在常规列之后定义。
- 聚合函数不能用于生成列的表达式中。
- 生成列的表达式不能引用其他生成列或自增列,但表达式可以引用多个常规列。
- 生成列的数据类型必须与生成列表达式生成的结果的数据类型匹配。
- 不能在 Aggregate 表上创建生成列。
在创建表后添加生成列
在大多数情况下,查询期间经常使用的表达式是在创建表后确定的,因此生成列通常是在创建表后添加的。 出于性能考虑,优化了 StarRocks 在创建表后添加生成列的底层逻辑。 这样,在添加生成列时,StarRocks 不需要重写所有数据。 相反,StarRocks 只需要写入新添加的生成列的数据,并将该数据与现有的物理数据文件相关联,这大大提高了在创建表后添加生成列的效率。
-
创建一个名为
test_tbl2
的表,其中包含三个常规列id
、data_array
和data_json
。 将数据行插入表中。-- Create a table.
CREATE TABLE test_tbl2
(
id INT NOT NULL,
data_array ARRAY<int> NOT NULL,
data_json JSON NOT NULL
)
PRIMARY KEY (id)
DISTRIBUTED BY HASH(id);
-- Insert a data row.
INSERT INTO test_tbl2 VALUES (1, [1,2], parse_json('{"a" : 1, "b" : 2}'));
-- Query the table.
MySQL [example_db]> select * from test_tbl2;
+------+------------+------------------+
| id | data_array | data_json |
+------+------------+------------------+
| 1 | [1,2] | {"a": 1, "b": 2} |
+------+------------+------------------+
1 row in set (0.04 sec) -
执行 ALTER TABLE ... ADD COLUMN ... 以添加生成列
newcol1
和newcol2
,它们是通过基于常规列data_array
和data_json
的值评估表达式来创建的。ALTER TABLE test_tbl2
ADD COLUMN newcol1 DOUBLE AS array_avg(data_array);
ALTER TABLE test_tbl2
ADD COLUMN newcol2 String AS json_string(json_query(data_json, "$.a"));注意:
- 不支持向 Aggregate 表添加生成列。
- 常规列需要在生成列之前定义。 当您使用 ALTER TABLE ... ADD COLUMN ... 语句添加常规列而不指定新常规列的位置时,系统会自动将其放置在生成列之前。 此外,您不能使用 AFTER 显式地将常规列放置在生成列之后。
-
查询表数据。
MySQL [example_db]> SELECT * FROM test_tbl2;
+------+------------+------------------+---------+---------+
| id | data_array | data_json | newcol1 | newcol2 |
+------+------------+------------------+---------+---------+
| 1 | [1,2] | {"a": 1, "b": 2} | 1.5 | 1 |
+------+------------+------------------+---------+---------+
1 row in set (0.04 sec)结果表明,生成列
newcol1
和newcol2
已添加到表中,并且 StarRocks 会根据表达式自动计算它们的值。
将数据加载到生成列中
在数据加载期间,StarRocks 会根据表达式自动计算生成列的值。 您不能指定生成列的值。 以下示例使用INSERT INTO语句加载数据
-
使用 INSERT INTO 将记录插入到
test_tbl1
表中。 请注意,您不能在VALUES ()
子句中指定生成列的值。INSERT INTO test_tbl1 (id, data_array, data_json)
VALUES (1, [1,2], parse_json('{"a" : 1, "b" : 2}')); -
查询表数据。
MySQL [example_db]> SELECT * FROM test_tbl1;
+------+------------+------------------+---------+---------+
| id | data_array | data_json | newcol1 | newcol2 |
+------+------------+------------------+---------+---------+
| 1 | [1,2] | {"a": 1, "b": 2} | 1.5 | 1 |
+------+------------+------------------+---------+---------+
1 row in set (0.01 sec)结果表明,StarRocks 会根据表达式自动计算生成列
newcol1
和newcol2
的值。注意:
如果在数据加载期间指定生成列的值,则会返回以下错误
MySQL [example_db]> INSERT INTO test_tbl1 (id, data_array, data_json, newcol1, newcol2)
VALUES (2, [3,4], parse_json('{"a" : 3, "b" : 4}'), 3.5, "3");
ERROR 1064 (HY000): Getting analyzing error. Detail message: materialized column 'newcol1' can not be specified.
MySQL [example_db]> INSERT INTO test_tbl1 VALUES (2, [3,4], parse_json('{"a" : 3, "b" : 4}'), 3.5, "3");
ERROR 1064 (HY000): Getting analyzing error. Detail message: Column count doesn't match value count.
修改生成列
修改生成列时,StarRocks 需要重写所有数据,这既耗时又耗费资源。 如果不可避免地要使用 ALTER TABLE 来修改生成列,建议提前评估所涉及的成本和时间。
您可以修改生成列的数据类型和表达式。
-
创建一个表
test_tbl3
,其中包含五列,其中列newcol1
和newcol2
是生成列,它们的值通过使用指定的表达式并分别引用常规列data_array
和data_json
的值来计算。 将数据行插入表中。-- Create a table.
MySQL [example_db]> CREATE TABLE test_tbl3
(
id INT NOT NULL,
data_array ARRAY<int> NOT NULL,
data_json JSON NOT NULL,
-- The data types and expressions of generated columns are specified as follows:
newcol1 DOUBLE AS array_avg(data_array),
newcol2 String AS json_string(json_query(data_json, "$.a"))
)
PRIMARY KEY (id)
DISTRIBUTED BY HASH(id);
-- Insert a data row.
INSERT INTO test_tbl3 (id, data_array, data_json)
VALUES (1, [1,2], parse_json('{"a" : 1, "b" : 2}'));
-- Query the table.
MySQL [example_db]> select * from test_tbl3;
+------+------------+------------------+---------+---------+
| id | data_array | data_json | newcol1 | newcol2 |
+------+------------+------------------+---------+---------+
| 1 | [1,2] | {"a": 1, "b": 2} | 1.5 | 1 |
+------+------------+------------------+---------+---------+
1 row in set (0.01 sec) -
修改了生成的列
newcol1
和newcol2
-
将生成的列
newcol1
的数据类型更改为ARRAY<INT>
,并将其表达式更改为data_array
。ALTER TABLE test_tbl3
MODIFY COLUMN newcol1 ARRAY<INT> AS data_array; -
修改生成列
newcol2
的表达式,以从常规列data_json
中提取字段b
的值。ALTER TABLE test_tbl3
MODIFY COLUMN newcol2 String AS json_string(json_query(data_json, "$.b"));
-
-
查看修改后的架构和表中的数据。
-
查看修改后的架构。
MySQL [example_db]> show create table test_tbl3\G
**** 1. row ****
Table: test_tbl3
Create Table: CREATE TABLE test_tbl3 (
id int(11) NOT NULL COMMENT "",
data_array array<int(11)> NOT NULL COMMENT "",
data_json json NOT NULL COMMENT "",
-- After modification, the data types and expressions of generated columns are as follows:
newcol1 array<int(11)> NULL AS example_db.test_tbl3.data_array COMMENT "",
newcol2 varchar(65533) NULL AS json_string(json_query(example_db.test_tbl3.data_json, '$.b')) COMMENT ""
) ENGINE=OLAP
PRIMARY KEY(id)
DISTRIBUTED BY HASH(id)
PROPERTIES (...);
1 row in set (0.00 sec) -
修改后查询表数据。 结果表明,StarRocks 会根据修改后的表达式重新计算生成列
newcol1
和newcol2
的值。MySQL [example_db]> select * from test_tbl3;
+------+------------+------------------+---------+---------+
| id | data_array | data_json | newcol1 | newcol2 |
+------+------------+------------------+---------+---------+
| 1 | [1,2] | {"a": 1, "b": 2} | [1,2] | 2 |
+------+------------+------------------+---------+---------+
1 row in set (0.01 sec)
-
删除生成列
从表 test_tbl3
中删除列 newcol1
ALTER TABLE test_tbl3 DROP COLUMN newcol1;
注意:
如果生成列的表达式中引用了常规列,则无法直接删除或修改该常规列。 相反,您需要先删除生成列,然后再删除或修改常规列。
查询重写
如果查询中的表达式与生成列的表达式匹配,则优化器会自动重写查询以直接读取生成列的值。
-
假设您创建了一个表
test_tbl4
,其架构如下CREATE TABLE test_tbl4
(
id INT NOT NULL,
data_array ARRAY<int> NOT NULL,
data_json JSON NOT NULL,
newcol1 DOUBLE AS array_avg(data_array),
newcol2 String AS json_string(json_query(data_json, "$.a"))
)
PRIMARY KEY (id) DISTRIBUTED BY HASH(id); -
如果您使用
SELECT array_avg(data_array), json_string(json_query(data_json, "$.a")) FROM test_tbl4;
语句查询表test_tbl4
中的数据,则查询仅涉及常规列data_array
和data_json
。 但是,查询中的表达式与生成列newcol1
和newcol2
的表达式匹配。 在这种情况下,执行计划表明 CBO 会自动重写查询以读取生成列newcol1
和newcol2
的值。MySQL [example_db]> EXPLAIN SELECT array_avg(data_array), json_string(json_query(data_json, "$.a")) FROM test_tbl4;
+---------------------------------------+
| Explain String |
+---------------------------------------+
| PLAN FRAGMENT 0 |
| OUTPUT EXPRS:4: newcol1 | 5: newcol2 | -- The query is rewritten to read data from the generated columns newcol1 and newcol2 are accessed.
| PARTITION: RANDOM |
| |
| RESULT SINK |
| |
| 0:OlapScanNode |
| TABLE: test_tbl4 |
| PREAGGREGATION: ON |
| partitions=0/1 |
| rollup: test_tbl4 |
| tabletRatio=0/0 |
| tabletList= |
| cardinality=1 |
| avgRowSize=2.0 |
+---------------------------------------+
15 rows in set (0.00 sec)
部分更新和生成列
要在主键表上执行部分更新,您必须在 columns
参数中指定生成列引用的所有常规列。 以下示例使用 Stream Load 执行部分更新。
-
创建一个表
test_tbl5
,其中包含五列,其中列newcol1
和newcol2
是生成列,它们的值通过使用指定的表达式并分别引用常规列data_array
和data_json
的值来计算。 将数据行插入表中。-- Create a table.
CREATE TABLE test_tbl5
(
id INT NOT NULL,
data_array ARRAY<int> NOT NULL,
data_json JSON NULL,
newcol1 DOUBLE AS array_avg(data_array),
newcol2 String AS json_string(json_query(data_json, "$.a"))
)
PRIMARY KEY (id)
DISTRIBUTED BY HASH(id);
-- Insert into a data row.
INSERT INTO test_tbl5 (id, data_array, data_json)
VALUES (1, [1,2], parse_json('{"a" : 1, "b" : 2}'));
-- Query the table.
MySQL [example_db]> select * from test_tbl5;
+------+------------+------------------+---------+---------+
| id | data_array | data_json | newcol1 | newcol2 |
+------+------------+------------------+---------+---------+
| 1 | [1,2] | {"a": 1, "b": 2} | 1.5 | 1 |
+------+------------+------------------+---------+---------+
1 row in set (0.01 sec) -
准备一个 CSV 文件
my_data1.csv
以更新test_tbl5
表中的某些列。1|[3,4]|{"a": 3, "b": 4}
2|[3,4]|{"a": 3, "b": 4} -
使用Stream Load和
my_data1.csv
文件来更新test_tbl5
表的某些列。 您需要设置partial_update:true
并在columns
参数中指定生成列引用的所有常规列。curl --location-trusted -u <username>:<password> -H "label:1" \
-H "column_separator:|" \
-H "partial_update:true" \
-H "columns:id,data_array,data_json" \
-T my_data1.csv -XPUT \
http://<fe_host>:<fe_http_port>/api/example_db/test_tbl5/_stream_load -
查询表数据。
[example_db]> select * from test_tbl5;
+------+------------+------------------+---------+---------+
| id | data_array | data_json | newcol1 | newcol2 |
+------+------------+------------------+---------+---------+
| 1 | [3,4] | {"a": 3, "b": 4} | 3.5 | 3 |
| 2 | [3,4] | {"a": 3, "b": 4} | 3.5 | 3 |
+------+------------+------------------+---------+---------+
2 rows in set (0.01 sec)
如果您执行部分更新而不指定生成列引用的所有常规列,则 Stream Load 会返回错误。
-
准备一个 CSV 文件
my_data2.csv
。1|[3,4]
2|[3,4] -
当使用 Stream Load 和
my_data2.csv
文件执行部分列更新时,如果在my_data2.csv
中未提供data_json
列的值,并且 Stream Load 作业中的columns
参数不包含data_json
列,即使data_json
列允许空值,Stream Load 也会返回错误,因为列data_json
被生成列newcol2
引用。