动态查询
在拖拽式报表平台等场景中,往往表名、列名、查询结构都是动态的,在编译期一无所知,而且经常需要生成带有多层子查询的甚至上千行的复杂查询,而传统的静态ORM和MyBatis为代表的XML模板无法胜任此场景的动态SQL构建,实际应用中我们往往需要:要么使用非常容易出错的字符串拼接;要么接入Calcite等重量级数据库内核前端。
在这种场景下,sqala提供了一个全新的解决方案:为动态查询构建场景专门设计一套DSL,动态构建器减少了静态查询场景的类型安全校验,但内部也沿用了sqala底层的SQL语法树等基础设施,因此也提供了SQL安全转义,且保证生成的SQL语法正确,调用形式既不像字符串拼接那样容易出错,也不像手动拼接语法树那样繁琐。
需要注意的是,动态查询构建场景与静态查询的场景完全不同,为了区分两种使用场景,sqala采用了完全不同的DSL风格,以更好的贴合使用场景,并且作为视觉区分。
动态查询构建器可以从import sqala.dynamic.dsl.*导入。
动态查询构建器依然是无副作用的,每次调用其中方法都会生成一个新的查询,而不是修改之前的查询。
构建查询
from方法创建一个查询,其参数通常是若干个使用table方法生成的表:
val q =
from(table("a"), table("b"))动态查询的表名使用字符串传递,所以其可以来自外界传参,sqala会对表名进行安全转义:
val tableNames = List("a", "b")
val tables = tableNames.map(n => table(n))
val q =
from(tables)as方法给表起别名:
val q =
from(table("a").as("t1"), table("b").as("t2"))from后,需要使用select方法传递若干个查询列(或者一个查询列的List),来完成查询创建,查询字段通常使用column方法创建,as方法给表达式起别名,多次调用会依次拼接:
val q =
from(table("a")).select(column("a", "x").as("col1"))生成SQL
sql方法用于生成SQL字符串,参数是方言和转义模式(参考数据库交互):
val q =
from(table("a")).select(column("a", "x").as("col1"))
val sql = q.sql(PostgresqlDialect, true)获取查询树
在动态构建场景下,往往需要对生成的SQL进行一些优化或者转换工作,但这些工作是无法在SQL字符串层面展开的,此时我们可以使用ast方法来查看sqala生成的查询语法树,如果您有编译器和数据库内核相关领域知识,可以查看sqala.ast包的相关定义,就可以使用sqala生成的语法树进行查询优化或转换工作:
val q =
from(table("a")).select(column("a", "x").as("col1"))
val queryTree: sqala.ast.statement.SqlQuery = q.ast当然,表和表达式也支持ast方法:
val t = table("a")
val tableTree: sqala.ast.table.SqlTable = t.ast
val e = column("c") == value(1)
val exprTree: sqala.ast.expr.SqlExpr = e.ast在查询树转换完毕后,我们可以这样来生成SQL:
val printer = PostgresqlDialect.printer(standardEscapeStrings)
// 或是printTable、printExpr等方法,您可以自行查看sqala.printer.SqlPrinter中的相关定义
printer.printQuery(queryTree)
val sql = printer.sql过滤
where方法传递一个表达式,来过滤数据,通常使用value来生成值表达式,多次调用使用AND来拼接表达式:
val q =
from(table("a"))
.where(column("a", "x") == value(1))
.select(column("a", "x").as("col1"))当然,==等运算符右侧也支持另一个列:
val q =
from(table("a"))
.where(column("a", "x") == column("a", "y"))
.select(column("a", "x").as("col1"))比较运算和函数的大部分使用规则与静态查询的静态表达式相同,但条件表达式为了动态场景特化,使用caseWhen方法传递若干个表达式(或一个表达式的List),采用CASE和WHEN的顺序传递,如果表达式个数是奇数则将最后一个置入ELSE:
val e =
caseWhen(
column("a") == value(1),
value(1),
column("a") == value(2),
value(2),
value(3)
)生成的SQL为:
CASE WHEN "a" = 1 THEN 1 WHEN "a" = 2 THEN 2 ELSE 3排序
orderBy方法传递若干个排序规则(或一个排序规则的List)生成排序,多次调用会依次拼接:
val q =
from(table("a"))
.where(column("a", "x") == column("a", "y"))
.select(column("a", "x").as("col1"))
.orderBy(column("a", "x").asc, column("a", "y").desc)排序规则支持与静态查询的排序规则相同。
分组
groupBy方法传递若干个表达式(或一个表达式的List)生成分组,多次调用会依次拼接:
from(table("a"))
.where(column("a", "x") == column("a", "y"))
.groupBy(column("a", "x"), column("a", "y"))
.select(count().as("c"))分组后过滤的having方法与where类似,不再赘述。
限制结果
limit和offset方法用来限制结果集:
from(table("a"))
.select(column("a", "x").as("col1")).limit(10).offset(10)关联表
join和on用来生成关联表:
val q =
from(
table("a").join(table("b")).on(column("a", "x") == column("b", "x"))
)支持的关联类型与静态查询关联表相同
子查询
subquery方法用于生成子查询表达式:
val q =
from(table("a"))
.where(
column("a", "x") ==
subquery(from(table("b")).select(count().as("c")))
)subqueryTable用于生成子查询表:
val q =
from(
from(table("a")).select(count().as("c")).as("t1")
).select(count().as("c"))any、all、exists用于生成带量词的子查询:
val q =
from(table("a"))
.where(
column("a", "x") ==
any(from(table("b")).select(column("x").as("x")))
)集合操作
union等方法用于集合操作:
val q1 = from(table("a")).select(column("x").as("x"))
val q2 = from(table("b")).select(column("x").as("x"))
val q = q1.union(q2)支持的集合操作与静态集合操作相同。