如果Slick 3.0.0中不存在则插入

Ixx*_*Ixx 21 scala slick

我试图插入如果不存在,我发现这个帖子为1.0.1,2.0.

我在3.0.0的文档中使用事务性发现了代码段

val a = (for {
  ns <- coffees.filter(_.name.startsWith("ESPRESSO")).map(_.name).result
  _ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally

val f: Future[Unit] = db.run(a)
Run Code Online (Sandbox Code Playgroud)

如果不存在这种结构,我很难从插入编写逻辑.我是Slick的新手,对Scala没什么经验.这是我尝试插入,如果在事务外不存在...

val result: Future[Boolean] = db.run(products.filter(_.name==="foo").exists.result)
result.map { exists =>  
  if (!exists) {
    products += Product(
      None,
      productName,
      productPrice
    ) 
  }  
}
Run Code Online (Sandbox Code Playgroud)

但是我怎么把它放在事务块中呢?这是我能走得最远的地方:

val a = (for {
  exists <- products.filter(_.name==="foo").exists.result
  //???  
//    _ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally
Run Code Online (Sandbox Code Playgroud)

提前致谢

dwi*_*ern 24

可以使用单个insert ... if not exists查询.这避免了多个数据库往返和竞争条件(根据隔离级别,事务可能不够).

def insertIfNotExists(name: String) = users.forceInsertQuery {
  val exists = (for (u <- users if u.name === name.bind) yield u).exists
  val insert = (name.bind, None) <> (User.apply _ tupled, User.unapply)
  for (u <- Query(insert) if !exists) yield u
}

Await.result(db.run(DBIO.seq(
  // create the schema
  users.schema.create,

  users += User("Bob"),
  users += User("Bob"),
  insertIfNotExists("Bob"),
  insertIfNotExists("Fred"),
  insertIfNotExists("Fred"),

  // print the users (select * from USERS)
  users.result.map(println)
)), Duration.Inf)
Run Code Online (Sandbox Code Playgroud)

输出:

Vector(User(Bob,Some(1)), User(Bob,Some(2)), User(Fred,Some(3)))
Run Code Online (Sandbox Code Playgroud)

生成的SQL:

insert into "USERS" ("NAME","ID") select ?, null where not exists(select x2."NAME", x2."ID" from "USERS" x2 where x2."NAME" = ?)
Run Code Online (Sandbox Code Playgroud)

这是github上的完整示例

  • @dwickern很棒的答案.你知道是否有可能编译`insertIfNotExists`查询或查询编译器是否必须在每个方法调用上运行? (2认同)

Ixx*_*Ixx 19

这是我提出的版本:

val a = (
    products.filter(_.name==="foo").exists.result.flatMap { exists => 
      if (!exists) {
        products += Product(
          None,
          productName,
          productPrice
        ) 
      } else {
        DBIO.successful(None) // no-op
      }
    }
).transactionally
Run Code Online (Sandbox Code Playgroud)

但它有点缺乏,例如返回插入或现有对象会很有用.

为了完整性,这里的表定义:

case class DBProduct(id: Int, uuid: String, name: String, price: BigDecimal)
class Products(tag: Tag) extends Table[DBProduct](tag, "product") {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc) // This is the primary key column
  def uuid = column[String]("uuid")
  def name = column[String]("name")
  def price = column[BigDecimal]("price", O.SqlType("decimal(10, 4)"))

  def * = (id, uuid, name, price) <> (DBProduct.tupled, DBProduct.unapply)
}
val products = TableQuery[Products]
Run Code Online (Sandbox Code Playgroud)

我正在使用映射表,该解决方案也适用于元组,只需稍作修改即可.

另请注意,根据在插入操作中忽略的文档,没有必要将id定义为可选:

在插入操作中包含AutoInc列时,将以静默方式忽略它,以便数据库可以生成正确的值

这里的方法:

def insertIfNotExists(productInput: ProductInput): Future[DBProduct] = {

  val productAction = (
    products.filter(_.uuid===productInput.uuid).result.headOption.flatMap { 
    case Some(product) =>
      mylog("product was there: " + product)
      DBIO.successful(product)

    case None =>
      mylog("inserting product")

      val productId =
        (products returning products.map(_.id)) += DBProduct(
            0,
            productInput.uuid,
            productInput.name,
            productInput.price
            )

          val product = productId.map { id => DBProduct(
            id,
            productInput.uuid,
            productInput.name,
            productInput.price
          )
        }
      product
    }
  ).transactionally

  db.run(productAction)
}
Run Code Online (Sandbox Code Playgroud)

(感谢Google小组代表 Matthew Pocock ,让我找到这个解决方案).