【问题标题】:How to write readable nested join queries with Slick 3.0如何使用 Slick 3.0 编写可读的嵌套连接查询
【发布时间】:2016-08-24 10:32:46
【问题描述】:

此代码正在创建一个查询,用于在 Web 后端检索用户的个人资料。它创建一个查询,将必要的信息组装成一个 DTO(它只是一个案例类),随后以 JSON 的形式发回。

  def getProfile(userId: Long)={
    val q = for{
    ((((u,p),a),b), ba) <- filterById(userId) join
               People on (_.personId === _.id) joinLeft
               Addresses on (_._2.addressId === _.id) joinLeft
               Businesses on (_._1._2.businessId === _.id) joinLeft
               Addresses on (_._2.flatMap(_.addressId) === _.id)
    }yield(p, a, b, ba)

    db.run(q.result.headOption).map{ _.map{case(p,a,b,ba) =>
      val business = b match {
      case Some(b) => Some(dtos.Business(b.name, b.abn, b.adminFee, ba, b.id))
      case _ => None
    }

    dtos.ProfileInfo(p, a, business)
  }}
}

我已将结果处理 (db.run(...)) 仅用于上下文。

我正在寻找一种更易读的方式来表达查询结构。

我的阅读体验是“等等,什么??...on (_._1._2.flatMap(_.addressId) ....这是在做什么??为什么 flatmap 在那里而不是在这里:on (_._1._2.businessId。这些实际上是直截了当的事情,但不要完全不直接阅读。

我正在寻找一种表达方式,不需要阅读此版本所需的扣除量。我必须“推断”什么是_._1._2,以及为什么需要将其展平,这与等效的SQL无关。

注意事项:

  • 此代码来自我正在扩展的现有应用程序(不是我编写的)。
  • 用户、人员、地址、企业是(显然?)表格。
  • 人和企业都有地址。
  • 用户有一个人(*),人有一个企业
  • filterByUserId(userId) 基本等同于Users.filter(_.id === userId)
  • 等效的 SQL 是:

    select p.*, a1.*, b.*, a2.* from Users u 
    innerJoin People p on (u.personId == p.id) 
    leftJoin Addresses a1 on (p.addressId == a1.id) 
    leftJoin Businesses b on (p.businessId == b.id) 
    leftJoin Addresses a2 on ( b.addressId == a2.id)
    

【问题讨论】:

  • 我很乐意用简洁换取可读性。我一直在寻找以增量方式编写查询的方法,但看不到如何。我想摆脱 LHS 上的嵌套元组和 RHS 上的嵌套 .

标签: scala slick slick-3.0


【解决方案1】:

你应该尝试这样的事情:

val q = Users join People joinLeft Addresses joinLeft Businesses joinLeft Addresses on {
  case ((((u, p), a), b), ba) => u.personId === p.id && p.addressId === a.flatMap(_.id) && p.businessId === b.flatMap(_.id) && b.flatMap(_.addressId) === ba.id
} map {
  case ((((u, p), a), b), ba) => (p, a, b, ba)
}

另一种解决方案是在不使用 for 理解的情况下进行连接,因此您不必使用下划线从元组中提取值:

val q = Users join People on {
  case (u, p) => u.personId === p.id
} joinLeft Addresses on {
  case ((u, p), a) => p.addressId === a.id
} joinLeft Businesses on {
  case (((u, p), a), b) => p.businessId === b.id
} joinLeft Addresses on {
  case ((((u, p), a), b), ba) => b.flatMap(_.addressId) === ba.id
} map {
  case ((((u, p), a), b), ba) => (p, a, b, ba)
}

您尚未提供数据的完整定义,因此我无法对这些解决方案进行全面测试,但这应该能让您对在 Slick 中定义连接的不同方式有所了解。让我知道这是否有帮助。

【讨论】:

  • 嗯,我没有你的数据结构,所以我不得不在这里猜测一些事情。我假设在使用leftJoin 之后,连接子句的右侧将是Option。通过使用.?,您也可以将常规列提升到Option,因此它们现在具有可比性。我现在建议的是,您应该删除所有那些 .?.map 操作,并尝试通过增量编译来完成代码并尝试逐步修复每个错误。 PS 我已经编辑了我的帖子,并为您的问题提供了另一种可能的解决方案。
  • 谢谢 - 这一切都很有趣和有希望:我会看看我能做些什么。
  • 我认为您的第二个解决方案(由我调整 - 对我来说很容易,因为我有编译器 :))值得赏金:它摆脱了嵌套元组 引用 i> 在 RHS 上。我不得不说我仍然希望有一些更优雅的东西...... LHS 上的嵌套元组字符串仍然相当不整洁,但它确实符合我的交换简洁性标准。我现在将尝试使原始解决方案起作用。
  • 现在我根据编译器告诉我的内容调整了您的第一个解决方案。非常感谢您的帮助。如果您不介意评论为什么第一个比第二个需要更多flatMap(),那就太棒了。 (也许我应该为此提出一个单独的问题?)
  • 有趣的更新:第一个变体可以编译,但实际上并没有工作。它返回错误的配置文件!第二个确实有效。我想知道它们有什么不同!?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-25
  • 2014-09-24
  • 1970-01-01
  • 2020-12-02
  • 2014-01-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多