【问题标题】:Scala's Slick with multiple PK insertOrUpdate() throws exception ERROR: syntax error at end of input具有多个 PK insertOrUpdate() 的 Scala 的 Slick 引发异常 ERROR: syntax error at end of input
【发布时间】:2014-07-04 19:02:47
【问题描述】:

我正在使用 Scala 的 Slick 和 PostgreSQL。 而且我在单 PK 表上工作得很好。 现在我需要使用具有多个 PK 的表:

case class Report(f1: DateTime,
    f2: String,
    f3: Double)

class Reports(tag: Tag) extends Table[Report](tag, "Reports") {
    def f1 = column[DateTime]("f1")
    def f2 = column[String]("f2")
    def f3 = column[Double]("f3")

    def * = (f1, f2, f3) <> (Report.tupled, Report.unapply)
    def pk = primaryKey("pk_report", (f1, f2))
}

val reports = TableQuery[Reports]

当我有空表并使用reports.insert(report) 时,它运行良好。 但是当我使用reports.insertOrUpdate(report) 时,我收到异常:

Exception in thread "main" org.postgresql.util.PSQLException: ERROR: syntax error at end of input
  Position: 76
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2102)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1835)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:500)
    at ....

我做错了什么?如何解决?

提前致谢。


PS。我尝试了解决方法 - 尝试通过以下方式实现“如果存在更新则插入”逻辑:

  val len = reports.withFilter(_.f1 === report.f1).withFilter(_.f2 === report.f2).length.run.toInt
                    if(len == 1) {
                        println("Update: " + report)
                        reports.update(report)
                    } else {
                        println("Insert: " + report)
                        reports.insert(report)
                    }

但我仍然在更新时遇到异常:

Exception in thread "main" org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "pk_report"
  Detail: Key ("f1", f2)=(2014-01-31 04:00:00, addon_io.aha.connect) already exists.

【问题讨论】:

  • insertOrUpdate是什么,我在Slick api中没有看到这种方法。
  • 我有它,当表有一个 PK 时它工作得很好。我还能如何实现 UPSERT 操作?
  • Ende Neu:insertOrUpdate 已添加到 Slick 2.1-M2 中,并将成为 Slick 2.1 的一部分。
  • 关于第二个例外。如果 len > 1 你也尝试插入。也许这就是问题所在。疯狂的猜测:)。

标签: postgresql scala slick composite-primary-key upsert


【解决方案1】:

关于您最初的问题,带有复合键的表上的 insertOrUpdate 在 Slick 中被破坏(至少在 PGSql 中),因此错误不在您这边。请参阅错误报告,例如:https://github.com/slick/slick/issues/966

所以你必须设计一个解决方法,但是“upsert”操作很容易出现竞争条件,并且很难正确设计,因为 PostgreSQL 不提供执行此操作的本机功能。参见例如http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/

无论如何,另一种不太容易出现竞争条件的操作的方法是首先更新(如果行不存在,它将不做任何事情),然后执行“插入选择”查询,这仅当该行不存在时才插入。这是 Slick 在 PostgreSQL 上使用单个 PK 执行 insertOrUpdate 操作的方式。但是,“插入选择”不能直接使用 Slick 完成,您必须回退到直接 SQL。

【讨论】:

    【解决方案2】:

    你有的第二部分

    val len = reports.withFilter(_.f1 === report.f1).withFilter(_.f2 === report.f2).length.run.toInt
                    if(len == 1) {
                        println("Update: " + report)
                        reports.update(report)
                    } else {
                        println("Insert: " + report)
                        reports.insert(report)
                    }
    

    更改 reports.update(report)

    reports.filter(_.id === report.id).update(report)

    实际上您只需拨打一个filter 电话(替换您的第一个withFilter

    【讨论】:

      【解决方案3】:

      我已经成功应用了here 描述的技术 所以我的 upsert 方法如下所示:

        def upsert(model: String, module: String, timestamp: Long) = {
          // see this article http://www.the-art-of-web.com/sql/upsert/
          val insert     = s"INSERT INTO $ModulesAffectedTableName (model, affected_module, timestamp) SELECT '$model','$module','$timestamp'"
          val upsert     = s"UPDATE $ModulesAffectedTableName SET timestamp=$timestamp WHERE model='$model' AND affected_module='$module'"
          val finalStmnt = s"WITH upsert AS ($upsert RETURNING *) $insert WHERE NOT EXISTS (SELECT * FROM upsert)"
          conn.run(sqlu"#$finalStmnt")
        }
      

      【讨论】:

        【解决方案4】:

        希望这个问题会在3.2.0得到解决

        目前,我通过为创建表创建一个虚拟表来解决此问题:

        class ReportsDummy(tag: Tag) extends Table[Report](tag, "Reports") {
            def f1 = column[DateTime]("f1")
            def f2 = column[String]("f2")
            def f3 = column[Double]("f3")
        
            def * = (f1, f2, f3) <> (Report.tupled, Report.unapply)
            def pk = primaryKey("pk_report", (f1, f2))
        }
        

        还有一个用于 upsert 的“真实”表格

        class Reports(tag: Tag) extends Table[Report](tag, "Reports") {
            def f1 = column[DateTime]("f1", O.PrimaryKey) 
            def f2 = column[String]("f2", O.PrimaryKey) //two primary keys here, which would throw errors on table creation. Hence a dummy one for the task
            def f3 = column[Double]("f3")
        
            def * = (f1, f2, f3) <> (Report.tupled, Report.unapply)
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-04-29
          • 2016-10-21
          • 2021-11-06
          • 1970-01-01
          • 1970-01-01
          • 2018-10-14
          • 1970-01-01
          相关资源
          最近更新 更多