连表查询
查询多个数据源
join(...) 用于构建可组合的 FROM source tree。每个右侧 source 对应一个明确的关系方法;所有关系建立完成后,调用 select { ... } 把 source 转成可执行查询。
kotlin
val query = User().join(Order()) { user, order ->
leftJoin { user.id == order.userId }
.select { [user.id, user.name, order.status] }
.where { user.enabled == true }
}
val rows = query.toList()Mysql
SELECT `user`.`id`, `user`.`name`, `order`.`status`
FROM `user`
LEFT JOIN `order` ON `user`.`id` = `order`.`user_id`
WHERE `user`.`enabled` = :enabledsource 参数只在外层 lambda 中声明一次,关系和查询子句直接捕获这些参数。leftJoin、select、where 和 orderBy 不需要重复声明参数。
选择连接类型
innerJoin、leftJoin、rightJoin 和 fullJoin 接收连接条件。笛卡尔积使用无条件的 crossJoin()。JOIN 不再提供隐式连接类型,也没有单独的 on { ... } 方法。
multiple sources
val rows = User().join(Order(), Product()) { user, order, product ->
innerJoin { user.id == order.userId }
.leftJoin { order.productId == product.id }
.select {
[user.id, product.name.alias("productName"), order.amount]
}
}.toList()cross join
val combinations = User().join(Region()) { user, region ->
crossJoin()
.select { [user.id, region.name.alias("regionName")] }
}.toList()fullJoin 是否可用取决于数据库方言,见 📚方言支持。
选择、过滤、分组和排序
select 是原始 JOIN source 与查询之间的类型边界。调用 select 后,可以继续使用普通查询的 where、by、groupBy、having、orderBy、distinct、limit、lock、patch 和结果方法。
kotlin
val rows = User().join(Order()) { user, order ->
leftJoin { user.id == order.userId }
.select {
[
user.id.alias("userId"),
f.count(order.id).alias("orderCount")
]
}
.where { user.enabled == true }
.groupBy { user.id }
.having { f.count(order.id) > 0 }
.orderBy { it.orderCount.desc() }
}.toList()where、groupBy 和 having 读取 joined Source 字段。orderBy 读取 select 后的 Context,因此可以访问 orderCount 这类选中 alias。
连接选中查询或分页 source
任何 KSelectable 都可以作为 JOIN operand,包括普通 select、已选中的 JOIN、union 和 offset page。对应 source 参数会暴露该 operand 的选中输出字段。
selected source
val paidOrders = Order()
.select { [it.userId, it.status] }
.where { it.status == 1 }
val rows = User().join(paidOrders) { user, order ->
leftJoin { user.id == order.userId }
.select { [user.id, user.name, order.status] }
}.toList()finite page source
val recentOrders = Order()
.select { [it.id, it.userId, it.createdAt] }
.orderBy { it.createdAt.desc() }
.page(pageIndex = 1, pageSize = 100)
val rows = User().join(recentOrders) { user, order ->
innerJoin { user.id == order.userId }
.select { [user.id, order.id.alias("orderId")] }
}.toList()只有 OffsetPageQuery 可以继续作为关系 source;带总数分页和游标分页是执行阶段,不能作为 JOIN operand。
构建嵌套 JOIN tree
内层 JOIN block 以关系方法结束时会保留原始 JoinSource。这个 source 可以作为另一层 JOIN 的左侧或右侧,并保留括号分组。
right nested
val companyRegion = Company().join(Region()) { company, region ->
innerJoin { company.regionId == region.id }
}
val rows = User().join(companyRegion) { user, company, region ->
leftJoin { user.companyId == company.id }
.select {
[user.id, company.name, region.name.alias("regionName")]
}
}.toList()生成的 SQL 形态是 User LEFT JOIN (Company INNER JOIN Region)。原始 JoinSource 不能直接执行、过滤、排序或分页,外层必须先调用 select。
组合已选中的 JOIN
JOIN select 返回 KSelectable,可以继续作为派生 source、参与 union、用于 INSERT SELECT,或再次 join。
derived
val joined = User().join(Order()) { user, order ->
leftJoin { user.id == order.userId }
.select { [user.id, order.status.alias("orderStatus")] }
}
val active = joined
.select { [it.id, it.orderStatus] }
.where { it.orderStatus == 1 }
.toList()union
val all = union(joined, joined)
.select { [it.id, it.orderStatus] }
.toList()自连接与重复输出名
自连接的两个 operand 即使使用同一种 KPojo 类型,也由不同 source 参数表示。建议使用体现角色的 alias。
aliases
val hierarchy = Employee().join(Employee()) { manager, report ->
leftJoin { manager.id == report.managerId }
.select {
[
manager.id.alias("managerId"),
report.id.alias("reportId")
]
}
}.toList()确实需要重复名称时,opt-in 确定性后缀分配。第一次出现保留原名,后续值使用 _1、_2 等后缀。
duplicate names
import com.kotlinorm.annotations.UnsafeProjectionOverride
@OptIn(UnsafeProjectionOverride::class)
val rows = User().join(Company()) { user, company ->
leftJoin { user.companyId == company.id }
.select { [user.id, company.id] }
}.toList()
val userId = rows.first().id
val companyId = rows.first().id_1显式名称保留和 Context 遮蔽规则见 📚投影。
分页 JOIN 查询
分页在 select 后调用。offset page 直接返回记录;需要总数时,在该 page 上调用 withTotal(),结果是命名的 PageResult。
kotlin
val query = User().join(Order()) { user, order ->
innerJoin { user.id == order.userId }
.select { [user.id, order.status] }
.orderBy { it.id.asc() }
}
val records = query.page(pageIndex = 1, pageSize = 20).toList()
val page = query.page(pageIndex = 1, pageSize = 20).withTotal().toList()
val total = page.total
val pageRecords = page.records游标和派生分页契约见 📚排序、分页与聚合。
选择结果形态和数据库
JOIN 查询和普通 select 一样支持生成投影、DTO、Map、单行结果方法和显式 KronosDataSourceWrapper。
wrapper
val rows = User().join(Order()) { user, order ->
leftJoin { user.id == order.userId }
.select { [user.id, order.status] }
.db(order to "archive")
}.toMapList(customWrapper)终端方法行为见 📚结果方法。