Slick左连接后如何通过外键导航关联表字段?
要解决这个问题,我们需要在Slick的查询中通过嵌套左连接来处理层级关联,因为BOpt是Rep[Option[B]](对应A左连接B后的可选B记录),直接访问外键关联是行不通的——我们需要把C(以及后续的D)也通过左连接纳入查询,同时处理每一层的可选性。
1. 先明确表结构(假设的定义)
先假设你的Slick Table映射大致如下(方便后续代码对应):
// 实体类 case class A(id: Long, a: String) case class B(id: Long, b: String, cId: Long) case class C(id: Long, c: String, dId: Long) case class D(id: Long, d: String) // Slick Table映射 class As(tag: Tag) extends Table[A](tag, "A") { def id = column[Long]("id", O.PrimaryKey) def a = column[String]("a") def * = (id, a) <> (A.tupled, A.unapply) } val A = TableQuery[As] class Bs(tag: Tag) extends Table[B](tag, "B") { def id = column[Long]("id", O.PrimaryKey) def b = column[String]("b") def cId = column[Long]("c_id") // 外键指向C的id def * = (id, b, cId) <> (B.tupled, B.unapply) def fkR = foreignKey("FK_R", cId, Cs)(_.id) // 外键定义 } val B = TableQuery[Bs] class Cs(tag: Tag) extends Table[C](tag, "C") { def id = column[Long]("id", O.PrimaryKey) def c = column[String]("c") def dId = column[Long]("d_id") // 外键指向D的id def * = (id, c, dId) <> (C.tupled, C.unapply) } val C = TableQuery[Cs] class Ds(tag: Tag) extends Table[D](tag, "D") { def id = column[Long]("id", O.PrimaryKey) def d = column[String]("d") def * = (id, d) <> (D.tupled, D.unapply) } val D = TableQuery[Ds]
2. 构建嵌套左连接查询
我们可以通过Slick的for推导式,逐层添加左连接,处理每一层的可选性:
val query = for { // 第一步:A左连接B,得到可选的B记录 (aRow, bOpt) <- A joinLeft B on (_.id === _.id) // 第二步:基于B的可选外键cId,左连接C,得到可选的C记录 cOpt <- C joinLeftOn (bOpt.map(_.cId) === _.id) // 第三步:基于C的可选外键dId,左连接D,得到可选的D记录 dOpt <- D joinLeftOn (cOpt.map(_.dId) === _.id) } yield ( aRow.a, // A的a字段(必存在) bOpt.map(_.b), // B的b字段(可选,因为A左连接B) cOpt.map(_.c), // C的c字段(可选,依赖B是否存在) dOpt.map(_.d) // D的d字段(可选,依赖C是否存在) )
3. 关键逻辑解释
- 为什么不能直接写
BOpt.FK_R.c?BOpt是Rep[Option[B]],它代表的是数据库层面的可选记录,而fkR是Slick定义的外键关联(本质是一个查询),无法直接通过Option类型的Rep来访问。必须通过左连接将C表与B的可选记录关联,才能合法获取C的字段。 joinLeftOn的作用:
这是Slick专门针对“左表是可选记录”场景的语法,它允许我们用bOpt.map(_.cId)(可选的外键值)来匹配C表的主键,确保即使B不存在,查询也能正常返回(此时C的记录也是None)。
4. 另一种写法:先关联下层表再左连接到A
如果你更习惯先把B、C、D关联起来,再和A左连接,也可以这样写:
// 先把B、C、D依次内连接(如果外键约束保证关联存在),再和A左连接 val bcdJoin = B join C on (_.cId === _.id) join D on (_._2.dId === _.id) val aBcdJoin = A joinLeft bcdJoin on (_.id === _._1._1.id) val query = aBcdJoin.map { case (aRow, bcdOpt) => ( aRow.a, bcdOpt.map(_._1._1.b), bcdOpt.map(_._1._2.c), bcdOpt.map(_._2.d) ) }
这种写法更简洁,但前提是外键约束保证B一定关联到C,C一定关联到D——如果存在B存在但C不存在的情况(虽然外键约束不允许),这种写法会丢失对应的B记录,所以第一种嵌套左连接的方式更通用。
内容的提问来源于stack exchange,提问作者merma
相关产品推荐
相关产品推荐

