【问题标题】:How do you run a patch/partial database UPDATE in Scala Slick?如何在 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 端点,我可以简单地向它发送一个更新的字段。
  • 我找到了一个类似问题的答案。 stackoverflow.com/a/42004236/7505973

标签: scala slick


【解决方案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)
       }    
}

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

【讨论】:

  • 我只是想补充一下,其实还有第三种解决方法,就是写一个宏。我不打算在这里详细介绍,但它看起来根本不像你的解决方案(但可能更酷?)
猜你喜欢
  • 2014-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-27
  • 1970-01-01
  • 2020-04-28
  • 2023-01-30
  • 2018-01-08
相关资源
最近更新 更多