首页 > 数据库 > 国内数据库 > Databend SQL 存储过程使用指南
存储过程(Stored Procedure)是一组预编译的 SQL 语句集合,它们被保存在数据库中,可以像函数一样被重复调用。想象一下,如果你经常需要执行一系列复杂的数据处理操作,与其每次都手动输入这些 SQL 语句,不如将它们封装成一个存储过程,需要时直接调用即可。
让我们从最简单的例子开始。假设我们需要一个简单的加法存储过程:
CREATE PROCEDURE my_add(a Int32, b Int32) RETURNS Int32 LANGUAGE SQL AS $$ BEGIN RETURN a + b; END; $$;
让我们逐行理解这个存储过程:
CREATE PROCEDURE my_add:创建一个名为
my_add 的存储过程(a Int32, b Int32):定义输入参数
a 和
b,类型为 Int32RETURNS Int32:指定返回值类型为 Int32LANGUAGE SQL:指定使用 SQL 语言编写(目前 Databend 仅支持 SQL)AS $$ ... $$:使用美元符号包裹存储过程的主体代码BEGIN ... END:存储过程主体的开始和结束标记RETURN a + b:执行计算并返回结果创建后,我们可以这样调用它, 注意参数类型需要显式指定:
call PROCEDURE my_add(3::Int,4::Int); ---- 7
存储过程中的语法我们称之为 SqlScript, 我们也可以直接使用
execute immediate 来执行 SqlScript 语句。
execute immediate 'CREATE TABLE test (id Int32)';
execute immediate $$ BEGIN select 33; let s RESULTSET := select number from numbers(100); RETURN TABLE(s); END; $$;
现在让我们学习如何在 SqlScript 中使用变量、条件判断和循环。
在 Databend 中,使用
LET 关键字声明变量:
语法有:
LET <variable_name> := <value> -- 声明并初始化变量 xLET <variable_name> [<type>] := <value> -- 声明并初始化变量 xLET <variable_name> [<type>] DEFAULT <value> -- 声明并初始化变量 xLET <variable_name> [<type>] -- 声明变量 x, 后续初始化execute immediate $$ BEGIN LET sum := 0; -- 声明并初始化变量 sum FOR i IN 1 TO 10 DO IF i % 2 = 0 THEN sum := sum + i; -- 累加偶数 END IF; END FOR; RETURN sum; END; $$;
在这个例子中:
LET sum := 0:声明一个名为
sum 的变量并初始化为 0:=:赋值操作符RETURNS UInt8 NOT NULL:指定返回值不能为 NULLResultSet 变量用于存储查询结果集,语法有:
示例语法:
execute immediate $$ BEGIN LET x RESULTSET := select number from numbers(10); RETURN TABLE(x); END; $$;
上面是返回结果集,所以使用
RETURN TABLE(x) 语句
Cursor 变量用于遍历结果集,语法有:
LET <cursor_variable> CURSOR for <query>LET <cursor_variable> CURSOR for <result_set_variable>OPEN <cursor_variable>FETCH <cursor_variable> INTO <variable>CLOSE <cursor_variable>for <variable> in <cursor_variable> do ... end for示例语法:
execute immediate $$ BEGIN LET v Int; LET c CURSOR for select max(number) from numbers(10); OPEN c; FETCH c INTO v; CLOSE c; let d RESULTSET := select number from numbers(10); let e CURSOR for d; for v2 in e do v := v + v2.number; end for; return v; END; $$;
IF 语句允许我们根据条件执行不同的代码分支:
execute immediate $$ BEGIN LET score := 57 + 10 + 10 + 10; LET grade := ''; IF score >= 90 THEN grade := '优秀'; ELSEIF score >= 80 THEN grade := '良好'; ELSEIF score >= 70 THEN grade := '中等'; ELSEIF score >= 60 THEN grade := '及格'; ELSE grade := '不及格'; END IF; RETURN grade; END; $$;
FOR 循环有两种常见形式:
FOR i IN start_value TO end_value DO -- 循环体 END FOR;
示例:
execute immediate $$ BEGIN LET sum := 0; FOR i IN 1 TO 10 DO sum := sum + i; END FOR; RETURN sum; END; $$;
示例:
execute immediate $$ BEGIN -- 声明一个结果集变量 LET x RESULTSET := SELECT number n FROM numbers(10); LET sum := 0; -- 遍历结果集 FOR r IN x DO -- 使用 r.n 访问列值 sum := sum + r.n; END FOR; RETURN sum; END; $$;
让我们看一个更复杂的例子,展示嵌套循环和多层逻辑:
execute immediate $$ BEGIN -- 声明结果集变量:从 0 到 9 的数字 LET x RESULTSET := SELECT number n FROM numbers(10); LET sum := 0; -- 外层循环:遍历结果集 FOR x IN x DO -- 内层循环:从 0 到当前数字 FOR batch IN 0 TO x.n DO IF batch % 2 = 0 THEN sum := sum + batch; -- 偶数加 ELSE sum := sum - batch; -- 奇数减 END IF; END FOR; END FOR; RETURN sum; END; $$;
让我们分析一下这个过程的执行流程:
例如当 x.n = 3 时:
batch = 0(偶):sum += 0
batch = 1(奇):sum -= 1
batch = 2(偶):sum += 2
batch = 3(奇):sum -= 3
execute immediate $$ BEGIN LET tbl_name := 'abcd1' ; LET drop_sql := 'DROP TABLE default."' || tbl_name || '"' ; EXECUTE IMMEDIATE :drop_sql ; END ; $$ ;
除了返回单个值,存储过程还可以返回整张表:
execute immediate $$ BEGIN RETURN TABLE( SELECT number % 3 d, SUM(number) AS total_amount FROM numbers(10) GROUP BY d ) ; END ; $$ ;
| 操作 | SQL | 说明 |
|---|---|---|
| 查看所有存储过程 |
SHOW PROCEDURES; |
|
| 查看存储过程详情 |
DESC PROCEDURE sum_even_numbers(UInt8, UInt8);
或 DESCRIBE PROCEDURE sum_even_numbers(UInt8, UInt8); |
注意:
• 无参数的存储过程使用空括号: DESC PROCEDURE proc_name()
• 有参数的必须指定确切的参数类型 |
| 删除存储过程 |
DROP PROCEDURE my_add(int, int); |
|
| 替换存储过程 |
CREATE OR REPLACE PROCEDURE my_add(a Int32, b Int32)
RETURNS Int32
LANGUAGE SQL
AS $$
BEGIN
RETURN a + b + 3;
END;
$$; |
|
calc_,
get_,
update_)-- 好的命名 CREATE PROCEDURE calc_monthly_revenue(...) CREATE PROCEDURE get_active_users(...) CREATE PROCEDURE update_user_status(...) -- 不好的命名 CREATE PROCEDURE proc1(...) CREATE PROCEDURE x(...)
始终为存储过程添加清晰的注释:
CREATE PROCEDURE process_orders(order_date DATE) RETURNS INT LANGUAGE SQL COMMENT = '处理指定日期的订单,返回处理数量' AS $$ ... $$ ;
让我们用二个实际案例来综合运用所学知识:
CREATE OR REPLACE PROCEDURE cleanup_user_data(days_threshold INT) RETURNS TABLE( action VARCHAR, user_count INT, processed_at TIMESTAMP ) LANGUAGE SQL COMMENT = '清理和归档不活跃用户数据' AS $$ BEGIN LET cutoff_date := DATE_SUB(DAY, days_threshold,today()) ; LET inactive_users := 0 ; LET deleted_users := 0 ; -- 统计不活跃用户 LET inactive_resultset RESULTSET := SELECT COUNT(*) AS cnt FROM users WHERE last_login_date < cutoff_date AND status = 'active' ; FOR r IN inactive_resultset DO inactive_users := r.cnt ; END FOR ; -- 标记不活跃用户 UPDATE users SET status = 'inactive' WHERE last_login_date < cutoff_date AND status = 'active' ; -- 删除长期不活跃用户 DELETE FROM users WHERE last_login_date < DATE_SUB(cutoff_date, INTERVAL days_threshold DAY) AND status = 'inactive' ; -- 返回处理结果 RETURN TABLE( SELECT 'Marked Inactive' AS action, inactive_users AS user_count, CURRENT_TIMESTAMP() AS processed_at UNION ALL SELECT 'Deleted' AS action, deleted_users AS user_count, CURRENT_TIMESTAMP() AS processed_at ) ; END ; $$ ;
调用方式:
-- 清理 90 天未登录的用户 CALL PROCEDURE cleanup_user_data(90::Int) ;
CREATE OR REPLACE PROCEDURE PROC_MERGE_GPS() RETURNS STRING LANGUAGE SQL AS $$ BEGIN create or replace table default.gps as select number from numbers(100) ; create or replace table default.abcd1 as select number from numbers(100) ; create or replace table default.abcd2 as select number from numbers(100) ; create or replace table default.abcd3 as select number from numbers(100) ; -- Step 1: 查询符合条件的表名(使用 INFORMATION_SCHEMA) LET records RESULTSET := ( select name from system.tables where database = 'default' and name like '%abcd%' ) ; LET table_count := 0 ; LET record_count := 0 ; LET table_names := [] ; LET union_parts := [] ; for table_record in records DO LET name := table_record.name ; table_count := table_count + 1 ; table_names := ARRAY_APPEND(table_names, name) ; union_parts := ARRAY_APPEND(union_parts, 'SELECT * FROM default.' || name) ; END FOR ; -- 如果没有匹配的表,直接返回 IF (table_count = 0) THEN RETURN 'No data to process' ; END IF ; -- Step 3: 创建临时视图 LET view_sql := 'CREATE OR REPLACE VIEW default.TEMPORARY_GPS_TABLES AS ' || ARRAY_TO_STRING(union_parts, ' UNION ALL ') ; EXECUTE IMMEDIATE :view_sql ; -- Step 2: 查询表中的记录数 LET record_count_sql := 'SELECT COUNT(*) c FROM default.TEMPORARY_GPS_TABLES' ; LET r RESULTSET := EXECUTE IMMEDIATE :record_count_sql ; for record in r DO record_count := record.c ; END FOR ; -- Step 4: 设置会话参数, example EXECUTE IMMEDIATE 'set max_block_size = 65536' ; -- Step 5: 执行 示例SQL LET merge_sql := 'insert into default.gps select * from default.TEMPORARY_GPS_TABLES;' ; EXECUTE IMMEDIATE :merge_sql ; -- Step 6: 清理:删除视图 EXECUTE IMMEDIATE 'DROP VIEW IF EXISTS default.TEMPORARY_GPS_TABLES' ; -- Step 7: 删除所有 %abcd% 表 FOR i IN 1 TO ARRAY_SIZE(table_names) DO LET tbl_name := table_names[i]::STRING ; LET drop_sql := 'DROP TABLE default."' || tbl_name || '"' ; EXECUTE IMMEDIATE :drop_sql ; END FOR ; RETURN 'Merge completed successfully. Processed ' || table_count || ' tables. Total records: ' || record_count ; END ; $$ ;
调用结果:
call PROCEDURE PROC_MERGE_GPS() ; --- Merge completed successfully. Processed 3 tables. Total records: 300
Databend 的 SQL 存储过程为数据处理提供了强大而灵活的工具。通过本文,我们学习了:
CREATE PROCEDURE 创建存储过程CALL PROCEDURE 调用存储过程EXECUTE IMMEDIATE 执行动态 SQLLET 声明变量,
:= 赋值IF-THEN-ELSE 条件判断FOR...IN...DO 循环CREATE OR REPLACE 更新存储过程RESULTSET 类型处理查询结果现在你已经掌握了 Databend 存储过程的核心知识,可以开始:
Happy coding with Databend! 🚀
Databend 是一款开源、弹性、低成本,基于对象存储也可以做实时分析的新式湖仓。期待您的关注,一起探索云原生数仓解决方案,打造新一代开源 Data Cloud。
👨💻 Databend Cloud: databend.cn
📖 Databend 文档: docs.databend.cn
💻 Wechat:Databend
✨ GitHub: github.com/databendlab…
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70042558/viewspace-3101648/ ,如需转载,请注明出处,否则将追究法律责任。