2014-07-04 14 views
5

Używam Scala 'Slick i PostgreSQL. I pracuję dobrze z tabelami z jednym PK. Teraz trzeba użyć tabeli z wieloma PK:Scala's Slick z wieloma PK insertOrUpdate() zgłasza wyjątki ERROR: błąd składni na końcu wejścia

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] 

kiedy mam pustą tabelę i używać reports.insert(report) to działa dobrze. Ale gdy używam reports.insertOrUpdate(report) otrzymuję i wyjątek:

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 .... 

Co robię źle? Jak to naprawić?

Z góry dziękuję.


PS. Próbowałem obejście - starał się realizować „jeśli istnieje aktualizacji innego insert” logiki przez:

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) 
        } 

Ale wciąż wyjątek na aktualizacji:

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. 
+0

Co to jest 'insertOrUpdate', nie widzę takiej metody w apikcie' Zręczny'. –

+0

Mam go i działa dobrze, gdy tabela ma jeden PK. Jak inaczej mogę wdrożyć działanie UPSERT? –

+1

Ende Neu: insertOrUpdate został dodany w Slick 2.1-M2 i będzie częścią Slick 2.1. – cvogt

Odpowiedz

2

drugiej części, gdzie trzeba

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) 
       } 

zmiana reports.update(report) z

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

rzeczywiście można po prostu zrobić jeden filter połączenia (zastępując swoją pierwszą withFilter)

7

dotyczących początkowego pytanie insertOrUpdate na stole z kluczy złożonych jest złamana w Slick (przynajmniej z pgsql), więc błąd nie jest na Twoja strona. Zobacz raport o błędzie, np. Tutaj: https://github.com/slick/slick/issues/966

Musisz zaprojektować obejście, jednak operacja "upsert" jest bardzo podatna na warunki wyścigowe i jest bardzo trudna do zaprojektowania, ponieważ PostgreSQL nie zapewnia natywnej funkcji do wykonania tego . Patrz np. http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/

W każdym razie, innym sposobem wykonania operacji, która jest nieco mniej podatna na warunki wyścigu, jest pierwsza aktualizacja (która nie spowoduje niczego, jeśli wiersz nie istnieje), a następnie wykonanie "wstawienia" wybierz "zapytanie, które wstawi się tylko wtedy, gdy wiersz nie istnieje. To jest właśnie Slick wykona operację insertOrUpdate na PostgreSQL z pojedynczym PK. Jednak "wstaw wybierz" nie może być wykonane za pomocą Slick bezpośrednio, będziesz musiał wrócić do bezpośredniego SQL.

0

Mam powodzeniem stosowana technika opisana here więc moja metoda upsert wygląda następująco:

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") 
    } 
0

Mam nadzieję, że problem ten zostanie rozwiązany w 3.2.0

Obecnie mogę obejść ten problem poprzez stworzenie obojętny stół do tworzenia tabeli:

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)) 
} 

i "prawdziwy" stół do 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) 
} 
Powiązane problemy