example scala slick

scala - example - Insertar si no existe en Slick 3.0.0



scala slick example (4)

De acuerdo con la sección de consulta de inserción manual de Slick 3.0 ( http://slick.typesafe.com/doc/3.0.0/queries.html ), los valores insertados se pueden devolver con la identificación como se muestra a continuación:

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") (products returning products.map(_.id) into ((prod,id) => prod.copy(id=id))) += DBProduct( 0, productInput.uuid, productInput.name, productInput.price ) } ).transactionally db.run(productAction) }

Estoy tratando de insertar, si no existe, encontré esta publicación para 1.0.1, 2.0.

Encontré un fragmento usando transaccionalmente en los documentos de 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)

Estoy luchando para escribir la lógica de inserción si no existe con esta estructura. Soy nuevo en Slick y tengo poca experiencia con Scala. Este es mi intento de hacer una inserción si no existe fuera de la transacción ...

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

¿Pero cómo pongo esto en el bloque transaccional? Esto es lo más lejos que puedo ir:

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

Gracias por adelantado


Es posible utilizar una sola insert ... if not exists consulta. Esto evita múltiples viajes de ida y vuelta a la base de datos y condiciones de carrera (las transacciones pueden no ser suficientes dependiendo del nivel de aislamiento).

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)

Salida:

Vector(User(Bob,Some(1)), User(Bob,Some(2)), User(Fred,Some(3)))

SQL generado:

insert into "USERS" ("NAME","ID") select ?, null where not exists(select x2."NAME", x2."ID" from "USERS" x2 where x2."NAME" = ?)

Aquí está el ejemplo completo en github


Esta es la versión que se me ocurrió:

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

Sin embargo, falta un poco, por ejemplo, sería útil devolver el objeto insertado o existente.

Para completar, aquí la definición de la tabla:

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]

Estoy usando una tabla asignada, la solución funciona también para tuplas, con cambios menores.

Tenga en cuenta también que no es necesario definir el ID como opcional, de acuerdo con la documentation que se ignora en las operaciones de inserción:

Cuando incluye una columna de AutoInc en una operación de inserción, se ignora silenciosamente, de modo que la base de datos puede generar el valor adecuado

Y aquí el método:

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

(Gracias a Matthew Pocock del hilo de grupo de Google , por orientarme a esta solución).


Me he encontrado con la solución que parece más completa. La Sección 3.1.7. Más control sobre las inserciones del libro Essential Slick tiene el ejemplo.

Al final obtienes algo así como:

val entity = UserEntity(UUID.random, "jay", "jay@localhost") val exists = users .filter( u => u.name === entity.name.bind && u.email === entity.email.bind ) .exists val selectExpression = Query( ( entity.id.bind, entity.name.bind, entity.email.bind ) ).filterNot(_ => exists) val action = usersDecisions .map(u => (u.id, u.name, u.email)) .forceInsertQuery(selectExpression) exec(action) // res17: Int = 1 exec(action) // res18: Int = 0