调用执行 DML 并返回多个值的 Snowflake 存储过程的最佳方式是什么?

问题描述 投票:0回答:1

下面是一个简单的例子。最后的

CALL
因错误而失败

SQL 编译错误:表“EXAMPLE”上 FROM 子句中的存储过程中的子作业的语句类型“INSERT”无效

具体目的是调用一个存储过程,该存储过程 (a) 执行

INSERT
并 (b) 从另一个存储过程返回多个值,然后使用返回的值。

代码示例 1 - 失败:

--NB: This TABLE must exist for all Code Samples. Not repeated. ---
CREATE OR REPLACE TABLE EXAMPLE
(
    ID_STRING VARCHAR(36) NOT NULL,
    INSERT_TIMESTAMP TIMESTAMP_NTZ NOT NULL
);

CREATE OR REPLACE PROCEDURE EXAMPLE_WITH_INSERT()
RETURNS TABLE (
    ID_STRING VARCHAR(36) ,
    INSERT_TIMESTAMP TIMESTAMP_NTZ 
)
LANGUAGE SQL
AS
$$
DECLARE
    ID_STRING VARCHAR(36);
    INSERT_TIMESTAMP TIMESTAMP_NTZ;
    RESULT RESULTSET;
BEGIN
    ID_STRING:=UUID_STRING();
    INSERT_TIMESTAMP:=CURRENT_TIMESTAMP();
    INSERT INTO EXAMPLE(ID_STRING,INSERT_TIMESTAMP) SELECT :ID_STRING,:INSERT_TIMESTAMP; -- Appears to be causing the error later on...
    RESULT :=(SELECT :ID_STRING ID_STRING,:INSERT_TIMESTAMP INSERT_TIMESTAMP); 
    RETURN TABLE(RESULT); 
END
$$
;

CREATE OR REPLACE PROCEDURE CALLER_WITH_INSERT()
RETURNS VARCHAR
LANGUAGE SQL
AS
$$
DECLARE
    ID_STRING VARCHAR(36);
    INSERT_TIMESTAMP TIMESTAMP_NTZ;
BEGIN
    SELECT ID_STRING,INSERT_TIMESTAMP INTO ID_STRING,INSERT_TIMESTAMP FROM TABLE(EXAMPLE_WITH_INSERT()) ;
    RETURN CONCAT(ID_STRING,'@',INSERT_TIMESTAMP::VARCHAR) ;
END
$$
;

CALL CALLER_WITH_INSERT(); -- Fails "Invalid statement type 'INSERT' for child job from stored procedure in FROM clause on TABLE 

代码示例 2 - 成功(但不执行

INSERT
)。不适合。

CREATE OR REPLACE PROCEDURE EXAMPLE_NO_INSERT()
RETURNS TABLE (
    ID_STRING VARCHAR(36) ,
    INSERT_TIMESTAMP TIMESTAMP_NTZ 
)
LANGUAGE SQL
AS
$$
DECLARE
    ID_STRING VARCHAR(36);
    INSERT_TIMESTAMP TIMESTAMP_NTZ;
    RESULT RESULTSET;
BEGIN
    ID_STRING:=UUID_STRING();
    INSERT_TIMESTAMP:=CURRENT_TIMESTAMP();
    -- INSERT removed.
    RESULT :=(SELECT :ID_STRING ID_STRING,:INSERT_TIMESTAMP INSERT_TIMESTAMP); 
    RETURN TABLE(RESULT); 
END
$$
;

CREATE OR REPLACE PROCEDURE CALLER_NO_INSERT()
RETURNS VARCHAR
LANGUAGE SQL
AS
$$
DECLARE
    ID_STRING VARCHAR(36);
    INSERT_TIMESTAMP TIMESTAMP_NTZ;
BEGIN
    SELECT ID_STRING,INSERT_TIMESTAMP INTO ID_STRING,INSERT_TIMESTAMP FROM TABLE(EXAMPLE_NO_INSERT()) ;
    RETURN CONCAT(ID_STRING,'@',INSERT_TIMESTAMP::VARCHAR) ;
END
$$
;

CALL CALLER_NO_INSERT();

我的解决方法是返回一个标量

OBJECT
保存返回值。

代码示例 3 - 有效。有更好的办法吗?

CREATE OR REPLACE PROCEDURE EXAMPLE_WITH_INSERT_WORKAROUND()
RETURNS OBJECT
LANGUAGE SQL
AS
$$
DECLARE
    ID_STRING VARCHAR(36);
    INSERT_TIMESTAMP TIMESTAMP_NTZ;
BEGIN
    ID_STRING:=UUID_STRING();
    INSERT_TIMESTAMP:=CURRENT_TIMESTAMP();
    INSERT INTO EXAMPLE(ID_STRING,INSERT_TIMESTAMP) SELECT :ID_STRING,:INSERT_TIMESTAMP; 
    RETURN OBJECT_CONSTRUCT('ID_STRING',:ID_STRING,'INSERT_TIMESTAMP',:INSERT_TIMESTAMP); 
END
$$
;

CREATE OR REPLACE PROCEDURE CALLER_WITH_INSERT_WORKAROUND()
RETURNS VARCHAR
LANGUAGE SQL
AS
$$
DECLARE
    RESULT OBJECT;
BEGIN
    CALL EXAMPLE_WITH_INSERT_WORKAROUND() INTO RESULT ;
    RETURN CONCAT(RESULT:ID_STRING,'@',RESULT:INSERT_TIMESTAMP::VARCHAR) ;
END
$$
;

CALL CALLER_WITH_INSERT_WORKAROUND();

以下示例显示,可以调用存储过程

EXAMPLE_WITH_INSERT()
,但其
RETURN
值(据我所知)并未“处理”为
TABLE
,尽管(很好)被声明为
TABLE
。然而,存储过程
EXAMPLE_NO_INSERT()
没有同样的问题。

CALL EXAMPLE_NO_INSERT();
SELECT CONCAT(ID_STRING,'@',INSERT_TIMESTAMP) FORMTTED FROM TABLE(EXAMPLE_NO_INSERT());

CALL EXAMPLE_WITH_INSERT();
SELECT CONCAT(ID_STRING,'@',INSERT_TIMESTAMP) FORMTTED FROM TABLE(EXAMPLE_WITH_INSERT()) -- FAILS
;

注意:所有建设性的可执行答案都会受到欢迎,并且很可能会被投票,即使我没有发现它们比我的解决方法更好。有很多方法可以给这只猫剥皮!

sql stored-procedures snowflake-cloud-data-platform dml
1个回答
0
投票

可以从外部存储过程中的内部存储过程中“拦截”结果集:

CREATE OR REPLACE PROCEDURE CALLER_INSERT()
RETURNS TEXT
LANGUAGE SQL
AS
DECLARE
    ID_STRING VARCHAR(36);
    INSERT_TIMESTAMP TIMESTAMP_NTZ;
    RES RESULTSET;
BEGIN
    res := (CALL EXAMPLE_NO_INSERT());

    LET cur1 CURSOR FOR res;

    -- assumption that single row is returned
    FOR rw IN cur1 DO
        ID_STRING := rw.ID_STRING;
        INSERT_TIMESTAMP := rw.INSERT_TIMESTAMP;
    END FOR;

    RETURN CONCAT(ID_STRING,'@',INSERT_TIMESTAMP::TEXT) ;
    
END ;

要点:

  • 内部存储过程返回结果集(多列)
  • 外部存储过程拦截结果
     res := (CALL EXAMPLE_NO_INSERT());
  • 打开结果集光标
    LET cur1 CURSOR FOR res;
  • 用于处理返回值的游标 FOR 循环
     FOR rw IN cur1 DO ... END FOR

内部存储过程:

CREATE OR REPLACE PROCEDURE EXAMPLE_NO_INSERT()
RETURNS TABLE (
    ID_STRING VARCHAR(36) ,
    INSERT_TIMESTAMP TIMESTAMP_NTZ 
)
LANGUAGE SQL
AS
DECLARE
    ID_STRING VARCHAR(36);
    INSERT_TIMESTAMP TIMESTAMP_NTZ;
    RESULT RESULTSET;
BEGIN
    ID_STRING:=UUID_STRING();
    INSERT_TIMESTAMP:=CURRENT_TIMESTAMP();
    INSERT INTO EXAMPLE(ID_STRING,INSERT_TIMESTAMP) SELECT :ID_STRING,:INSERT_TIMESTAMP; 
    
    RESULT :=(SELECT :ID_STRING ID_STRING,:INSERT_TIMESTAMP INSERT_TIMESTAMP); 
    RETURN TABLE(RESULT); 
END;

输出:

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.