处理异常

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

在 Snowflake Scripting 中处理异常简介

Snowflake Scripting raises an exception if an error occurs while executing a statement. For example, if a statement attempts to drop a table that doesn't exist, Snowflake Scripting raises an exception.

In a Snowflake Scripting block, you can write exception handlers that catch specific types of exceptions declared in that block and in blocks nested inside that block. In addition, for errors that can occur in your code, you can define your own exceptions that you can raise when errors occur.

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

When an exception is raised in a Snowflake Scripting block, either by your code or by a statement that fails to execute, Snowflake Scripting attempts to find a handler for that exception:

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

  • If the block doesn't have its own exception handler, then the exception can be caught by the enclosing block.

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

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

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

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

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

Declaring an exception in Snowflake Scripting

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

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

Raising a declared exception in 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

注意:如果您在 Python Connector 代码中使用 Snowflake CLISnowSQLClassic Console 或者 execute_streamexecute_string 方法,请改用本示例(请参阅 在 Snowflake CLI、SnowSQL、Classic Console 和 Python Connector 中使用 Snowflake Scripting):

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

If there is no handler, execution stops at the point when the exception is raised. In the example, counter is never incremented and isn't returned.

The client that submits this block for execution --- for example, Snowsight --- reports an error and indicates that the exception was not caught:

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

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

备注

在异常处理程序中,如果需要再次引发相同的异常,请参阅 Raising the same exception again in an exception handler in Snowflake Scripting

Handling an exception in 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 子句中的语句完成,并且语句在块中继续执行之后,这些变量的值将返回引发异常之前的值。

To handle all other exceptions that aren't built-in or declared, use a WHEN OTHER THEN clause. The WHEN OTHER THEN clause can be of type EXIT or 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

注意:如果您在 Python Connector 代码中使用 Snowflake CLISnowSQLClassic Console 或者 execute_streamexecute_string 方法,请改用本示例(请参阅 在 Snowflake CLI、SnowSQL、Classic Console 和 Python Connector 中使用 Snowflake Scripting):

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

注意:如果您在 Python Connector 代码中使用 Snowflake CLISnowSQLClassic Console 或者 execute_streamexecute_string 方法,请改用本示例(请参阅 在 Snowflake CLI、SnowSQL、Classic Console 和 Python Connector 中使用 Snowflake Scripting):

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

For more information about CONTINUE handlers, see EXCEPTION (Snowflake Scripting).

In rare cases, you might want to explicitly handle an exception by doing nothing. This enables you to continue, rather than terminate, when the exception occurs. For more information, see the NULL command.

备注

如果需要再次引发相同的异常,请参阅 Raising the same exception again in an exception handler in Snowflake Scripting

If you don't set up a handler for an exception, the client that submits the block for execution; for example, Snowsight reports an error as explained in Raising a declared exception in Snowflake Scripting.

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

Raising the same exception again in an exception handler in 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

注意:如果您在 Python Connector 代码中使用 Snowflake CLISnowSQLClassic Console 或者 execute_streamexecute_string 方法,请改用本示例(请参阅 在 Snowflake CLI、SnowSQL、Classic Console 和 Python Connector 中使用 Snowflake Scripting):

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

Passing variables to an exception handler in 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

注意:如果您在 Python Connector 代码中使用 Snowflake CLISnowSQLClassic Console 或者 execute_streamexecute_string 方法,请改用本示例(请参阅 在 Snowflake CLI、SnowSQL、Classic Console 和 Python Connector 中使用 Snowflake Scripting):

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

注意:如果您在 Python Connector 代码中使用 Snowflake CLISnowSQLClassic Console 或者 execute_streamexecute_string 方法,请改用本示例(请参阅 在 Snowflake CLI、SnowSQL、Classic Console 和 Python Connector 中使用 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

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

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). |
+----------------------------------------------------------------------+
语言: 中文