Can Slick's insertOrUpdate modify a subset of columns in the event the record already exists? - sql

In my use case I have a createdDate field that I would like to preserve in the event that the record already exists.
case class Record(id:Long, value:String, createdDate:DateTime, updateDate:DateTime)
Is it possible to use a TableQuery.insertOrUpdate(record) such that only parts of the record are updated in the event the record already exists?
In my case I'd want only the value and updateDate fields to change. Using plain SQL in a stored procedure I'd do something like:
merge Record r
using (
select #id,
#value
) as source (
id,
value
)
on r.id = source.id
when matched then
update set value = source.value, updateDate = getDate()
when not matched then
insert (id, value, createdDate, updatedDate) values
(id, value, getDate(), getDate()

Can Slick's insertOrUpdate modify a subset of columns?
No, I don't believe this is possible with the insertOrUpdate function. This has been requested as a feature but it is not currently implemented.
How can we work around this?
Since the update function does support updating a specific list of columns, we can write our own upsert logic instead of using the insertOrUpdate function. It might work like this:
def insertOrUpdate(record: Record): Future[Int] = {
val insertOrUpdateAction = for {
recordOpt <- records.filter(_.id === record.id).result.headOption
updateAction = recordOpt.map(_ => updateRecord(record))
action <- updateAction.getOrElse(insertRecord(record))
} yield action
connection.run(insertOrUpdateAction)
}
private def updateRecord(record: Record) = {
val query = for {
r <- records.filter(_.id === record.id)
} yield (r.value, r.updatedDate) // list of columns which can be updated
query.update(record.value, record.updatedDate)
}
private def insertRecord(record: Record) = records += record

Related

Update database row by id fails

I'm trying to update a specific row by ID :
fun updateEmployeeInfo(id:Int, firstName:String): Int {
val db = this.writableDatabase
var cv = ContentValues()
cv.put(COL_FIRSTNAME, firstName )
val result = db.update(TABLE_NAME, cv, COL_FIRSTNAME+"=?", arrayOf(firstName))
return result
}
Running this with an ID that already exists in the database it isn't updating.
screenshot of the database
You are saying update the rows with the provided (passed) first name by changing the first name to the very same first name (effectively doing nothing).
I believe that you want to use:-
val result = db.update(TABLE_NAME, cv, COL_ID+"=?", arrayOf(id.toString))
assuming that COL_ID holds the value of the id column name.
or you could use the more concise return db.update(TABLE_NAME, cv, COL_ID+"=?", arrayOf(id.toString))
This is then saying update the row where the id is the provided/passed id changing the first name, whatever it is, to the provided/passed first name.

How to use NVL or COALESCE in native query for list of values in Spring Data JPA

I am using native query as follows which fetches data from multiple tables.
I also want to perform filtering on it based on users input. User can select either none, one or multiple checkboxes in filter menu based on which my query should return the record.
I have tried both the approaches like:
src_region.region_id NVL(:regionIds,src_region.region_id )
src_region.region_id in(:regionIds) OR COALESCE (:regionIds)IS NULL)
But for both of them I am getting errors as I am sending List values.
public interface dbRepository
{
public static final String dbQuery = "select * from
(select src_data.*,
trg_data.*
from
(select reg.region_id,reg.region_name,
ctry.country_id,ctry.country_name
from src_region reg,src_ctry ctry
where reg.region_id=ctry.region_id
AND src_region.region_id in(:regionIds) OR COALESCE (:regionIds)IS NULL) src_data,
(select md.module_id,reg.module_name,
ctry.country_id,ctry.country_name
from trg_module md,trg_ctry ctry
where md.module_id=ctry.country_id
AND ctry.country_id in(:countryIds) OR COALESCE (:countryIds)IS NULL) trg_data
ORDER BY src_data.region_id ";
#Query( value = dbQuery, nativeQuery = true )
List<Object[]> findBySorceOrTarget( #Param( "regionIds" ) List<Long> regionIds, #Param( "countryIds" ) List<Long> countryIds );
}
User may on may not send values. Also, rather than sending multiple values, they can also send only one value. What is the better way to pass list of values to parameters in native query?
Generally speaking, that should be
AND (src_region.region_id = :regionIds or :regionIds IS NULL)
However, if you can select multiple values, then = won't be OK - you'll have to switch to in (as code you posted suggests). Though, you can't do it that way as you'll get nothing as a result - you'll have to split those values into rows.
Something like this (presuming that values in :regionIds are comma-separated, such as 1,2,5:
and (src_region.region_id in (select regexp_substr(:regionIds, '[^,]+', 1, level)
from dual
connect by level <= regexp_count(:regionIds, ',') + 1
)
or :regionIds is null)

INSERT INTO SELECT in Slick that runs on the server. Is it possible?

I’m going to duplicate some records in table tbl.
It looks like
INSERT INTO tbl SELECT id+100, name FROM tbl
in plain SQL.
I expected that it could look like
db.run(
tableQuery.forceInsertQuery(
tableQuery.map{rec=>rec.copy(id=rec.id+100)}
))
in Slick, where
rec is an instance of Table[ScalaCaseClassForTbl]
with
val id = column[Int]("id", O.PrimaryKey)
val name = column[String]("name")
and
override def * : ProvenShape[ScalaCaseClassForTbl] =
But I do not understand how to make map.
Thank you for any ideas.
The problem with...
tableQuery.map{rec=>rec.copy(id=rec.id+100)}
...is that rec is not a case class, so there's not a copy.
What you can do is map to a tuple of the columns (the Rep[T]) values) and then convert that to a case class.
For example:
tableQuery.map{ rec =>
(rec.id+100, rec.name).mapTo[YourCaseClass]
}

Inserting default values if column value is 'None' using slick

My problem is simple.
I have a column seqNum: Double which is NOT NULL DEFAULT 1 in CREATE TABLE statement as follows:
CREATE TABLE some_table
(
...
seq_num DECIMAL(18,10) NOT NULL DEFAULT 1,
...
);
User can enter a value for seqNum or not from UI. So the accepting PLAY form is like:
case class SomeCaseClass(..., seqNum: Option[Double], ...)
val secForm = Form(mapping(
...
"seqNum" -> optional(of[Double]),
...
)(SomeCaseClass.apply)(SomeCaseClass.unapply))
The slick Table Schema & Objects looks like this:
case class SomeSection (
...
seqNum: Option[Double],
...
)
class SomeSections(tag: Tag) extends Table[SomeSection](tag, "some_table") {
def * = (
...
seqNum.?,
...
) <> (SomeSection.tupled, SomeSection.unapply _)
...
def seqNum = column[Double]("seq_num", O.NotNull, O.Default(1))
...
}
object SomeSections {
val someSections = TableQuery[SomeSections]
val autoInc = someSections returning someSections.map(_.sectionId)
def insert(s: someSection)(implicit session: Session) = {
autoInc.insert(s)
}
}
When I'm sending seqNum from UI, everything is works fine but when None is there, it breaks saying that cannot insert NULL in NOT NULL column which is correct. This question explains why.
But how to solve this problem using slick? Can't understand where should I check about None? I'm creating & sending an object of SomeSection to insert method of SomeSections Object.
I'm using sql-server, if it matters.
Using the default requires not inserting the value at all rather than inserting NULL. This means you will need a custom projection to insert to.
people.map(_.name).insert("Chris") will use defaults for all other fields. The limitations of scala's native tuple transformations and case class transformations can make this a bit of a hassle. Things like Slick's HLists, Shapeless, Scala Records or Scala XR can help, but are not trivial or very experimental at the moment.
Either you enforce the Option passed to Slick by suffixing it with a .getOrElse(theDefault), or you make the DB accepts NULL (from a None value) and defaults it using some trigger.

Increment counter or insert row in one statement, in SQLite

In SQLite, given this database schema
CREATE TABLE observations (
src TEXT,
dest TEXT,
verb TEXT,
occurrences INTEGER
);
CREATE UNIQUE INDEX observations_index
ON observations (src, dest, verb);
whenever a new observation tuple (:src, :dest, :verb) comes in, I want to either increment the "occurrences" column for the existing row for that tuple, or add a new row with occurrences=1 if there isn't already one. In concrete pseudocode:
if (SELECT COUNT(*) FROM observations
WHERE src == :src AND dest == :dest AND verb == :verb) == 1:
UPDATE observations SET occurrences = occurrences + 1
WHERE src == :src AND dest == :dest AND verb == :verb
else:
INSERT INTO observations VALUES (:src, :dest, :verb, 1)
I'm wondering if it's possible to do this entire operation in one SQLite statement. That would simplify the application logic (which is required to be fully asynchronous wrt database operations) and also avoid a double index lookup with exactly the same key. INSERT OR REPLACE doesn't appear to be what I want, and alas there is no UPDATE OR INSERT.
I got this answer from Igor Tandetnik on sqlite-users:
INSERT OR REPLACE INTO observations
VALUES (:src, :dest, :verb,
COALESCE(
(SELECT occurrences FROM observations
WHERE src=:src AND dest=:dest AND verb=:verb),
0) + 1);
It's slightly but consistently faster than dan04's approach.
Don't know of a way to do it in one statement, but you could try
BEGIN;
INSERT OR IGNORE INTO observations VALUES (:src, :dest, :verb, 0);
UPDATE observeraions SET occurrences = occurrences + 1 WHERE
src = :src AND dest = :dest AND verb = :verb;
COMMIT;