分支/If|Else
传统 SQL 是不支持分支语句的,因此如果想要在 SQL 中添加判断或过滤条件,往往需要多条 SQL 拼接才能完成需求。但是在 Byzer-lang 中配合宏命令做到了分支语句的支持,允许我们正常使用 if/else,强化拓展了语言自身的能力。 1. 基本用法 一段最简单的分支语法示例: set a = "wow
传统 SQL 是不支持分支语句的,因此如果想要在 SQL 中添加判断或过滤条件,往往需要多条 SQL 拼接才能完成需求。但是在 Byzer-lang 中配合宏命令做到了分支语句的支持,允许我们正常使用 if/else,强化拓展了语言自身的能力。
1. 基本用法
一段最简单的分支语法示例:
set a = "wow,jack";
!if ''' split(:a,",")[0] == "jack" ''';
select 1 as a as b;
!else;
select 2 as a as b;
!fi;
select * from b as output;
结果为:a:2。
!if/!else
在 Byzer-lang 中并非关键字,都是 宏函数。
在上面的示例中:
-
先通过变量申明得到一个变量
a
。 然后在宏函数!if
只接受一个位置参数,因为是一个宏函数,调用的最后必须加上分号;
。 -
!if
后面接一个文本参数,该文本的内容是一个表达式。在表达式里可以使用 Spark SQL 支持所有函数。比如上面的例子是split
函数。表达式也支持使用 register 句式注册的函数,本文后面部分会有使用示例。 -
在条件表达式中使用
:
来标识一个变量。变量来源于 set 句式。比如示例中表达式的:a
变量对应的值为 "wow,jack" 。如果表达式:
split(:a,",")[0] == "jack"
返回 true , 那么会执行:
select 1 as a as b;
这条 select
语句会通过 as 语法
生成一个临时表 b
, 该表只有一个 a
字段,并且值为 1
.
如果返回 false, 那么会执行:
select 2 as a as b;
这条 select
语句会通过 as 语法
生成一个临时表 b
, 该表只有一个 a
字段,并且值为 2
.
示例代码最后通过下列语句对 b
表进行输出:
select * from b as output;
从上面的例子可以看到,Byzer-lang 的条件判断语句具有以下特色:
- 语法设计遵循 SQL 的一些原则。比如采用
and/or
替代&&/||
。使用select
语句做变量赋值 - 兼容 Spark SQL 函数
- 支持用户自定义函数(参看文章后半部分)
2. 分支语句嵌套
Byzer-lang 也支持分支语句的嵌套。
示例:
set a="jack,2";
!if ''' select split(:a,",")[0] as :name, split(:a,",")[1] as :num;
:name == "jack" and :num == 3
''';
select 0 as a as b;
!elif ''' select split(:a,",")[1] as :num; :num==2 ''';
!if ''' 2==1 ''';
select 1.1 as a as b;
!else;
select 1.2 as a as b;
!fi;
!else;
select 2 as a as b;
!fi;
select * from b as output;
在上述代码中,!if
表达式里变得复杂了:
!if ''' select split(:a,",")[0] as :name, split(:a,",")[1] as :num;
:name == "jack" and :num == 3
''';
和第一个例子不同之处在于多了一个 select
句法结构, 该结构如下:
select split(:a,",")[0] as :name, split(:a,",")[1] as :num;
在上面的示例代码中,通过 select
生成了 :name
和 :num
两个变量。
接着,用户可以使用 ;
对提交表达式的不同语句进行分割。 第二条语句是对新产生的两个变量进行条件表达式判定:
:name == "jack" and :num == 3
Byzer-lang 会在执行时对变量 :num
自动进行类型转换,转换为数字。
3. 表达式中变量的作用域
在 !if/!elif
里申明的变量有效范围是整个 !if/!fi
区间。子 !if/!else
语句可以使用上层 !if/!else
语句的变量。
对于以下示例:
set name = "jack";
!if '''select :name as :newname ;:name == "jack" ''';
!if ''' :newname == "jack" ''';
!println '''====1''';
!else;
!println '''====2 ''';
!fi;
!else;
!println '''=====3''';
!fi;
该语句输出为 ====1
,子 !if
语句中使用了上层 !if
语句中的 select
产生的 :newname
变量。
同样的,用户也可以在分支语句内部的代码中引用条件表达式里的变量,比如:
set name = "jack";
!if '''select concat(:name,"dj") as :newname ;:name == "jack" ''';
!if ''' :newname == "jackdj" ''';
!println '''====${newname}''';
select "${newname}" as a as b;
!else;
!println '''====2 ''';
!fi;
!else;
!println '''=====3''';
!fi;
select * from b as output;
在该示例中,用户在 select
, !println
语句里通过 ${}
引用了 !if
或者 !elif
里声明的变量。
4. 结合 defaultParam 变量
条件分支语句可以与强大的变量声明语法结合。
这里主要介绍和 defaultParam 变量 的结合。
比如:
set a = "wow,jack" where type="defaultParam";
!if ''' split(:a,",")[0] == "jack" ''';
select 1 as a as b;
!else;
select 2 as a as b;
!fi;
select * from b as output;
此时代码的输出会是 2
。 但是如果在前面加一句:
set a = "jack,";
set a = "wow,jack" where type="defaultParam";
!if ''' split(:a,",")[0] == "jack" ''';
select 1 as a as b;
!else;
select 2 as a as b;
!fi;
select * from b as output;
这个时候会输出 1
。 这意味着,用户可以通过执行脚本时,动态在脚本最前面添加一些变量就可以覆盖掉脚本原有的变量,从而实现更加动态的控制脚本的执行流程。
5. 在条件表达式中使用自定义函数
前面提到, Byzer-lang 支持使用自定义 UDF 函数,经过注册的 UDF 函数,也可以用在条件分支语句中的条件表达式中。
示例:
register ScriptUDF.`` as title where
lang="scala"
and code='''def apply()={
"jack"
}'''
and udfType="udf";
!if ''' title() == "jack" ''';
select 1 as a as b;
!else;
select 2 as a as b;
!fi;
select * from b as output;
6. !if/!else 子语句中表的生命周期问题以及解决办法
在 Byzer-lang 中,表的生命周期是 session 级别的。这意味着在一个 session 中, 表都是会被自动注册在系统中的,除非被删除或者被重新定义了亦或是 session 失效。 session 级别的生命周期主要配套 notebook 使用,方便用户进行调试。 然而,这在使用 !if/!else
的时候则会有困惑发生。
来看下面这个例子:
!if ''' 2==1 ''';
select 1 as a as b;
!else;
!fi;
select * from b as output;
当第一次运行的时候,因为 2==1
会返回 false, 所以会执行 !else
后面的空分支。 接着我们再引用 b
进行查询,系统会报错,因为没有表 b
。
于是我们修改下条件,将 2==1
修改为 1==1
时,此时,系统执行了 select 1 as a as b;
, 产生了 b
表, 整个脚本正常运行。
再次,我们将 1==1
再次修改为 2==1
此时,系统输出了和条件 1==1
时一样的结果。这显然不符合逻辑。
原因在于,系统记住了上次运行的 b
,所以虽然当前没有执行 select
语句,但是依然有输出,从而造成错误。
解决办法有两个:
- 请求参数设置
sessionPerRequest
,这样每次请求表的生命周期都是request
。 - 在脚本里备注
set __table_name_cache__ = "false";
让系统不要记住表名,逻辑上是每次执行完脚本后,系统自动清理产生运行产生的临时表。
其中第二个办法的使用示例如下:
set __table_name_cache__ = "false";
!if ''' 2==1 ''';
select 1 as a as b;
!else;
!fi;
select * from b as output;
更多推荐
所有评论(0)