处理异常

在 Snowflake Scripting 块中,如果发生错误,可以引发异常。您还可以处理 Snowflake Scripting 代码中发生的异常。

在 Snowflake Scripting 中处理异常简介

如果在执行语句时发生错误,Snowflake Scripting 会引发异常。例如,如果语句尝试删除不存在的表,Snowflake Scripting 会引发异常。

在 Snowflake Scripting 块中,您可以编写异常处理程序,以捕获该块中以及嵌套在该块中的块中声明的特定类型的异常。此外,对于代码中可能发生的错误,您可以定义自己的异常,以便在发生错误时引发这些异常。

处理程序中的语句运行后,可以选择退出块或继续运行块中的语句。有关更多信息,请参阅 处理 Snowflake Scripting 中的异常

当 Snowflake Scripting 块中引发异常时(由代码或无法执行的语句引发),Snowflake Scripting 会尝试查找该异常的处理程序:

  • 如果发生异常的块具有该异常的处理程序,则在该异常处理程序的开头恢复执行。

  • 如果该块没有自己的异常处理程序,则封闭块可以捕获该异常。

    如果异常发生的深度超过一层,则系统每次将异常向上发送一层,直到出现以下任一情况:

    • 具有适当异常处理程序的层可以处理该异常。

    • 到达最外层,在这种情况下会发生错误。

  • 如果当前块或任何封闭块中没有异常处理程序,则块的执行将停止,提交块以供执行的客户端(例如 Snowsight、SnowSQL 等)会将此报告为 Snowflake 错误。

异常处理程序可以包含自己的异常处理程序,以防在处理其他异常时发生异常。

在 Snowflake Scripting 中声明异常

您可以在块的 DECLARE 部分中声明自己的异常。请使用 异常声明语法 中描述的语法。例如:

DECLARE
  my_exception EXCEPTION (-20002, 'Raised MY_EXCEPTION.');
Copy

在 Snowflake Scripting 中引发已声明的异常

要引发异常,请执行 RAISE 命令。例如:

DECLARE
  my_exception EXCEPTION (-20002, 'Raised MY_EXCEPTION.');
BEGIN
  LET counter := 0;
  LET should_raise_exception := true;
  IF (should_raise_exception) THEN
    RAISE my_exception;
  END IF;
  counter := counter + 1;
  RETURN counter;
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, and Python Connector):

EXECUTE IMMEDIATE $$
DECLARE
  my_exception EXCEPTION (-20002, 'Raised MY_EXCEPTION.');
BEGIN
  LET counter := 0;
  LET should_raise_exception := true;
  IF (should_raise_exception) THEN
    RAISE my_exception;
  END IF;
  counter := counter + 1;
  RETURN counter;
END;
$$
;
Copy

如果没有处理程序,则执行将在引发异常时停止。在本例中,counter 从不递增和返回。

提交此块以供执行的客户端(例如 Snowsight)将报告错误,并指示未捕获异常:

-20002 (P0001): Uncaught exception of type 'MY_EXCEPTION' on line 8 at position 4 : Raised MY_EXCEPTION.
Copy

如果要添加代码来处理引发的任何异常(以及语句无法执行时引发的异常),可以编写异常处理程序。请参阅 处理 Snowflake Scripting 中的异常

备注

在异常处理程序中,如果需要再次引发相同的异常,请参阅 在 Snowflake Scripting 的异常处理程序中再次引发相同的异常

处理 Snowflake Scripting 中的异常

您可以使用 EXCEPTION 子句捕获异常来显式处理异常,也可以允许块将异常传递给封闭块。

EXCEPTION 子句中,请使用 WHEN 子句按名称处理异常。您可以处理您声明的异常以及内置异常。目前,Snowflake 提供以下内置异常:

  • STATEMENT_ERROR:此异常表示执行语句时出错。例如,如果尝试弃用不存在的表,则会引发此异常。

  • EXPRESSION_ERROR:此异常表示与表达式相关的错误。例如,如果创建了一个计算结果为 VARCHAR 的表达式,并尝试将该表达式的值赋给 FLOAT,则会引发此错误。

异常块中的每个 WHEN 子句可以是以下类型之一:

  • EXIT – 块会执行处理程序中的语句,然后退出当前块。如果块运行此类型的异常,并且块包含导致错误的语句之后的语句,则不会运行这些语句。

    如果块是内部块,并且异常处理程序不包含 RETURN 语句,则执行将退出内部块并继续执行外部块中的代码。

    EXIT 是默认值。

  • CONTINUE - 该块运行异常块中的语句,并继续执行紧随导致错误的语句之后的语句。

    CONTINUE 处理程序可以捕获和处理异常,而无需结束引发异常的语句块。使用默认 EXIT 处理程序,当块中发生错误时,流程将中断并将错误返回给调用方。但是,当错误条件没有严重到足以中断流时,您可以使用 CONTINUE 处理程序。

一个 EXCEPTION 子句可以同时包含 EXIT 和 CONTINUE 类型的 WHEN 子句。

发生异常时,可以通过读取以下三个内置变量,获取有关异常的信息:

  • SQLCODE:这是一个 5 位的带符号整数。对于用户定义的异常,这是 用于声明异常的语法 中显示的 exception_number

  • SQLERRM:这是一条错误消息。对于用户定义的异常,这是 用于声明异常的语法 中显示的 exception_message

  • SQLSTATE:这是一个基于 ANSI SQL 标准 SQLSTATE (link removed) 的 5 字符代码。除 ANSI SQL 标准中的值外,Snowflake 还使用了其他值。

当您使用 CONTINUE 类型的 WHEN 子句时,这些内置变量会反映导致 WHEN 子句中异常的错误。在 WHEN 子句中的语句完成,并且语句在块中继续执行之后,这些变量的值将返回引发异常之前的值。

要处理所有其他非内置或未声明的异常,请使用 WHEN OTHER THEN 子句。WHEN OTHER THEN 子句的类型可以是 EXIT 或 CONTINUE。

例如,假设您拥有以下错误日志表来跟踪异常:

CREATE OR REPLACE TABLE test_error_log(
  error_type VARCHAR,
  error_code VARCHAR,
  error_message VARCHAR,
  error_state VARCHAR,
  error_timestamp TIMESTAMP);
Copy

以下匿名块将有关异常的信息插入表中,并将有关异常的信息返回给用户:

小技巧

该示例在 DECLARE 部分中定义了一个异常,随后对该异常进行了处理。对于处理 STATEMENT_ERROR 异常的示例,请移除此行的注释 (--):

-- SELECT 1/0;
Copy

要查看处理其他错误的示例,请移除此行的注释:

-- LET var := 1/0;
Copy
DECLARE
  my_exception EXCEPTION (-20002, 'Raised MY_EXCEPTION.');
BEGIN
  -- SELECT 1/0;
  -- LET var := 1/0;
  LET counter := 0;
  LET should_raise_exception := true;
  IF (should_raise_exception) THEN
    RAISE my_exception;
  END IF;
  counter := counter + 1;
  RETURN 'My counter value: ' || counter;
EXCEPTION
  WHEN STATEMENT_ERROR THEN
    INSERT INTO test_error_log VALUES(
      'STATEMENT_ERROR', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
    RETURN OBJECT_CONSTRUCT('Error type', 'STATEMENT_ERROR',
                            'SQLCODE', sqlcode,
                            'SQLERRM', sqlerrm,
                            'SQLSTATE', sqlstate);
  WHEN my_exception THEN
    INSERT INTO test_error_log VALUES(
      'MY_EXCEPTION', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
    RETURN OBJECT_CONSTRUCT('Error type', 'MY_EXCEPTION',
                            'SQLCODE', sqlcode,
                            'SQLERRM', sqlerrm,
                            'SQLSTATE', sqlstate);
  WHEN OTHER THEN
    INSERT INTO test_error_log VALUES(
      'OTHER', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
    RETURN OBJECT_CONSTRUCT('Error type', 'Other error',
                            'SQLCODE', sqlcode,
                            'SQLERRM', sqlerrm,
                            'SQLSTATE', sqlstate);
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, and Python Connector):

EXECUTE IMMEDIATE $$
DECLARE
  my_exception EXCEPTION (-20002, 'Raised MY_EXCEPTION.');
BEGIN
  -- SELECT 1/0;
  -- LET var := 1/0;
  LET counter := 0;
  LET should_raise_exception := true;
  IF (should_raise_exception) THEN
    RAISE my_exception;
  END IF;
  counter := counter + 1;
  RETURN 'My counter value: ' || counter;
EXCEPTION
  WHEN STATEMENT_ERROR THEN
    INSERT INTO test_error_log VALUES(
      'STATEMENT_ERROR', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
    RETURN OBJECT_CONSTRUCT('Error type', 'STATEMENT_ERROR',
                            'SQLCODE', sqlcode,
                            'SQLERRM', sqlerrm,
                            'SQLSTATE', sqlstate);
  WHEN my_exception THEN
    INSERT INTO test_error_log VALUES(
      'MY_EXCEPTION', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
    RETURN OBJECT_CONSTRUCT('Error type', 'MY_EXCEPTION',
                            'SQLCODE', sqlcode,
                            'SQLERRM', sqlerrm,
                            'SQLSTATE', sqlstate);
  WHEN OTHER THEN
    INSERT INTO test_error_log VALUES(
      'OTHER', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
    RETURN OBJECT_CONSTRUCT('Error type', 'Other error',
                            'SQLCODE', sqlcode,
                            'SQLERRM', sqlerrm,
                            'SQLSTATE', sqlstate);
END;
$$
;
Copy

对于返回值,此示例通过调用 OBJECT_CONSTRUCT 构造并返回一个包含异常详细信息的对象,从而处理每种类型的异常。此示例生成以下输出:

+--------------------------------------+
| anonymous block                      |
|--------------------------------------|
| {                                    |
|   "Error type": "MY_EXCEPTION",      |
|   "SQLCODE": -20002,                 |
|   "SQLERRM": "Raised MY_EXCEPTION.", |
|   "SQLSTATE": "P0001"                |
| }                                    |
+--------------------------------------+

您可以查询 test_error_log 表以确认已记录错误:

SELECT * FROM test_error_log;
Copy
+--------------+------------+----------------------+-------------+-------------------------+
| ERROR_TYPE   | ERROR_CODE | ERROR_MESSAGE        | ERROR_STATE | ERROR_TIMESTAMP         |
|--------------+------------+----------------------+-------------+-------------------------|
| MY_EXCEPTION | -20002     | Raised MY_EXCEPTION. | P0001       | 2025-09-05 12:15:00.068 |
+--------------+------------+----------------------+-------------+-------------------------+

前面的示例使用了默认类型 (EXIT) 的 WHEN 子句。如果其中一个 WHEN 子句捕获异常,它会运行 WHEN 子句中的语句,然后退出。因此,以下代码不会运行:

counter := counter + 1;
RETURN 'My counter value: ' || counter;
Copy

如果要处理异常,然后继续运行块中的代码,请指定 CONTINUE 类型的 WHEN 子句。以下示例与前面的示例相同,但它指定 CONTINUE 类型的 WHEN 子句并从每个 WHEN 子句中移除 RETURN 语句:

DECLARE
  my_exception EXCEPTION (-20002, 'Raised MY_EXCEPTION.');
BEGIN
  -- SELECT 1/0;
  -- LET var := 1/0;
  LET counter := 0;
  LET should_raise_exception := true;
  IF (should_raise_exception) THEN
    RAISE my_exception;
  END IF;
  counter := counter + 1;
  RETURN 'My counter value: ' || counter;
EXCEPTION
  WHEN STATEMENT_ERROR CONTINUE THEN
    INSERT INTO test_error_log VALUES(
      'STATEMENT_ERROR', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
  WHEN my_exception CONTINUE THEN
    INSERT INTO test_error_log VALUES(
      'MY_EXCEPTION', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
  WHEN OTHER CONTINUE THEN
    INSERT INTO test_error_log VALUES(
      'OTHER', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, and Python Connector):

EXECUTE IMMEDIATE $$
DECLARE
  my_exception EXCEPTION (-20002, 'Raised MY_EXCEPTION.');
BEGIN
  -- SELECT 1/0;
  -- LET var := 1/0;
  LET counter := 0;
  LET should_raise_exception := true;
  IF (should_raise_exception) THEN
    RAISE my_exception;
  END IF;
  counter := counter + 1;
  RETURN 'My counter value: ' || counter;
EXCEPTION
  WHEN STATEMENT_ERROR CONTINUE THEN
    INSERT INTO test_error_log VALUES(
      'STATEMENT_ERROR', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
  WHEN my_exception CONTINUE THEN
    INSERT INTO test_error_log VALUES(
      'MY_EXCEPTION', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
  WHEN OTHER CONTINUE THEN
    INSERT INTO test_error_log VALUES(
      'OTHER', :sqlcode, :sqlerrm, :sqlstate, CURRENT_TIMESTAMP());
END;
$$
;
Copy
+---------------------+
| anonymous block     |
|---------------------|
| My counter value: 1 |
+---------------------+

输出显示该示例在引发异常后继续运行以下代码:

counter := counter + 1;
RETURN counter;
Copy

有关 CONTINUE 处理程序的更多信息,请参阅 EXCEPTION (Snowflake Scripting)

在极少数情况下,您可能希望不执行任何操作来显式处理异常。这使您能够在发生异常时继续,而不是终止。有关更多信息,请参阅 NULL 命令。

备注

如果需要再次引发相同的异常,请参阅 在 Snowflake Scripting 的异常处理程序中再次引发相同的异常

如果没有为异常设置处理程序,则提交块以供执行的客户端(例如 Snowsight)会报告错误(如 在 Snowflake Scripting 中引发已声明的异常 中所述)。

-20002 (P0001): Uncaught exception of type 'MY_EXCEPTION' on line 8 at position 4 : Raised MY_EXCEPTION.
Copy

在 Snowflake Scripting 的异常处理程序中再次引发相同的异常

在某些情况下,您可能需要引发在异常处理程序中捕获的相同异常。在这些情况下,请执行 RAISE 命令,且不指定任何实参。

例如,假设在异常处理期间,您需要先获取有关异常的一些详细信息,然后再引发相同的异常。获取详细信息后,请执行 RAISE 命令:

BEGIN
  SELECT * FROM non_existent_table;
EXCEPTION
  WHEN OTHER THEN
    LET LINE := SQLCODE || ': ' || SQLERRM;
    INSERT INTO myexceptions VALUES (:line);
    RAISE; -- Raise the same exception that you are handling.
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, and Python Connector):

EXECUTE IMMEDIATE $$
BEGIN
  SELECT * FROM non_existent_table;
EXCEPTION
  WHEN OTHER THEN
    LET LINE := SQLCODE || ': ' || SQLERRM;
    INSERT INTO myexceptions VALUES (:line);
    RAISE; -- Raise the same exception that you are handling.
END;
$$;
Copy

在 Snowflake Scripting 中将变量传递给异常处理程序

您可以将变量传递给异常处理程序。异常处理程序可以根据变量的值执行代码,变量值可以在错误消息中返回。

要将变量传递给 EXCEPTION 部分中的处理程序,必须在 DECLARE 部分中声明该变量。如果在块的 BEGIN ...END 部分中声明了变量,则无法在 EXCEPTION 部分中访问该变量。

此外,如果您编写的 Snowflake Scripting 存储过程可以接受实参,则可以在异常处理程序中使用这些实参。

例如,以下匿名块将 counter_val 变量的值传递给异常处理程序:

DECLARE
  counter_val INTEGER DEFAULT 0;
  my_exception EXCEPTION (-20002, 'My exception text');
BEGIN
  WHILE (counter_val < 12) DO
    counter_val := counter_val + 1;
    IF (counter_val > 10) THEN
      RAISE my_exception;
    END IF;
  END WHILE;
  RETURN counter_val;
EXCEPTION
  WHEN my_exception THEN
    RETURN 'Error ' || sqlcode || ': Counter value ' || counter_val || ' exceeds the limit of 10.';
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, and Python Connector):

EXECUTE IMMEDIATE $$
DECLARE
  counter_val INTEGER DEFAULT 0;
  my_exception EXCEPTION (-20002, 'My exception text');
BEGIN
  WHILE (counter_val < 12) DO
    counter_val := counter_val + 1;
    IF (counter_val > 10) THEN
      RAISE my_exception;
    END IF;
  END WHILE;
  RETURN counter_val;
EXCEPTION
  WHEN my_exception THEN
    RETURN 'Error ' || sqlcode || ': Counter value ' || counter_val || ' exceeds the limit of 10.';
END;
$$
;
Copy

该块返回以下错误消息:

+---------------------------------------------------------+
| anonymous block                                         |
|---------------------------------------------------------|
| Error -20002: Counter value 11 exceeds the limit of 10. |
+---------------------------------------------------------+

以下是传入实参的 Snowflake Scripting 存储过程示例。该示例演示了如何在异常处理程序中使用实参:

CREATE OR REPLACE PROCEDURE exception_test_vars(amount INT)
  RETURNS TEXT
  LANGUAGE SQL
AS
DECLARE
  my_exception_1 EXCEPTION (-20002, 'Value too low');
  my_exception_2 EXCEPTION (-20003, 'Value too high');
BEGIN
  CREATE OR REPLACE TABLE test_order_insert(units INT);
  IF (amount < 1) THEN
    RAISE my_exception_1;
  ELSEIF (amount > 10) THEN
    RAISE my_exception_2;
  ELSE
    INSERT INTO test_order_insert VALUES (:amount);
  END IF;
  RETURN 'Order inserted successfully.';
EXCEPTION
  WHEN my_exception_1 THEN
    RETURN 'Error ' || sqlcode || ': Submitted amount ' || amount || ' is too low (1 or greater required).';
  WHEN my_exception_2 THEN
    RETURN 'Error ' || sqlcode || ': Submitted amount ' || amount || ' is too high (exceeds limit of 10).';
END;
Copy

Note: If you use Snowflake CLI, SnowSQL, the Classic Console, or the execute_stream or execute_string method in Python Connector code, use this example instead (see Using Snowflake Scripting in Snowflake CLI, SnowSQL, and Python Connector):

CREATE OR REPLACE PROCEDURE exception_test_vars(amount INT)
  RETURNS TEXT
  LANGUAGE SQL
AS
$$
DECLARE
  my_exception_1 EXCEPTION (-20002, 'Value too low');
  my_exception_2 EXCEPTION (-20003, 'Value too high');
BEGIN
  CREATE OR REPLACE TABLE test_order_insert(units INT);
  IF (amount < 1) THEN
    RAISE my_exception_1;
  ELSEIF (amount > 10) THEN
    RAISE my_exception_2;
  ELSE
    INSERT INTO test_order_insert VALUES (:amount);
  END IF;
  RETURN 'Order inserted successfully.';
EXCEPTION
  WHEN my_exception_1 THEN
    RETURN 'Error ' || sqlcode || ': Submitted amount ' || amount || ' is too low (1 or greater required).';
  WHEN my_exception_2 THEN
    RETURN 'Error ' || sqlcode || ': Submitted amount ' || amount || ' is too high (exceeds limit of 10).';
END;
$$
;
Copy

以下对存储过程的调用显示了预期的输出:

CALL exception_test_vars(7);
Copy
+------------------------------+
| EXCEPTION_TEST_VARS          |
|------------------------------|
| Order inserted successfully. |
+------------------------------+
CALL exception_test_vars(-3);
Copy
+-----------------------------------------------------------------------+
| EXCEPTION_TEST_VARS                                                   |
|-----------------------------------------------------------------------|
| Error -20002: Submitted amount -3 is too low (1 or greater required). |
+-----------------------------------------------------------------------+
CALL exception_test_vars(20);
Copy
+----------------------------------------------------------------------+
| EXCEPTION_TEST_VARS                                                  |
|----------------------------------------------------------------------|
| Error -20003: Submitted amount 20 is too high (exceeds limit of 10). |
+----------------------------------------------------------------------+