使用序列¶
序列用于在会话和语句(包括并发语句)之间生成唯一编号。它们可用于为主键或任何需要唯一值的列生成值。
重要
Snowflake 无法保证生成的序列号没有间隙。生成的序列号不一定是连续的。
本主题内容:
序列语义¶
Snowflake 序列目前使用以下语义:
只要序列间隔的符号不改变(如改变步长),序列生成的所有值都是全局唯一的。并发查询绝不会观察到相同的值,而单个查询中的值始终是不同的。
将序列间隔从正值改为负值(例如,从
1
到-1
),可能会导致重复,反之亦然。例如,如果第一次查询返回序列值1
、2
和3
,然后将间隔从1
更改为-1
,则接下来生成的几个值中会包括之前生成的2
和1
。Snowflake 可能会在当前序列号使用完毕后立即计算序列的下一个值,而不是等到请求下一个序列号时才进行计算。
这样做的结果是,
ALTER SEQUENCE ... SET INCREMENT ...
命令可能不会影响使用该序列的下一个操作。有关示例,请参阅 了解逆转序列方向的影响。每个生成的序列值都会根据序列间隔(也称为“步长”)额外保留值。保留值的范围从序列值到
<value> + (sign(<step>) * abs(<step>)) - (sign(<step>) * 1)
(含)。
因此,如果生成的值为
100
:当步长为
2
时,则保留值100
和101
。当步长为
10
时,则保留值100
至109
。当步长为
-5
时,则保留值96
至100
。
只要 从不 修改步长/间隔,序列就不会生成保留值。
如果 满足以下条件,则序列生成的值大于前一条语句生成的最大值(如果步长为负值,则小于前一条语句生成的最小值):
序列没有 NOORDER 属性。
NOORDER 指定不保证值按递增顺序排列。
例如,如果序列中有 START 1 INCREMENT 2,生成的值可能是
1
、3
、101
、5
、103
等。NOORDER 可以在需要同时执行多个插入操作时提高性能(例如,在多个客户端执行多个 INSERT 语句时)。
在提交当前语句之前,已完成前一条语句并收到确认。
如果间隔的符号发生更改(正变为负或负变为正),则此行为 不 成立。
系统无法保证序列中的值是连续的(无间隙),也无法保证序列值是按照特定顺序分配的。事实上,要将序列中的值按指定顺序分配给行,只能使用单行语句(这仍然不能保证值之间无间隙)。
序列值可以表示 64 位二进制整数(-2^63
至 2^63 - 1
)。如果序列中下一个值的内部表示超出此范围(在任一方向上),则会出现错误,导致查询失败。请注意,这可能会导致序列值 丢失。
在这种情况下,必须使用较小(量级)的增量值或创建具有较小起始值的新序列。由于值之间可能会出现间隙,即使返回的序列值都在允许范围内,下一个值的内部表示也可能会超出允许范围。Snowflake 不提供关于如何避免此错误的明确保证,但 Snowflake 支持正确提供唯一值的序列对象。使用起始值 1
和增量值 1
创建的序列对象极不可能超出允许的序列值范围。
引用序列¶
不支持 currval
¶
许多数据库都提供 currval
序列参考;然而,Snowflake 并未提供这一参考。在其他系统中,currval
通常用于创建表之间的主外键关系,第一条语句使用序列将单个行插入事实表以创建键。后续语句将行插入维度表中,该表使用 currval
引用事实表的键。
这种模式与 Snowflake 的最佳实践相悖 – 批量查询应该优先于小型单行查询。对于同样的任务,在嵌套子查询中使用多表 INSERT 和序列引用可以更好地实现。有关详细示例,请参阅 引入和规范化非规范数据 (本主题内容)。
作为表达式的序列¶
序列可以在查询中以 seq_name.NEXTVAL
形式的表达式访问。每次出现序列都会生成一组非重复值。这与许多其他数据库提供的方式不同,在其他数据库中,多次引用序列的 NEXTVAL
为每行返回相同的值。
例如,以下查询为列 a
和 b
返回非重复值:
CREATE OR REPLACE SEQUENCE seq1; SELECT seq1.NEXTVAL a, seq1.NEXTVAL b FROM DUAL;
若要返回具有相同生成序列值的两列,请使用嵌套子查询和视图:
CREATE OR REPLACE SEQUENCE seq1; SELECT seqRef.a a, seqRef.a b FROM (SELECT seq1.NEXTVAL a FROM DUAL) seqRef;
嵌套子查询生成的不同序列值与子查询返回的行一样多(因此,具有多个联接的查询块中的序列引用不是引用联接对象,而是引用查询块的输出)。如果随后过滤掉了相关行,则可能无法观察到这些生成的值;如果多次引用序列的列或内联视图,则可能会观察到两次值(如上面的示例所示)。
备注
对于多表插入,可以在 VALUES 子句和 SELECT 输入中提供插入值:
引用从输入 SELECT 中别名的序列值的 VALUES 子句,接收相同的值。
包含对序列
NEXTVAL
的直接引用的 VALUES 子句可接收不同的值。
相反,Oracle 则将序列引用限制为仅限 VALUES 子句。
作为表函数的序列¶
带有序列引用的嵌套查询通常很难理解且很冗长,任何共享引用(其中一行的两列应该接收相同的序列值)都需要额外的查询嵌套级别。为了简化嵌套查询语法,Snowflake 提供了一个额外的方法来使用表函数 GETNEXTVAL 生成序列,如下例所示:
CREATE OR REPLACE SEQUENCE seq1; CREATE OR REPLACE TABLE foo (n NUMBER); INSERT INTO foo VALUES (100), (101), (102); SELECT n, s.nextval FROM foo, TABLE(GETNEXTVAL(seq1)) s;
GETNEXTVAL 是一个特殊的单行表函数,它可生成一个唯一值(并将此值连接到 SELECT 语句中的其他对象)。对 GETNEXTVAL 的调用必须具有别名;否则,无法引用生成的值。通过访问此别名,多个列可以引用生成的值。GETNEXTVAL 别名还包含一个名为 NEXTVAL
的属性。
此外,当许多表连接在一起时, GETNEXTVAL 表函数还允许对序列生成进行精确控制。FROM 子句中对象的顺序决定了值生成的位置。序列值是在 FROM 子句中 GETNEXTVAL 之前列出的所有对象之间的连接结果上生成的。然后将生成的行连接到右侧的对象。在 GETNEXTVAL 和 FROM 子句中的所有其他对象之间存在隐式横向依赖关系。联接不能围绕 GETNEXTVAL 重新排序。这在 SQL 中是一个例外,因为对象的顺序通常不会影响查询语义。
请参考表 t1
、 t2
、 t3
和 t4
中的以下示例:
CREATE OR REPLACE SEQUENCE seq1; SELECT t1.*, t2.*, t3.*, t4.*, s.NEXTVAL FROM t1, t2, TABLE(GETNEXTVAL(seq1)) s, t3, t4;
此查询将 t1
连接到 t2
,生成结果的唯一值,然后将结果关系连接到 t3
和 t4
。未指定后序列关系、 t3
和 t4
之间的联接顺序,是因为内部联接是关联的。
备注
这些语义可能很棘手。我们建议在可能和适当的情况下,在 FROM 子句末尾使用 GETNEXTVAL,以避免混淆。
使用序列创建默认列值¶
序列可以在表中使用,以生成表列的主键。下面的工具提供了一种执行此操作的简单方法。
列默认表达式¶
列默认表达式可以是序列引用。在 insert 语句中省略该列,或者在 insert 或 update 语句中将该值设置为 DEFAULT,都会为该行生成一个新的序列值。
例如:
CREATE OR REPLACE SEQUENCE seq1; CREATE OR REPLACE TABLE foo (k NUMBER DEFAULT seq1.NEXTVAL, v NUMBER); -- insert rows with unique keys (generated by seq1) and explicit values INSERT INTO foo (v) VALUES (100); INSERT INTO foo VALUES (DEFAULT, 101); -- insert rows with unique keys (generated by seq1) and reused values. -- new keys are distinct from preexisting keys. INSERT INTO foo (v) SELECT v FROM foo; -- insert row with explicit values for both columns INSERT INTO foo VALUES (1000, 1001); SELECT * FROM foo; +------+------+ | K | V | |------+------| | 1 | 100 | | 2 | 101 | | 3 | 100 | | 4 | 101 | | 1000 | 1001 | +------+------+
使用序列作为列默认值的优点是,序列可以在其他位置引用,甚至可以作为多个列和多个表的默认值。如果将序列命名为列的默认表达式,随后又被删除,那么任何使用默认值插入/更新表的尝试都会导致错误,系统也会提示无法找到标识符。
引入和规范化非规范数据¶
可考虑使用具有 people
和 contact
两个表的架构:
people
表包括:主键唯一标识符:
id
两个字符串列:
firstName
和lastName
contact
表包括:主键唯一标识符:
id
将此联系人条目链接到人员的外键:
p_id
两个字符串列:
c_type
:联系人的类型(例如“电子邮件地址”或“电话”)。data
:实际联系信息。
这种格式的数据在引入或处理半结构化数据时,通常会被非规范化。
此示例说明了引入 JSON 数据、对其进行去规范化以提取所需的数据,以及在将数据插入表时对其进行规范化。同时,在保持表中各行之间的预期关系的同时,必须在行上创建唯一标识符。我们可以使用序列来实现这一点。
首先,我们设置示例中使用的表和序列:
-- primary data tables CREATE OR REPLACE TABLE people (id number, firstName string, lastName string); CREATE OR REPLACE TABLE contact (id number, p_id number, c_type string, data string); -- sequences to produce primary keys on our data tables CREATE OR REPLACE SEQUENCE people_seq; CREATE OR REPLACE SEQUENCE contact_seq; -- staging table for json CREATE OR REPLACE TABLE input (json variant);
下一步,我们从表
json
中插入数据:INSERT INTO input SELECT parse_json( '[ { firstName : \'John\', lastName : \'Doe\', contacts : [ { contactType : \'phone\', contactData : \'1234567890\', } , { contactType : \'email\', contactData : \'jdoe@acme.com\', } ] } , { firstName : \'Mister\', lastName : \'Smith\', contacts : [ { contactType : \'phone\', contactData : \'0987654321\', } , { contactType : \'email\', contactData : \'msmith@acme.com\', } ] } , { firstName : \'George\', lastName : \'Washington\', contacts : [ { contactType : \'phone\', contactData : \'1231231234\', } , { contactType : \'email\', contactData : \'gwashington@acme.com\', } ] } ]' );
然后,我们对 JSON 进行解析和扁平化处理,为每个人员和联系人条目生成唯一标识符,并在插入数据的同时保留人员和联系人条目之间的关系:
INSERT ALL WHEN 1=1 THEN INTO contact VALUES (c_next, p_next, contact_value:contactType, contact_value:contactData) WHEN contact_index = 0 THEN INTO people VALUES (p_next, person_value:firstName, person_value:lastName) SELECT * FROM ( SELECT f1.value person_value, f2.value contact_value, f2.index contact_index, p_seq.NEXTVAL p_next, c_seq.NEXTVAL c_next FROM input, LATERAL FLATTEN(input.json) f1, TABLE(GETNEXTVAL(people_seq)) p_seq, LATERAL FLATTEN(f1.value:contacts) f2, table(GETNEXTVAL(contact_seq)) c_seq );
这将生成以下数据(唯一的 IDs 可能会更改):
SELECT * FROM people; +----+-----------+------------+ | ID | FIRSTNAME | LASTNAME | |----+-----------+------------| | 1 | John | Doe | | 2 | Mister | Smith | | 3 | George | Washington | +----+-----------+------------+ SELECT * FROM contact; +----+------+--------+----------------------+ | ID | P_ID | C_TYPE | DATA | |----+------+--------+----------------------| | 1 | 1 | phone | 1234567890 | | 2 | 1 | email | jdoe@acme.com | | 3 | 2 | phone | 0987654321 | | 4 | 2 | email | msmith@acme.com | | 5 | 3 | phone | 1231231234 | | 6 | 3 | email | gwashington@acme.com | +----+------+--------+----------------------+
可以看到,行与行之间是关联的,并且可以在 people.id
和 contact.p_id
之间连接。
如果添加了其他数据,新行将继续接收唯一的 IDs。例如:
TRUNCATE TABLE input; INSERT INTO input SELECT PARSE_JSON( '[ { firstName : \'Genghis\', lastName : \'Khan\', contacts : [ { contactType : \'phone\', contactData : \'1111111111\', } , { contactType : \'email\', contactData : \'gkahn@acme.com\', } ] } , { firstName : \'Julius\', lastName : \'Caesar\', contacts : [ { contactType : \'phone\', contactData : \'2222222222\', } , { contactType : \'email\', contactData : \'gcaesar@acme.com\', } ] } ]' ); INSERT ALL WHEN 1=1 THEN INTO contact VALUES (c_next, p_next, contact_value:contactType, contact_value:contactData) WHEN contact_index = 0 THEN INTO people VALUES (p_next, person_value:firstName, person_value:lastName) SELECT * FROM ( SELECT f1.value person_value, f2.value contact_value, f2.index contact_index, p_seq.NEXTVAL p_next, c_seq.NEXTVAL c_next FROM input, LATERAL FLATTEN(input.json) f1, table(GETNEXTVAL(people_seq)) p_seq, LATERAL FLATTEN(f1.value:contacts) f2, table(GETNEXTVAL(contact_seq)) c_seq ); SELECT * FROM people; +----+-----------+------------+ | ID | FIRSTNAME | LASTNAME | |----+-----------+------------| | 4 | Genghis | Khan | | 5 | Julius | Caesar | | 1 | John | Doe | | 2 | Mister | Smith | | 3 | George | Washington | +----+-----------+------------+ SELECT * FROM contact; +----+------+--------+----------------------+ | ID | P_ID | C_TYPE | DATA | |----+------+--------+----------------------| | 1 | 1 | phone | 1234567890 | | 2 | 1 | email | jdoe@acme.com | | 3 | 2 | phone | 0987654321 | | 4 | 2 | email | msmith@acme.com | | 5 | 3 | phone | 1231231234 | | 6 | 3 | email | gwashington@acme.com | | 7 | 4 | phone | 1111111111 | | 8 | 4 | email | gkahn@acme.com | | 9 | 5 | phone | 2222222222 | | 10 | 5 | email | gcaesar@acme.com | +----+------+--------+----------------------+
更改序列¶
了解逆转序列方向的影响¶
下面的示例显示了逆转序列方向时发生的情况。
这也表明,由于预先计算了序列值, ALTER SEQUENCE 命令似乎只有在执行 ALTER SEQUENCE 命令后 第二次 使用该序列后才会生效。
创建序列并将其用作表中某一列的默认值:
CREATE OR REPLACE SEQUENCE test_sequence_wraparound_low
START = 1
INCREMENT = 1
;
CREATE or replace TABLE test_seq_wrap_low (
i int,
j int default test_sequence_wraparound_low.nextval
);
加载表:
INSERT INTO test_seq_wrap_low (i) VALUES
(1),
(2),
(3);
显示 j
列中的序列值:
SELECT * FROM test_seq_wrap_low ORDER BY i;
+---+---+
| I | J |
|---+---|
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
+---+---+
改变序列的增量(步长):
ALTER SEQUENCE test_sequence_wraparound_low SET INCREMENT = -4;
再插入两行:
INSERT INTO test_seq_wrap_low (i) VALUES
(4),
(5);
显示序列值。请注意,在 ALTER SEQUENCE 之后插入的首行值是 4
,而不是 -1
。在 ALTER SEQUENCE 之后插入的第二行确实考虑了新的步长。
SELECT * FROM test_seq_wrap_low ORDER BY i;
+---+---+
| I | J |
|---+---|
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 0 |
+---+---+