SQL 中很缺乏条件分支能力。为此,一般大家都会通过如下两种方式解决:
Byzer 在语言层面支持条件分支。
下面是一段典型的分支语句:
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 变量的变化, 我们会执行不同的语句,导致 b 表结果发生变化。
在 Byzer 中,if/else 并非关键字,都是一个宏函数。既然是一个宏函数,那么为了符合 Byzer 语法规范,那么每个命令后面都需要添加';'表示这个命令的结束。
我们来细细看看前面的 if 语句:
!if 命令后面接一个文本参数,该文本的内容是一个表达式。在表达式里,我们可以使用大部分SQL支持的函数。比如上面的例子是split函数。 我们使用":"来标识一个变量。变量来源于set语法。比如示例中表达式的:a变量对应的值为"wow,jack",他是通过set语法来设置的。
从上面的例子可以看到,Byzer 的条件表达式语句具有以下特色:
下面的 Byzer 代码示例具有复杂的分支嵌套以及更加复杂的条件表达式:
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
''';
和上个例子有些不同,因为我们要对:a变量进行多次处理,为了使得最后的表达式更加简单,Byzer 支持通过select语法来做变量赋值。语句
select split(:a,",")[0] as :name, split(:a,",")[1] as :num;
通过该语句,我们得到了:name 和:num两个变量。之后通过";"表示该语句的结束。在后续的语句中我们就可以引用对应的变量了:
:name == "jack" and :num == 3
Byzer 会对:num自动进行类型转换。
比如我们加载了张表,然后我们通过 b_count 获得该表的数据量,然后根据数据量决定后续的逻辑,此时代码就可以写成下面这个样子:
select 1 as a as mockTable;
set b_count=`select count(*) from mockTable ` where type="sql" and mode="runtime";
!if ''':b_count > 1 ''';
select 1 as a as final_table;
!else;
select 2 as a as final_table;
!fi;
select * from final_table as output;
在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语句中使用了上面的 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;
我们可以在if/else的子语句里,比如select,!println等语句里通过原先的'${}'符号引用!if或者!elif里申明的变量。
条件分支语句结合强大的set语法,其实可以做很多有意思的事情,比如:
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. 也就是用户可以通过添加set变量覆盖已经存在的变量从而控制脚本的执行。
另外,Byzer 也支持使用自定义UDF函数,并且在if语句中也是可以使用的。比如:
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;
这意味着你可以通过代码获取某个接口的值(或者特定的处理逻辑),来决定脚本的最后执行情况。有木有很强大。
因为在 Byzer 里有session的概念。当一个表被使用过后,系统会自动记住。这样可以很方便的用户进行调试。 但是当使用if/else的时候则会有困惑发生。
我们来看下面这个例子:
!if ''' 2==1 ''';
select 1 as a as b;
!else;
!fi;
select * from b as output;
当一次运行的时候,系统会报错,因为没有表b. 如果我们将2==1 修改为 1==1 时,则系统正常运行。 然后我们将1==1 再次修改为2==1 此时系统依然正常运行。 因为系统记住了上次运行的b,所以虽然当前没有执行select语句,但是依然有输出,从而造成错误。解决办法有两个:
第二种方式我们推荐,使用如下:
set __table_name_cache__ = "false";
!if ''' 2==1 ''';
select 1 as a as b;
!else;
!fi;
select * from b as output;
指的注意的是,!fi 如果是最后一条语句,不会有任何输出,如果你希望看到某张表的数据,需要显示的在最后加一条 select 语句。
Byzer 通过引入了分支条件语句,结合Byzer Man:Byzer 模板编程入门 以及 Byzer Man:Byzer 模块化编程 ,极大的提升了 Byzer 语言的灵活性和表达能力。