我正在使用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。我尝试了解决方法 - 尝试实施&#34;如果存在则更新其他插入&#34;逻辑:
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.
答案 0 :(得分:7)
关于你的初始问题,在带有复合键的表上的insertOrUpdate在Slick(至少使用PGSql)中被破坏,因此错误不在你身边。请参阅错误报告,例如:https://github.com/slick/slick/issues/966
所以你必须设计一个解决方法,但是“upsert”操作非常容易出现竞争条件,并且很难正确设计,因为PostgreSQL不提供本机功能来执行此操作。例如,参见http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/
无论如何,执行不太容易出现竞争条件的操作的另一种方法是首先更新(如果该行不存在则不会执行任何操作),然后执行“插入选择”查询,只有在行不存在时才会插入。这就是Wlick Slick将使用单个PK在PostgreSQL上执行insertOrUpdate操作。但是,“插入选择”无法直接使用Slick完成,您将不得不回退直接SQL。
答案 1 :(得分:3)
你拥有的第二部分
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
)
答案 2 :(得分:0)
我已经成功应用了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")
}
答案 3 :(得分:0)
希望此问题将在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)
}