Scala Slick с несколькими PK insertOrUpdate() выдает исключение ОШИБКА: синтаксическая ошибка в конце ввода

Я использую Scala' Slick и PostgreSQL. И я хорошо работаю с таблицами с одним ПК. Теперь мне нужно использовать таблицу с несколькими ПК:

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.

person Konstantin Trunin    schedule 04.07.2014    source источник
comment
Что такое insertOrUpdate, я не вижу такого метода в API Slick.   -  person Ende Neu    schedule 04.07.2014
comment
Он у меня есть, и он хорошо работает, когда в таблице есть один ПК. Как еще я могу реализовать операцию UPSERT?   -  person Konstantin Trunin    schedule 05.07.2014
comment
Ende Neu: insertOrUpdate был добавлен в Slick 2.1-M2 и станет частью Slick 2.1.   -  person cvogt    schedule 06.07.2014
comment
Что касается второго исключения. В случае len› 1 вы также пытаетесь вставить. Может быть, это проблема. Грубое предположение :).   -  person cvogt    schedule 06.07.2014


Ответы (4)


Что касается вашего первоначального вопроса, в Slick (по крайней мере, с PGSql) вставка OrUpdate в таблицу с составными ключами не работает (по крайней мере, с PGSql), поэтому ошибка не на вашей стороне. См. отчет об ошибке, например, здесь: https://github.com/slick/slick/issues/966

Таким образом, вам нужно разработать обходной путь, однако операция «upsert» очень подвержена условиям гонки, и ее очень сложно правильно спроектировать, поскольку PostgreSQL не предоставляет встроенной функции для ее выполнения. См., например, http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/

В любом случае, другой способ выполнить операцию, которая немного менее подвержена гонке, состоит в том, чтобы сначала обновить (что ничего не сделает, если строка не существует), а затем выполнить запрос «вставить выбор», который будет только вставлять если строки не существует. Именно так Slick будет выполнять операцию insertOrUpdate в PostgreSQL с одним PK. Однако «вставить выбор» нельзя выполнить напрямую с помощью Slick, вам придется вернуться к прямому SQL.

person scand1sk    schedule 04.01.2015

Вторая часть, где у вас есть

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 )

person Vikas Pandya    schedule 17.09.2014

Я успешно применил метод, описанный здесь, поэтому мой метод 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")
  }
person kumetix    schedule 01.12.2015

Надеемся, что эта проблема будет исправлена ​​в 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)
}
person user2829759    schedule 06.02.2017