动态查询
之前介绍的都是偏向于编译期确定的查询构造,sqala可以提供类型安全以及自动反序列化的能力,但是在某些应用里(比如用户可定制的报表系统),执行的查询并不能在编译期事先知道,我们甚至在编译期不知道数据中有什么表,表中有什么字段,为了应对这种场景,sqala提供了一套动态查询DSL。
首先添加依赖:
libraryDependencies += "com.wz7982" % "sqala-dynamic_3" % "latest.integration"
动态查询DSL需要import sqala.dynamic.dsl.*
。
我们使用asTable
方法创建一个表,并且带入到查询里:
import import sqala.dynamic.dsl.*
val department = asTable[Department]
val q = select (department.id, department.name) from department where department.id == 1
我们也可以使用table
、column
两个方法来动态创建编译期无法感知到的表和字段,并且带入到查询里:
import import sqala.dynamic.dsl.*
val q = select (List(column("a"), column("b"))) from table("t") where column("a") == 1
sqala的动态查询不仅可以填写简单的字符串,我们可以传进一个SQL片段,unsafeExpr
方法会启用一个SQL解析器,并将其转换成SQL的语法树:
val q = select (List(unsafeExpr("a"), unsafeExpr("b"))) from table("t") where unsafeExpr("a = 1") && unsafeExpr("b") == 1
顾名思义,unsafeExpr方法是不安全的,其可能有SQL注入风险,请慎用!
下面我们来详细了解sqala的动态查询构造器。
SELECT
使用select
方法创建一个SELECT
语句:
val s = select(List(column("a"), column("b") as "column"))
链式调用select
会在生成SQL时依次拼接。
假设我们有一个运行时获取的字段列表,需要放入查询中,可以这样来编写:
// 假设列表是运行时获取的
val columnList: List[String] = List("a", "b")
val s = select(columnList.map(column(_)))
FROM
使用from
方法配合table
生成FROM
子句:
val s = select (column("a"), column("b")) from table("t")
WHERE
使用where
方法生成WHERE
子句:
val s = select (column("a"), column("b")) from table("t") where column("a") == 1
假设我们有一个运行时确定的值列表,我们需要在查询中使用OR
拼接,可以这样编写:
// 假设列表是运行时获取的
val values = List("x", "y", "z")
val condition = values.map(v => column("a") == v).reduce((x, y) => x || y)
val s = select (column("a")) from table("t") where condition
生成的SQL形如:
SELECT a FROM t WHERE a = 'x' OR a = 'y' OR a = 'z'
链式调用where
会使用AND
拼接条件。
JOIN
使用join
、leftJoin
、rightJoin
、fullJoin
配合on
方法,生成一个JOIN
表,然后放入from
中:
val t = table("a") join table("b") on column("a.x") == column("b.y")
val s = select (column("*")) from t
GROUP BY
使用groupBy
方法生成GROUP BY
子句:
val s = select (List(column("a"), sum(column("b")))) from table("t") groupBy List(column("a"))
链式调用groupBy
会依次拼接。
使用having
方法生成HAVING
子句:
val s = select (List(column("a"), sum(column("b")))) from table("t") groupBy List(column("a")) having sum(column("b")) > 1
ORDER BY
使用orderBy
方法生成GROUP BY
子句,配合表达式的asc
、desc
方法使用:
val s = select (List(column("a"), column("b"))) from table("t") orderBy List(column("a").asc, column("b").desc)
链式调用orderBy
会依次拼接。
LIMIT
使用limit
和offset
方法创建LIMIT
子句:
val s = select (List(column("a"), column("b"))) from table("t") limit 10 offset 10
生成SQL时会根据数据库方言分别生成不同的SQL。
UNION
使用union
、unionAll
、except
、exceptAll
、intersect
、intersectAll
方法生成集合查询:
val s1 = select (column("a")) from table("t1")
val s2 = select (column("b")) from table("t2")
val s = s1 union s2
子查询
子查询可以作为表达式的一部分出现,比如:
val subQuery = select (max(column("a"))) from table("t")
val s = select (List(column("a"))) from table("t") where column("a") < subQuery
此外也支持IN
、ANY
、SOME
、ALL
、EXISTS
等子查询相关谓词。
FROM
中的子查询,需要我们使用as
方法给字段和查询起别名,然后再次引用:
val subQuery = select (List(column("x"), column("y"))) from table("t1") as "q1"
val q = select (List(column("q1.x"), column("q1.y"))) from subQuery
表达式
动态查询构造器的表达式,大部分的用法都与静态查询的表达式部分类似,可以参照其中的说明,不同的是CASE
表达式的用法,其是为了方便运行时动态构建CASE
:
val caseExpr = `case`(List(column("a") == 1 -> 1, column("a") == 2 -> 2), 0)
为了在高度动态的查询构造场景中保持易用性,动态查询的表达式并没有采用严格的类型安全设计。
生成SQL
在创建好查询之后,如果我们要发送给JDBC处理,还需要获取生成的SQL,我们可以使用sql
方法配合sqala.printer
中的Dialect
生成SQL:
import sqala.printer.Dialect
val s = select (List(column("a"), column("b"))) from table("t")
val sql: (String, Array[Any]) = s.sql(MySqlDialect)
sql
方法返回一个二元组,第一项是生成的SQL语句,第二个参数是其中的参数。