# 查询
库内置了一系列中缀方法,来构建查询,比如:
import easysql.dsl.*
val s = select (user) from user where user.name === "x" orderBy user.id.asc
然后我们可以使用sql
方法,传入数据库方言的枚举,来生成sql语句:
val sql: String = s.sql(DB.MYSQL)
如果不需要使用多种数据库,每次生成sql都要传入一个数据库类型,十分不方便,这个时候,我们可以创建一个given
,然后使用toSql
方法来生成sql。
import easysql.dsl.*
import easysql.database.*
given DB = DB.MYSQL
val s1 = select (user.id) from user
val sql1 = s1.toSql
val s2 = select (user) from user where user.name === "x"
val sql2 = s2.toSql
# select
使用select方法,传入若干表达式或使用asTable创建的表,来生成select子句:
import easysql.dsl.*
// 后续的select方法调用,会把字段追加在后面
val s = select(user.id).select(user.name)
表结构和表达式都可以传入select中,生成sql时会进行自动展开:
import easysql.dsl.*
val s = select (user, count())
select列表中的字段类型会在子查询等时机保证类型安全,并且在与数据库交互时自动推断结果类型,但要求你的查询字段是可以在编译期确定的。如果查询字段是运行期动态传入的,需要使用dynamicSelect
:
import easysql.dsl.*
// 假设此处查询的列是用户传入参数
val list = List("x", "y", "z")
// 把字符串列表转换为字段列表,并传入dynamicSelect中
val s = dynamicSelect(list.map(col)*)
# from
使用中缀方法from
,并传入表结构:
import easysql.dsl.*
val s = select (user) from user
如果你只需要构建简单的单表查询,select子句可以省略:
import easysql.dsl.*
val s = from(user)
如果你的表名也是运行期动态确定的,可以使用table
方法:
import easysql.dsl.*
val s = select (col("c1")) from table("t1")
from方法也可以传入一个子查询,会在后续子查询部分进行说明。
表可以起别名,我们可以用别名表来处理自连接:
import easysql.dsl.*
val t1 = user as "t1"
val t2 = user as "t2"
val s = select (t1, t2) from t1 join t2 on t1.id === t2.id
如果别名是运行期确定,要使用unsafeAs
方法。
# where
使用中缀方法where
配合表达式来生成查询条件:
import easysql.dsl.*
val s = select (user) from user where user.id >= 1 && user.name === "x"
如果你的查询条件都是使用AND
来连接,那么也可以选择使用多个where
:
import easysql.dsl.*
val s = select (user) from user
val sWhere = s.where(user.id.in(1, 2, 3)).where(user.name === "x")
某些需要视情况而动态拼接的查询条件,可以在where
中传入一个Boolean值或者返回Boolean的函数:
import easysql.dsl.*
val name: Option[String] = ???
val s = select (user) from user
val sWhere = s.where(name.nonEmpty, user.name === name)
# order by
表达式类型中,有asc
、desc
两个方法,用来配合orderBy
创建排序规则:
import easysql.dsl.*
val s = select (user) from user orderBy (user.id.asc, user.name.desc)
# group by与having
使用groupBy
做数据分组,having
做分组后的筛选:
import easysql.dsl.*
val s = select (user.name, count()) from user groupBy user.name having count() > 1
除了普通的group by之外,还支持几个特殊的group by形式:
rollup
:select (user.id, user.name, count()) from user groupBy rollup(user.id, user.name)
cube
:select (user.id, user.name, count()) from user groupBy cube(user.id, user.name)
groupingSets
(参数类型支持表达式、表达式元组以及Unit):select (user.id, user.name, count()) from user groupBy groupingSets((user.id, user.name), user.name, ())
上面的各种group by形式可以组合调用。
# distinct
使用distinct
做数据去重:
import easysql.dsl.*
val s = select(user.name).from(user).distinct
# limit
使用limit和offset两个方法,来限制结果集:
import easysql.dsl.*
val s = select (user) from user limit 10 offset 100
当只调用其中一个方法的时候,limit的缺省值为1,offset的缺省值为0。
生成sql时会根据方言的不同生成不同的语句
# join
内置了join
、leftJoin
、rightJoin
、innerJoin
、fullJoin
、crossJoin
方法,配合on
方法来连接表:
import easysql.dsl.*
val s = select (a, b) from a join b on a.x === b.y
可以像真正的sql一样,使用小括号来限制表连接的顺序:
import easysql.dsl.*
val s = select (a, b, c) from a join (b leftJoin c on b.y === c.z) on a.x === b.y
# 子查询
from中的子查询:
使用as方法对子查询起别名,并需要对非字段类型表达式起别名。然后使用字段名或者别名作为属性来调用子查询的字段,子查询的字段类型会自动推断。
import easysql.dsl.* val sub = select (user.id, count() + sum(user.id) as "c1") from user as "sub" val s = select (sub.id, sub.c1) from sub
join中的子查询与from的类似,此处不再赘述。
可以使用
fromLateral
、joinLateral
等来调用lateral
子查询。表达式中的子查询:
只返回一列的子查询可以被用在表达式里,比如:
import easysql.dsl.* val sub = select (max(user.id)) from user val s = select (user) from user where user.id < sub
支持
some
、any
、exists
、notExists
、all
等子查询谓词:import easysql.dsl.* val sub = select (max(user.id)) from user val s = select (user) from user where exists(sub)
标量子查询
只返回一列一行的查询可以被放入select列表中,这被称作标量子查询,我们需要使用
toExpr
把子查询转换为Expr:import easysql.dsl.* val sub = select (user.id) from user limit 1 val s = select (sub.toExpr as "c1", user.name as "c2") from user
子查询写在表达式的左侧时,也可以使用toExpr方法进行转换
# for update
使用forUpdate
方法来给查询加锁:
import easysql.dsl.*
val s = select(user).from(user).forUpdate
在sqlserver中会在FROM子句后生成WITH (UPDLOCK)。
# 生成sql
上文的链式调用,只是构建出了sql语法树,并未真正生成sql语句,我们需要一个终止操作来生成sql:
import easysql.dsl.*
given DB = DB.MYSQL
val s = select (user) from user orderBy user.id.asc limit 10
val sql: String = s.toSql
val countSql: String = s.toCountSql
val pageSql: String = s.toPageSql(10, 1)
toSql
会一比一的生成sql语句。
为了避免性能浪费,toCountSql
会拷贝语法树副本,把limit信息和order by信息去掉,并把select列表替换为COUNT(*),这适用于查询中没有group by的情况:
SELECT COUNT(*) AS `count`
FROM `user`
toPageSql
,顾名思义,就是生成分页的查询,第一个参数为每页条数,第二个参数为页码,会依据这两个参数替换语法树副本的信息。
# union查询
支持union
、unionAll
、except
、exceptAll
、intersect
、intersectAll
来将两个查询拼接在一起:
import easysql.dsl.*
val s1 = select (user.name) from user where user.id === 1
val s2 = select (user.name) from user where user.id === 2
val union = s1 union s2
用于拼接的查询,select中的字段数目与字段类型必须一致,否则无法通过编译。
# with查询
可以使用cte
函数来创建一个with查询:
import easysql.dsl.*
val q = cte {
val q1 = select (user.name as "n1") from user where user.id > 1 as "q1"
val q2 = select (user.name as "n2") from user where user.id > 2 as "q2"
commonTable(q1, q2).query {
select (q1.n1, q2.n2) from q1 join q2
}
}
在cte
函数中创建公共查询,并使用commonTable
将公共查询引入并创建一个with查询表达式,使用query
创建最终的查询即可(mysql和pgsql使用递归查询在调用链添加.recursive)。
cte
是一个上下文函数,cte
中定义的查询在互相引用时,最终生成的sql会把查询别名当做一个表名称使用,而不是嵌套查询本身。
# values临时表
values临时表最大的用处是被放入union中,我们可以使用Tuple
或者List[Tuple]
来调用:
import easysql.dsl.*
val union = select (user.id, user.name) from user union (1, "x") union List((2, "y"), (3, "z"))
# 获取生成的语法树
上述的查询类型,均支持使用getAst
方法来获取查询生成的语法树,方便后续的自定义处理,处理获取到的语法树需要有一些编译原理相关知识,感兴趣的用户可以自行查看easysql.ast
包中的语法树定义。
import easysql.dsl.*
val s = select (user) from user where user.id === 1
val ast: SqlQuery = s.getAst
在处理一些特殊的需求时,可能需要对生成的查询进行深度定制,这种场景下,则可以在上面生成的语法树基础上进行二次处理。