使用序列

序列用于在会话和语句(包括并发语句)之间生成唯一编号。它们可用于为主键或任何需要唯一值的列生成值。

重要

Snowflake 无法保证生成的序列号没有间隙。生成的序列号不一定是连续的。

本主题内容:

序列语义

Snowflake 序列目前使用以下语义:

  • 只要序列间隔的符号不改变(如改变步长),序列生成的所有值都是全局唯一的。并发查询绝不会观察到相同的值,而单个查询中的值始终是不同的。

  • 将序列间隔从正值改为负值(例如,从 1-1),可能会导致重复,反之亦然。例如,如果第一次查询返回序列值 123,然后将间隔从 1 更改为 -1,则接下来生成的几个值中会包括之前生成的 21

  • Snowflake 可能会在当前序列号使用完毕后立即计算序列的下一个值,而不是等到请求下一个序列号时才进行计算。

    这样做的结果是, ALTER SEQUENCE ... SET INCREMENT ... 命令可能不会影响使用该序列的下一个操作。有关示例,请参阅 了解逆转序列方向的影响

  • 每个生成的序列值都会根据序列间隔(也称为“步长”)额外保留值。保留值的范围从序列值到

    <value>  +  (sign(<step>) * abs(<step>))  -  (sign(<step>) * 1)

    (含)。

    因此,如果生成的值为 100

    • 当步长为 2 时,则保留值 100101

    • 当步长为 10 时,则保留值 100109

    • 当步长为 -5 时,则保留值 96100

    只要 从不 修改步长/间隔,序列就不会生成保留值。

  • 如果 满足以下条件,则序列生成的值大于前一条语句生成的最大值(如果步长为负值,则小于前一条语句生成的最小值):

    • 序列没有 NOORDER 属性。

      NOORDER 指定不保证值按递增顺序排列。

      例如,如果序列中有 START 1 INCREMENT 2,生成的值可能是 131015103 等。

      NOORDER 可以在需要同时执行多个插入操作时提高性能(例如,在多个客户端执行多个 INSERT 语句时)。

    • 在提交当前语句之前,已完成前一条语句并收到确认。

    如果间隔的符号发生更改(正变为负或负变为正),则此行为 成立。

系统无法保证序列中的值是连续的(无间隙),也无法保证序列值是按照特定顺序分配的。事实上,要将序列中的值按指定顺序分配给行,只能使用单行语句(这仍然不能保证值之间无间隙)。

序列值可以表示 64 位二进制整数(-2^632^63 - 1)。如果序列中下一个值的内部表示超出此范围(在任一方向上),则会出现错误,导致查询失败。请注意,这可能会导致序列值 丢失

在这种情况下,必须使用较小(量级)的增量值或创建具有较小起始值的新序列。由于值之间可能会出现间隙,即使返回的序列值都在允许范围内,下一个值的内部表示也可能会超出允许范围。Snowflake 不提供关于如何避免此错误的明确保证,但 Snowflake 支持正确提供唯一值的序列对象。使用起始值 1 和增量值 1 创建的序列对象极不可能超出允许的序列值范围。

引用序列

不支持 currval

许多数据库都提供 currval 序列参考;然而,Snowflake 并未提供这一参考。在其他系统中,currval 通常用于创建表之间的主外键关系,第一条语句使用序列将单个行插入事实表以创建键。后续语句将行插入维度表中,该表使用 currval 引用事实表的键。

这种模式与 Snowflake 的最佳实践相悖 – 批量查询应该优先于小型单行查询。对于同样的任务,在嵌套子查询中使用多表 INSERT 和序列引用可以更好地实现。有关详细示例,请参阅 引入和规范化非规范数据 (本主题内容)。

作为表达式的序列

序列可以在查询中以 seq_name.NEXTVAL 形式的表达式访问。每次出现序列都会生成一组非重复值。这与许多其他数据库提供的方式不同,在其他数据库中,多次引用序列的 NEXTVAL 为每行返回相同的值。

例如,以下查询为列 ab 返回非重复值:

CREATE OR REPLACE SEQUENCE seq1;

SELECT seq1.NEXTVAL a, seq1.NEXTVAL b FROM DUAL;
Copy

若要返回具有相同生成序列值的两列,请使用嵌套子查询和视图:

CREATE OR REPLACE SEQUENCE seq1;

SELECT seqRef.a a, seqRef.a b FROM (SELECT seq1.NEXTVAL a FROM DUAL) seqRef;
Copy

嵌套子查询生成的不同序列值与子查询返回的行一样多(因此,具有多个联接的查询块中的序列引用不是引用联接对象,而是引用查询块的输出)。如果随后过滤掉了相关行,则可能无法观察到这些生成的值;如果多次引用序列的列或内联视图,则可能会观察到两次值(如上面的示例所示)。

备注

对于多表插入,可以在 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;
Copy

GETNEXTVAL 是一个特殊的单行表函数,它可生成一个唯一值(并将此值连接到 SELECT 语句中的其他对象)。对 GETNEXTVAL 的调用必须具有别名;否则,无法引用生成的值。通过访问此别名,多个列可以引用生成的值。GETNEXTVAL 别名还包含一个名为 NEXTVAL 的属性。

此外,当许多表连接在一起时, GETNEXTVAL 表函数还允许对序列生成进行精确控制。FROM 子句中对象的顺序决定了值生成的位置。序列值是在 FROM 子句中 GETNEXTVAL 之前列出的所有对象之间的连接结果上生成的。然后将生成的行连接到右侧的对象。在 GETNEXTVAL 和 FROM 子句中的所有其他对象之间存在隐式横向依赖关系。联接不能围绕 GETNEXTVAL 重新排序。这在 SQL 中是一个例外,因为对象的顺序通常不会影响查询语义。

请参考表 t1t2t3t4 中的以下示例:

CREATE OR REPLACE SEQUENCE seq1;

SELECT t1.*, t2.*, t3.*, t4.*, s.NEXTVAL FROM t1, t2, TABLE(GETNEXTVAL(seq1)) s, t3, t4;
Copy

此查询将 t1 连接到 t2,生成结果的唯一值,然后将结果关系连接到 t3t4。未指定后序列关系、 t3t4 之间的联接顺序,是因为内部联接是关联的。

备注

这些语义可能很棘手。我们建议在可能和适当的情况下,在 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 |
+------+------+
Copy

使用序列作为列默认值的优点是,序列可以在其他位置引用,甚至可以作为多个列和多个表的默认值。如果将序列命名为列的默认表达式,随后又被删除,那么任何使用默认值插入/更新表的尝试都会导致错误,系统也会提示无法找到标识符。

引入和规范化非规范数据

可考虑使用具有 peoplecontact 两个表的架构:

  • people 表包括:

    • 主键唯一标识符: id

    • 两个字符串列: firstNamelastName

  • contact 表包括:

    • 主键唯一标识符: id

    • 将此联系人条目链接到人员的外键: p_id

    • 两个字符串列:

      • c_type:联系人的类型(例如“电子邮件地址”或“电话”)。

      • data:实际联系信息。

这种格式的数据在引入或处理半结构化数据时,通常会被非规范化。

此示例说明了引入 JSON 数据、对其进行去规范化以提取所需的数据,以及在将数据插入表时对其进行规范化。同时,在保持表中各行之间的预期关系的同时,必须在行上创建唯一标识符。我们可以使用序列来实现这一点。

  1. 首先,我们设置示例中使用的表和序列:

    -- 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);
    
    Copy
  2. 下一步,我们从表 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\',
         }
       ]
     }
    ]'
    );
    
    Copy
  3. 然后,我们对 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
    );
    
    Copy
  4. 这将生成以下数据(唯一的 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 |
    +----+------+--------+----------------------+
    
    Copy

可以看到,行与行之间是关联的,并且可以在 people.idcontact.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     |
 +----+------+--------+----------------------+
Copy

更改序列

了解逆转序列方向的影响

下面的示例显示了逆转序列方向时发生的情况。

这也表明,由于预先计算了序列值, 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
    );
Copy

加载表:

INSERT INTO test_seq_wrap_low (i) VALUES
     (1),
     (2),
     (3);
Copy

显示 j 列中的序列值:

SELECT * FROM test_seq_wrap_low ORDER BY i;
+---+---+
| I | J |
|---+---|
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
+---+---+
Copy

改变序列的增量(步长):

ALTER SEQUENCE test_sequence_wraparound_low SET INCREMENT = -4;
Copy

再插入两行:

INSERT INTO test_seq_wrap_low (i) VALUES
    (4),
    (5);
Copy

显示序列值。请注意,在 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 |
+---+---+
Copy
语言: 中文