类别:

查询语法

AT | BEFORE

AT 或 BEFORE 子句用于 Snowflake Time Travel。在查询中,它在 FROM 子句中紧接在表名之后指定,并确定请求对象历史数据的过去时间点:

  • AT 关键字指定请求包含时间戳等于指定参数的语句或事务所做的任何变更。

  • BEFORE 关键字指定请求引用紧挨着指定参数之前的点。此时间点恰好位于由其查询 ID 标识的语句完成之前。有关更多信息,请参阅 使用 BEFORE 子句

有关更多信息,请参阅 了解和使用 Time Travel

另请参阅:

FROM

语法

SELECT ...
FROM ...
  {
   AT( { TIMESTAMP => <timestamp> | OFFSET => <time_difference> | STATEMENT => <id> | STREAM => '<name>' } ) |
   BEFORE( STATEMENT => <id> )
  }
[ ... ]
Copy

参数

TIMESTAMP => timestamp

指定要用于 Time Travel 的确切日期和时间。该值必须显式转换为 TIMESTAMP、TIMESTAMP_LTZ、TIMESTAMP_NTZ 或 TIMESTAMP_TZ 数据类型。

如未指定显式转换,AT 子句中的时间戳会被视为采用 UTC 时区的时间戳(相当于 TIMESTAMP_NTZ)。为显式强制转换使用 TIMESTAMP 数据类型也可能导致值被视为 TIMESTAMP_NTZ 值。有关详细信息,请参阅 日期和时间数据类型

OFFSET => time_difference

指定自当前时间开始的、要用于 Time Travel 的时间差值(以秒为单位),其格式为 -N,其中 N 可以是整数或算术表达式(例如,-120 为 120 秒、-30*60 为 1800 秒或 30 分钟)。

STATEMENT => id

指定要用作 Time Travel 参考点的语句的查询 ID。此参数支持以下某一类型的任意语句:

  • DML(例如 INSERT、UPDATE、DELETE)

  • TCL(BEGIN、COMMIT 事务)

  • SELECT

查询 ID 必须引用过去 14 天内执行的查询。如果查询 ID 引用的查询超过 14 天,则返回以下错误:

Error: statement <query_id> not found

若要解决此限制,请使用引用查询的时间戳。

STREAM => 'name'

指定已查询的表或视图上现有流的标识符(即名称)。流中的当前偏移用作返回源对象的变更数据的 AT 时间点。

只有在创建流(使用 CREATE STREAM)或查询更改数据(使用 CHANGES 子句)时才支持此关键字。有关示例,请参阅以下主题。

使用 AT TIMESTAMP 参数

在 AT 子句中,您可以指定 TIMESTAMP 关键字后接一个表示时间戳的字符串,以及一个到 TIMESTAMP、TIMESTAMP_TZ、TIMESTAMP_LTZ 或 TIMESTAMP_NTZ 数据类型的可选显式转换。以下示例均有效:

AT ( TIMESTAMP => '2024-06-05 12:30:00'::TIMESTAMP_LTZ )

AT ( TIMESTAMP => '2024-06-05 12:30:00'::TIMESTAMP )

AT ( TIMESTAMP => '2024-06-05 12:30:00' )
Copy

如未指定显式转换,AT 子句中的时间戳会被视为采用 UTC 时区的时间戳(相当于 TIMESTAMP_NTZ)。为显式强制转换使用 TIMESTAMP 数据类型也可能导致值被视为 TIMESTAMP_NTZ 值,如 日期和时间数据类型 中所述。

您选择的显式转换会影响 Time Travel 查询的结果,因为时间戳是根据会话的当前时区和 TIMESTAMP_TYPE_MAPPING 参数的值解释的。有关此行为的更多详细信息,请参阅 在使用非 UTC 时区的会话中查询 Time Travel 数据 (https://community.snowflake.com/s/article/Querying-time-travel-data-in-a-session-with-a-non-UTC-timezone)。

例如,您正在当前时区为 America/Los_Angeles 且 TIMESTAMP_TYPE_MAPPING 设置为 TIMESTAMP_NTZ 的 SQL 会话中运行查询。创建一个表并立即插入两行:

CREATE OR REPLACE TABLE tt1 (c1 INT, c2 INT);
INSERT INTO tt1 VALUES(1,2);
INSERT INTO tt1 VALUES(2,3);
Copy

使用以下 SHOW TABLES 命令检查表的创建时间:

SHOW TERSE TABLES LIKE 'tt1';
Copy
+-------------------------------+------+-------+---------------+----------------+
| created_on                    | name | kind  | database_name | schema_name    |
|-------------------------------+------+-------+---------------+----------------|
| 2024-06-05 15:25:35.557 -0700 | TT1  | TABLE | TRAVEL_DB     | TRAVEL_SCHEMA  |
+-------------------------------+------+-------+---------------+----------------+

请注意 created_on 列中的时区偏移。在五分钟后插入另一行:

INSERT INTO tt1 VALUES(3,4);
Copy

现在运行以下 Time Travel 查询,它本该返回前两行:

SELECT * FROM tt1 at(TIMESTAMP => '2024-06-05 15:29:00'::TIMESTAMP);
Copy
000707 (02000): Time travel data is not available for table TT1. The requested time is either beyond the allowed time travel period or before the object creation time.

查询失败,因为会话的时区是 UTC,并且显式转换为采用该时区的 TIMESTAMP。因此,假定该表是在指定时间戳 之后 创建的。要解决此问题,请在显式转换为 TIMESTAMP_LTZ(当地时区)的情况下再次运行查询:

SELECT * FROM tt1 at(TIMESTAMP => '2024-06-05 15:29:00'::TIMESTAMP_LTZ);
Copy
+----+----+
| C1 | C2 |
|----+----|
|  1 |  2 |
|  2 |  3 |
+----+----+

正如预期的那样,查询返回所插入的前两行。最后运行相同的查询,但指定稍晚的时间戳:

SELECT * FROM tt1 at(TIMESTAMP => '2024-06-05 15:31:00'::TIMESTAMP_LTZ);
Copy
+----+----+
| C1 | C2 |
|----+----|
|  1 |  2 |
|  2 |  3 |
|  3 |  4 |
+----+----+

给定较晚的时间戳,此查询会返回全部三行。

使用 BEFORE 子句

BEFORE 子句的 STATEMENT 参数必须引用查询 ID。Time Travel 使用的过去时间点恰好在该查询 ID 对应的语句完成之前,而不是在语句开始之前。如果并发查询在语句开始和结束之间提交了对数据的修改,则这些更改会包含在结果中。

例如,以下语句在两个独立的线程中在 my_table 表上同时执行:

时间

线程

操作

阶段

描述

t1

1

INSERT INTO my_table(id) VALUE(1)

开始时间

插入通过执行必要的检查开始执行。

t2

1

INSERT INTO my_table(id) VALUE(1)

结束时间

插入更新后的 my_table

t3

1

DELETE FROM my_table

开始时间

删除操作标识要删除的记录列表 (id=1)。

t4

2

INSERT INTO my_table(id) VALUE(2)

开始时间

插入通过执行必要的检查开始执行。

t5

2

INSERT INTO my_table(id) VALUE(2)

结束时间

插入更新后的 my_table

t6

2

SELECT * FROM my_table

结束时间

线程 2my_table 中选择行。结果包含所有行 (id=1, id=2)。

t7

1

DELETE FROM my_table

结束时间

当删除语句在线程 1 (id=1) 中启动时,删除操作更新 my_table,并删除在时间 t3 之前存在的所有旧记录。

t8

1

SELECT * FROM my_table BEFORE(STATEMENT => LAST_QUERY_ID())

结束时间

SELECT 语句使用 Time Travel 来检索删除操作完成前的历史数据。结果包含来自第 2 条插入语句的行,该语句在线程 2 (id=1, id=2) 中同时发生。

作为解决方法,可以使用 TIMESTAMP 参数指定正好在语句开始之前的时间点。

使用说明

  • Snowflake 中的数据由时间戳标识,时间戳可能与系统时间的确切值略有不同。

  • TIMESTAMP 或 OFFSET 的值必须是常量表达式。

  • TIMESTAMP 的最小时间分辨率为毫秒。

  • 如果请求的数据超出了 Time Travel 保留期(默认值为 1 天),则语句将失败。

    此外,如果请求的数据在 Time Travel 保留期内,但没有可用的历史数据(例如保留期已延长的情况),则语句将失败。

  • 如果指定的 Time Travel 时间在创建对象的时间点或之前,则语句将失败。请参阅 使用 AT TIMESTAMP 参数

  • 访问历史表数据时,结果包含表当前定义中的列和默认值等。非物化视图也是如此。例如,如果更改表以添加列,则查询在添加列的时间点之前的历史数据会返回包含新列的结果。

  • 历史数据与当前数据具有相同的访问控制要求。任何更改都具有追溯性。

  • AT 和 BEFORE 子句不支持通过 CTE 选择历史数据。

    例如,不支持以下查询:

    WITH mycte AS
      (SELECT mytable.* FROM mytable)
    SELECT * FROM mycte AT(TIMESTAMP => '2024-03-13 13:56:09.553 +0100'::TIMESTAMP_TZ);
    
    Copy

    不过,在 WITH 子句的查询中支持这些子句。例如,支持以下查询:

    WITH mycte AS
      (SELECT * FROM mytable AT(TIMESTAMP => '2024-03-13 13:56:09.553 +0100'::TIMESTAMP_TZ))
    SELECT * FROM mycte;
    
    Copy
  • 针对混合表的 Time Travel 查询具有以下限制:

    • TIMESTAMP 子句仅支持 AT 参数。不支持 OFFSET、STATEMENT 和 STREAM 参数。

    • 对于属于同一数据库的所有表,TIMESTAMP 参数的值必须相同。如果表属于不同的数据库,则可以使用不同的 TIMESTAMP 值。

    • 不支持 BEFORE 子句。

故障排除

错误

Time travel data is not available for table <tablename>

原因

在某些情况下,这是由于使用需要时间戳的字符串引起的。

解决方案

将字符串转换为时间戳。

... AT(TIMESTAMP => '2018-07-27 12:00:00')               -- fails
... AT(TIMESTAMP => '2018-07-27 12:00:00'::TIMESTAMP)    -- succeeds
Copy

示例

使用特定时间戳从表中选择历史数据。在使用 TIMESTAMP 参数的前两个例子中,my_table 可以是标准表或混合表。

SELECT * FROM my_table AT(TIMESTAMP => 'Wed, 26 Jun 2024 09:20:00 -0700'::TIMESTAMP_LTZ);
Copy
SELECT * FROM my_table AT(TIMESTAMP => TO_TIMESTAMP(1432669154242, 3));
Copy

从表中选择截至 5 分钟前的历史数据:

SELECT * FROM my_table AT(OFFSET => -60*5) AS T WHERE T.flag = 'valid';
Copy

从表中选择历史数据,但不包括指定事务所做的任何更改:

SELECT * FROM my_table BEFORE(STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726');
Copy

返回指定事务导致的表数据差异:

SELECT oldt.* ,newt.*
  FROM my_table BEFORE(STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726') AS oldt
    FULL OUTER JOIN my_table AT(STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726') AS newt
    ON oldt.id = newt.id
  WHERE oldt.id IS NULL OR newt.id IS NULL;
Copy

以下示例对同一数据库中的两个表运行 Time Travel 联接查询,其中一个表是混合表。必须为两个表使用相同的 TIMESTAMP 表达式。

SELECT *
  FROM db1.public.htt1
    AT(TIMESTAMP => '2024-06-05 17:50:00'::TIMESTAMP_LTZ) h
    JOIN db1.public.tt1
    AT(TIMESTAMP => '2024-06-05 17:50:00'::TIMESTAMP_LTZ) t
    ON h.c1=t.c1;
Copy
语言: 中文