如何在 Scala Slick 中运行补丁/部分数据库更新?

Posted

技术标签:

【中文标题】如何在 Scala Slick 中运行补丁/部分数据库更新?【英文标题】:How do you run a patch/partial database UPDATE in Scala Slick? 【发布时间】:2015-08-31 02:47:08 【问题描述】:

我们想使用 Slick (3.0.0) 运行补丁/部分 UPDATE,以便我们只修改记录中的一些字段。究竟哪些字段将被准确更新只有在运行时才能知道。

例如,对于REST PATCH request。

目前我们首先运行SELECT 以获取原始记录,然后运行UPDATE,但最好在单个SQL 语句中执行此操作。

类似这样的:

def patchPerson(name: Option[String], age: Option[Int]) = 
   people.filter(_.name === "M Odersky")
       .map(p => 
           (name, age) match 
              case (Some(_), Some(_)) => (p.name, p.age)
              case (Some(_), None)    => (p.name)
              case (None   , Some(_)) => (p.age)
           
       )
       .update(
           (name, age) match 
              case (Some(_), Some(_)) => (name.get, age.get)
              case (Some(_), None)    => (name.get)
              case (None   , Some(_)) => (age.get)
           
       )

(请忽略这里丑陋的代码)

上面没有编译并出现以下错误消息:

找不到匹配的形状。 Slick 不知道如何映射给定的 类型。可能的原因: Table[T] 中的 T 与您的 * 不匹配 投影。或者您在查询中使用不受支持的类型(例如 scala 列表)。所需级别:slick.lifted.FlatShapeLevel 源类型: 对象解包类型:T 打包类型:G

还有:

方法映射的参数不足:(隐式形状: slick.lifted.Shape[_ <: slick.lifted.flatshapelevel t g>

我认为这是因为 Slick 期望元组长度和类型与 filterupdate 函数的结果相匹配。

我们已经尝试使用 Slick heterogeneous list 类,但这似乎也期望长度和类型匹配。

有没有办法在 Slick 中编写此代码,以便我们可以通过一次数据库调用更新记录中的任意数量的字段?

【问题讨论】:

您找到解决方案了吗? 只运行原生 SQL 怎么样? 我已经添加了赏金,因为我遇到了与作者完全相同的问题。 @LukasEder 如果您不知道要修补哪些密钥,那将无法正常工作。例如。我有一个大的用户案例类要更新,我想要一个通用的 PATCH 端点,我可以简单地向它发送一个更新的字段。 我找到了一个类似问题的答案。 ***.com/a/42004236/7505973 【参考方案1】:

为什么不在构造更新查询之前进行模式匹配?

def patchPerson(name: Option[String], age: Option[Int]) = 
   val query = people.filter(_.name === "M Odersky")
   (name, age) match 
     case (Some(name), Some(age)) =>
       query.map(p => (p.name, p.age)).update(name, age)
     case (Some(name), None) =>
       query.map(p => p.name).update(name)
     case (None, Some(age)) =>
       query.map(p => p.age).update(age)
   

【讨论】:

很好,谢谢。很有用 不幸的是,这不是真正可扩展的,因为 n Options 你得到 2^n 案例【参考方案2】:

我最好的猜测是运行plain SQL query

即使 SQL 查询有 2 个部分,关系数据库管理系统(postgresql、mysql 等)也能够在后台调整查询。

我不确定在这种情况下 Slick 是否能够优化,但在某些情况下它本身也可以optimizes the queries。

典型更新:

def updateRecord(id: Long, field1: Int) = 
    db.withSession 
      self.filter(_.id === id).map(_.field1).update(field1)
    

进行您的更新类型需要像您一样的更多逻辑。如果您只在运行时知道要更改哪些字段,请不要认为可以简化。但是您可以强制更新,使用记录中字段的现有值作为后备(可能导致数据库上的更新超出其应有的数量)

def updateRecord(id: Long, field1: Option[Int], field2: Option[Int]) = 
    db.withSession 
        self.filter(_.id === id).map(_.field1, _.field2).update(field1.getOrElse(existingValue1), field2.getOrElse(existingValue2)) 
    

【讨论】:

我也相信普通 SQL 目前是解决这个问题的正确方法。 Slick 可能可以按照您的建议将每个字段的一个更新优化为一个 SQL 更新作为替代方案。唯一真正的问题是如何在没有额外查询的情况下获取 existingValueX。这个问题的整个想法是避免 SELECT + UPDATE。真可惜更新函数收不到当前值。【参考方案3】:

你已经得到了@pedrorijo91 和@thirstycow 所写的答案,但我会尝试解释为什么这不起作用。

我没有使用过 slick 3,但我猜测它是因为 map 函数没有返回一致的类型来运行更新。作为一个思想实验,如果您在 map 上打断电话,您认为该类型会是什么?

val partialQuery:??? = people.filter(_.name === "M Odersky")
       .map(p => 
           (name, age) match 
              case (Some(_), Some(_)) => (p.name, p.age)
              case (Some(_), None)    => (p.name)
              case (None   , Some(_)) => (p.age)
           
       );

val fullQuery:??? = partialQuery.update 
       (name, age) match 
          case (Some(_), Some(_)) => (name.get, age.get)
          case (Some(_), None)    => (name.get)
          case (None   , Some(_)) => (age.get)
           

匹配器在编译时返回不同的“形状”,我​​猜这将恢复为任何类型。

【讨论】:

我只是想补充一下,其实还有第三种解决方法,就是写一个宏。我不打算在这里详细介绍,但它看起来根本不像你的解决方案(但可能更酷?)

以上是关于如何在 Scala Slick 中运行补丁/部分数据库更新?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Play with Scala 和 Slick 从数据库中获取记录

Scala Slick 如何将 Scala 代码翻译成 JDBC?

使用 Java+Scala+Slick2D 时“无法从给定的启动配置中找到主要方法”

为不存在的表 slick scala (Slick 3.0.0, scala) 创建一个类 Table

IDEA中搭建Scala + Play+Slick环境

如何使用 AKKA-HTTP、spray-json、oauth2 和 slick 优化 scala REST api?