Apache Spark SQL Compound Statement 详解:BEGIN...END 复合语句、变量、游标与异常处理实战指南

发布时间:2026/9/19 23:32:42
Apache Spark SQL Compound Statement 详解:BEGIN...END 复合语句、变量、游标与异常处理实战指南 Apache Spark SQL Compound Statement 详解BEGIN...END 复合语句、变量、游标与异常处理实战指南【免费下载链接】sparkApache Spark - A unified analytics engine for large-scale data processing项目地址: https://gitcode.com/gh_mirrors/sp/spark导读Compound Statement复合语句是 Apache Spark SQL Scripting 的核心构建单元它以BEGIN ... END块的形式承载一段完整的过程化 SQL 脚本脚本内既可以声明局部变量、用户自定义条件、游标与异常处理器handler也可以自由编排 DDL、DML、查询与各类控制流语句。本文将以 Spark 官方文档 docs/control-flow/compound-stmt.md 为骨架逐项讲解复合语句的完整语法、全部参数语义、游标与条件处理器的工作机制并结合 SqlBaseParser.g4 语法文件与 AstBuilder.scala 等源码给出底层实现印证。读完本文你将能独立编写带局部变量、游标遍历与异常拦截的完整 SQL 脚本并理解 Spark 是如何解析与执行这些过程化语法的。一、什么是 Compound StatementSpark 的 SQL 脚本SQL Scripting基于 SQL/PSM 标准风格的过程化语法。任何 SQL 脚本都由且仅由一个复合语句块开始即BEGIN ... END。复合语句的语义是实现一个 SQL 脚本块其中可以包含一组 SQL 语句、控制流语句、局部变量声明以及异常处理器声明。复合语句的组成分为两段参见总览文档 docs/sql-ref-scripting.md声明段declaration section声明局部变量DECLARE variable、局部条件DECLARE ... CONDITION、游标DECLARE ... CURSOR以及条件处理器DECLARE ... HANDLER用于捕获异常主体段body由一组 SQL 语句构成包括控制流语句如循环类LOOP、WHILE、REPEAT、基于查询结果的 FOR、条件类IF、CASE以及跳出循环的 LEAVE 与 ITERATEDDL 语句ALTER、CREATE、DROPDML 语句如 INSERT返回结果集给调用方的 查询设置局部变量与会话变量的 SET 语句EXECUTE IMMEDIATE 动态执行语句游标处理语句OPEN、FETCH、CLOSE嵌套复合语句为变量、游标、条件与处理器提供嵌套作用域。语法层实现依据从语法实现看beginEndCompoundBlock与singleCompoundStatement两条 ANTLR 规则完整刻画了复合语句的结构见 SqlBaseParser.g4singleCompoundStatement : BEGIN (NOT ATOMIC)? compoundBody? END SEMICOLON? EOF ; beginEndCompoundBlock : beginLabel? BEGIN (NOT ATOMIC)? compoundBody? END endLabel? ; compoundBody : (compoundStatementscompoundStatement SEMICOLON) ; compoundStatement : declareConditionStatement | statement | setStatementInsideSqlScript | beginEndCompoundBlock | declareHandlerStatement | ifElseStatement | caseStatement | whileStatement | repeatStatement | leaveStatement | iterateStatement | loopStatement | forStatement ;可以看到compoundStatement的每个分支条件声明、普通语句、SET、嵌套复合块、处理器声明、IF/CASE/WHILE/REPEAT/LEAVE/ITERATE/LOOP/FOR都可以作为复合语句主体中的一条语句这从语法层面印证了官方文档对主体段构成的描述。这些规则最终由 AstBuilder.scala 转换为 Catalyst 逻辑计划中的CompoundStatement相关节点执行。二、完整语法复合语句的完整语法如下继承自官方文档并保留全部细节[ label : ] BEGIN [ { declare_variable | declare_condition } ; [...] ] [ declare_cursor ; [...] ] [ declare_handler ; [...] ] [ SQL_statement ; [...] ] END [ label ] declare_variable DECLARE variable_name [, ...] datatype [ DEFAULT default_expr ] declare_condition DECLARE condition_name CONDITION [ FOR SQLSTATE [ VALUE ] sqlstate ] declare_cursor DECLARE cursor_name [ ASENSITIVE | INSENSITIVE ] CURSOR FOR query declare_handler DECLARE handler_type HANDLER FOR condition_values handler_action handler_type { EXIT | CONTINUE } condition_values { { SQLSTATE [ VALUE ] sqlstate | condition_name } [, ...] | { SQLEXCEPTION | NOT FOUND } [, ...] }语法要点归纳声明段由declare_variable、declare_condition与declare_cursor组成必须位于主体语句之前declare_handler声明的位置在语法上与主体语句并列但语义上通常书写在声明段每个BEGIN ... END块都以分号结束语句整个脚本以;分隔各条语句。三、参数逐项详解3.1label标签可选标识符用于限定qualify块内定义的变量以及用于从块内跳转离开该复合语句配合 LEAVE 语句。起止标签必须匹配END后的标签只有在开头写明了label:的情况下才允许出现。顶层复合语句不允许指定label即整个脚本最外层的BEGIN ... END不能带标签标签只能用于嵌套的复合块。典型用法在嵌套复合语句中用outer_lbl: BEGIN ... END outer_lbl形式声明标签此后便可以在内层用outer_lbl.var_name限定外层变量、用outer_lbl.cursor_name限定外层游标从而消除同名歧义示例详见 docs/control-flow/fetch-stmt.md 中“Qualified cursor name with label”一例。3.2NOT ATOMIC非原子语义表示复合语句中某条 SQL 语句执行失败时不会回滚此前已执行的 SQL 语句。这是当前唯一且默认的行为。也就是说 Spark 的复合语句天然是“非原子”的不存在隐式事务包裹若需要整体原子性需要由调用方会话事务来保证。语法上singleCompoundStatement与beginEndCompoundBlock规则中的(NOT ATOMIC)?正是这一可选关键字的实现见 SqlBaseParser.g4。3.3declare_variable局部变量声明一次DECLARE可以同时声明一个或多个变量variable_name变量名。不允许带限定符即不能写成label.var的形式声明且必须在当前复合语句内唯一。data_type任意受支持的数据类型。若省略数据类型则必须提供DEFAULT此时变量类型由默认表达式推导得出。{ DEFAULT | } default_expression定义变量声明后的初始值。default_expression必须可被转换为castable todata_type。若未指定默认值变量被初始化为NULL。变量在复合语句内的任何表达式中都可被引用。Spark 从最内层作用域向外解析标识符遵循 Name Resolution 规则当嵌套作用域出现同名变量时可用可选标签label.var_name进行消歧。3.4declare_condition局部条件声明用于声明一个可被命名的异常条件之后可在处理器中按名字引用condition_name条件的非限定名作用域限定在当前复合语句内。sqlstate一个STRING字面量由 5 个字母数字字符组成A-Z与0-9大小写不敏感。约束如下不能以00、01或XX开头以02开头的 SQLSTATE 同时也会被预定义的NOT FOUND处理器捕获若省略sqlstate默认值为45000。语法对应declareConditionStatement规则见 SqlBaseParser.g4declareConditionStatement : DECLARE strictIdentifier CONDITION (FOR SQLSTATE VALUE? sqlStateValue)? ;3.5declare_cursor游标声明用于声明一个遍历查询结果的本地游标cursor_name游标的非限定名必须在当前复合语句中声明的所有游标中唯一。游标可通过复合语句的label限定如outer_label.my_cursor来消除嵌套作用域下的重名歧义。ASENSITIVE|INSENSITIVE可选关键字含义为游标一旦打开其结果集不受会话内或会话外的 DML 修改影响。这是默认且唯一支持的行为。query定义游标结果集的查询。查询不会在声明时执行只有执行OPEN cursor_name打开游标时才真正求值打开时查询中的变量引用与参数标记parameter markers会绑定为当时的当前值。游标的完整使用流程为 OPEN → FETCH → CLOSEFETCH每次取一行将各列按位置赋给目标变量若只指定一个STRUCT类型变量且游标返回多列则列值按位置赋给 struct 的字段详见 docs/control-flow/fetch-stmt.md。当取到结果集末尾之后会抛出CURSOR_NO_MORE_ROWS条件SQLSTATE02000该条件可被NOT FOUND处理器捕获若未声明任何NOT FOUND处理器该完成条件会被静默忽略并继续执行。从未打开的游标上执行FETCH会抛出CURSOR_NOT_OPEN错误。3.6declare_handler条件处理器声明用于声明针对异常的处理器是复合语句异常处理能力的核心handler_type处理器类型EXIT处理完条件后退出声明该处理器的复合语句。该复合语句及其所有嵌套复合语句中打开的所有游标会被隐式关闭。CONTINUE处理器执行完毕后继续执行从抛出条件的那条语句的下一条语句继续。condition_values条件值指定该处理器适用于哪些 SQLSTATE 或条件同一个复合语句内所有处理器的条件值必须互不重复具体条件值的优先级高于泛化的SQLEXCEPTION。sqlstate由 5 个字符组成的STRING字面量A-Z与0-9大小写不敏感。condition_name可以是当前复合语句内定义的、外层复合语句中定义的条件也可以是系统预定义的错误类error class如DIVIDE_BY_ZERO、CURSOR_NO_MORE_ROWS。SQLEXCEPTION适用于任何面向用户的错误条件即任何非XX***、非02***的 SQLSTATE。NOT FOUND适用于所有 SQLSTATE02xxx类的条件其中包括游标取到结果集末尾时抛出的CURSOR_NO_MORE_ROWSSQLSTATE02000。handler_action条件发生时执行的一条 SQL 语句。若需要执行多条语句则使用嵌套复合语句包裹。语法对应declareHandlerStatement规则见 SqlBaseParser.g4declareHandlerStatement : DECLARE (CONTINUE | EXIT) HANDLER FOR conditionValues (beginEndCompoundBlock | statement | setStatementInsideSqlScript) ;注意handler_action的三种合法形式正是嵌套复合块beginEndCompoundBlock、普通语句或SET语句。3.7SQL_statement主体语句复合语句主体中的一条 SQL 语句可以是 DDL、DML、控制语句或嵌套复合语句。任何SELECT或VALUES语句会产生一个结果集脚本调用方invoker可以消费该结果集——这是 SQL 脚本向外部返回数据的主要通道。四、条件处理器匹配与执行语义结合总览文档 docs/sql-ref-scripting.md 的“Condition handling”一节条件处理器在选择上遵循most appropriate handler最合适处理器规则处理器不能适用于其自身语句体内、或与它同一声明于同一复合语句的其他处理器体内抛出的条件防止自处理与互相处理造成的递归在抛出异常所在的最内层复合语句中声明的适用处理器是合适的若存在多个合适处理器选择最具体的例如基于命名条件的处理器比基于命名 SQLSTATE 的处理器更具体泛化的EXCEPTION即SQLEXCEPTION处理器最不具体。处理器执行的结果EXIT处理器执行其语句后退出声明该处理器的复合语句并隐式关闭该复合语句及其嵌套复合语句中打开的所有游标CONTINUE处理器执行其语句后从抛出条件语句的下一条语句继续执行。五、完整实战示例5.1 局部变量 EXIT 处理器 嵌套复合语句第一个官方示例演示了局部变量、EXIT HANDLER与嵌套复合块带标签的协同工作原文见 docs/control-flow/compound-stmt.md-- A compound statement with local variables, an exit handler and a nested compound. BEGIN DECLARE a INT DEFAULT 1; DECLARE b INT DEFAULT 5; DECLARE EXIT HANDLER FOR DIVIDE_BY_ZERO div0: BEGIN VALUES (15); END div0; SET a 10; SET a b / 0; VALUES (a); END; 15执行推演声明a 1、b 5并注册一个针对系统错误类DIVIDE_BY_ZERO的EXIT处理器处理器动作为带标签div0的嵌套复合块输出15SET a 10将a更新为10SET a b / 0触发除零异常 → 匹配到DIVIDE_BY_ZERO处理器嵌套复合块执行VALUES (15)输出15因为是EXIT处理器执行完直接退出外层复合语句末尾的VALUES (a)不再执行因此最终只输出15。5.2 游标 CONTINUE 处理器 REPEAT 循环第二个官方示例演示了声明游标、使用CONTINUE HANDLER FOR NOT FOUND配合REPEAT ... UNTIL循环逐行累加原文见 docs/control-flow/compound-stmt.md-- A compound statement with a cursor and a CONTINUE handler for iteration. BEGIN DECLARE x INT; DECLARE done BOOLEAN DEFAULT false; DECLARE total INT DEFAULT 0; DECLARE my_cursor CURSOR FOR SELECT id FROM range(5); DECLARE CONTINUE HANDLER FOR NOT FOUND SET done true; OPEN my_cursor; REPEAT FETCH my_cursor INTO x; IF NOT done THEN SET total total x; END IF; UNTIL done END REPEAT; CLOSE my_cursor; VALUES (total); END; 10执行推演声明局部变量x、done默认false、total默认0声明游标my_cursor指向SELECT id FROM range(5)并注册CONTINUE HANDLER FOR NOT FOUND SET done trueOPEN my_cursor打开游标此时才真正执行查询REPEAT循环中反复FETCH my_cursor INTO x正常时累加total当取完 5 行后再取时抛出CURSOR_NO_MORE_ROWSSQLSTATE02000被NOT FOUND处理器捕获将done置为true随后CONTINUE语义使执行从FETCH的下一条语句继续循环条件UNTIL done随即满足退出CLOSE my_cursor关闭游标输出VALUES (total)即01234 10。这两个示例恰好覆盖了EXIT与CONTINUE两种处理器类型的全部语义也体现了游标“声明—打开—取数—关闭”的完整生命周期。六、变量与游标的作用域规则变量在复合语句内声明的变量可被该复合语句内的任何表达式引用。Spark 按 Name Resolution 规则从最内层作用域向外解析标识符详见 docs/sql-ref-name-resolution.md嵌套复合语句提供新的作用域可用标签label.var消歧同名变量。游标在复合语句内声明的游标可被同复合语句内的OPEN、FETCH、CLOSE语句引用同样支持用标签label.cursor消歧嵌套作用域下的重名游标。处理器作用域处理器handler声明的作用域同样遵循最近嵌套原则——异常总是在抛出它的最内层复合语句中寻找适用处理器。此外脚本与调用方之间传递数据有两条途径见 docs/sql-ref-scripting.md会话变量跨脚本传递标量值或小规模数组/映射可用DECLARE VARIABLE在会话级别创建参数标记parameter markers从 Notebook 控件、Python 或其他语言向脚本传递标量值或小规模数组/映射数据。七、与其他控制流语句的关系复合语句是承载其他全部过程化语法的“容器”与其配套使用的语句包括完整列表见本文“相关文章”与总览文档游标三件套OPEN打开游标、FETCH逐行取数、CLOSE关闭游标条件分支IF多条件分支、CASE搜索式/简单式分支循环LOOP无条件循环、WHILE先判后循环、REPEAT先执行后判断、FOR基于查询结果循环跳转LEAVE跳出标签标记的复合语句或循环、ITERATE跳转到标签标记的循环开头继续下一轮。所有这些语句在 SqlBaseParser.g4 的compoundStatement规则中均作为并列分支存在说明它们只能出现在复合语句内部——这与 IF 等文档中“This statement may only be used within a compound statement”的说明完全一致。八、注意事项与最佳实践顶层复合语句不能带标签标签只用于嵌套复合块与循环且END标签必须与起始label:成对匹配声明必须先于使用变量、条件、游标的声明段位于主体语句之前处理器虽然语法上与主体语句并列建议统一写在声明段以增强可读性默认初始化未指定DEFAULT的变量初始化为NULL省略数据类型时则必须提供DEFAULT以推导类型自定义条件的默认 SQLSTATE 为45000且不能使用00、01、XX开头02开头会被NOT FOUND一并捕获游标是惰性执行的DECLARE ... CURSOR FOR query不会执行查询OPEN时才求值并绑定当时的变量值与参数标记注意EXIT的副作用EXIT处理器执行后会隐式关闭当前及嵌套复合语句中打开的所有游标并跳过后续语句设计脚本流程时要特别留意CONTINUE与NOT FOUND的组合是游标遍历的惯用模式用DECLARE CONTINUE HANDLER FOR NOT FOUND SET done true;配合REPEAT/WHILE循环即可安全地遍历完整结果集见 docs/control-flow/fetch-stmt.md脚本是NOT ATOMIC的复合语句内某条语句失败不会回滚之前的语句需要整体原子性时请在调用侧通过事务控制性能提醒过程化逐行循环通常比等价的关系表达式低效——官方 REPEAT 示例特意对比了SELECT sum(num) FROM range(1,10) ...的关系式写法见 docs/control-flow/repeat-stmt.md在能用声明式 SQL 表达时优先使用声明式 SQL。相关文章SQL ScriptingSQL 脚本总览OPEN Statement打开游标FETCH Statement取数CLOSE Statement关闭游标CASE Statement分支IF Statement条件分支LOOP Statement循环WHILE Statement循环REPEAT Statement循环FOR Statement查询驱动循环ITERATE Statement跳转继续LEAVE Statement跳出源码延伸阅读复合语句及全部控制流语句的语法规则集中在 SqlBaseParser.g4singleCompoundStatement、beginEndCompoundBlock、compoundBody、declareConditionStatement、declareHandlerStatement等规则其语义转换与执行逻辑位于 AstBuilder.scala感兴趣的读者可沿此路径深入阅读。【免费下载链接】sparkApache Spark - A unified analytics engine for large-scale data processing项目地址: https://gitcode.com/gh_mirrors/sp/spark创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考