How to update column with another subquery value in Slick? - scala

I want to do something like this
UPDATE item
SET value = (
SELECT max(value)
FROM item
)
WHERE id = 1;
I tried
for {
maxValue <- Tables.Item.map(_.value).max
x <- Tables.Item
.filter(item => item.id === 1)
.map(_.value).update(maxValue)
} yield x
but maxValue is a Rep[Int] instead of Int

Slick's update doesn't support dynamic values or sub-queries. You have a couple of options for this situation.
First, you can use Plain SQL:
sqlu""" UPDATE item SET value = (SELECT max(value) FROM item) WHERE id = 1 """
Second, you could run the expression as two queries (potentially inside a transaction). This is similar to the example you have as update is a DBIO, rather than a Query.
I'd expect max to have an optional value as there might be no rows in the table:
val updateAction: DBIO[Int] =
Tables.Item.map(_.value).max.result.flatMap {
case Some(maxValue) =>
Tables.Item
.filter(item => item.id === 1)
.map(_.value).update(maxValue)
case None =>
DBIO.successful(0) // ... or whatever behaviour you want
}
However, perhaps your value field is already an option and you can use your existing for comprehension with the addition of .result on the end of the maxValue expression as mentioned by #Duelist.

Related

Updating JSON column in Postgres?

I've got a JSON column in Postgres, that contains data in the following structure
{
"listings": [{
"id": "KTyneMdrAhAEKyC9Aylf",
"active": true
},
{
"id": "ZcjK9M4tuwhWWdK8WcfX",
"active": false
}
]
}
I need to do a few things, all of which I am unsure how to do
Add a new object to the listings array
Remove an object from the listings array based on its id
Update an object in the listings array based on its id
I am also using Sequelize if there are any built in functions to do this (I can't see anything obvious)
demo:db<>fiddle
Insert (jsonb_insert()):
UPDATE mytable
SET mydata = jsonb_insert(mydata, '{listings, 0}', '{"id":"foo", "active":true}');
Update (expand array, change value with jsonb_set(), reaggregate):
UPDATE mytable
SET mydata = jsonb_set(mydata, '{listings}', s.a)
FROM
(
SELECT
jsonb_agg(
CASE WHEN elems ->> 'id' = 'foo' THEN jsonb_set(elems, '{active}', 'false')
ELSE elems
END
) as a
FROM
mytable,
jsonb_array_elements(mydata -> 'listings') AS elems
) s;
Delete (expand array, filter relevant elements, reaggregate):
UPDATE mytable
SET mydata = jsonb_set(mydata, '{listings}', s.a)
FROM
(
SELECT
jsonb_agg(elems) as a
FROM
mytable,
jsonb_array_elements(mydata -> 'listings') AS elems
WHERE elems ->> 'id' != 'foo'
) s;
Updating and deleting can be done in one line like the inserting. But in that case you have to use the array element index instead of a certain value. If you want to change an array element with a value you need to read it first. This is only possible with a prior expanding.

Scala - how to assign entry numbers to sorted elements of a collection?

I wanted to write a method to process sales data in a way that the sales are sorted by date and concatenated with an entry number and a sale type like this:
0/2018-05-02 01:55:07/Sale type A,1/2018-09-22 02:55:07/Sale type B
But for now I could only achieve concatenating saleDate and saleType. How is it possible to produce an entry number for each record? By entry number I mean the order of sales after sorting by date
def concatSales(sales: Seq[Sale]): Seq[String] = {
sales
.sortWith(_.saleDate < _.saleDate)
.map(sale => s"$DELIMITER${sale.saleDate}$DELIMITER${sale.saleType}")
}
If you want to assign an index for each element, you can use zipWithIndex:
sales
.sortWith(_.saleDate < _.saleDate)
.zipWithIndex
.map {
case (sale, idx) => s"$idx: ..."
}
Note that you might want to use .sortBy instead of .sortWith since it looks simpler:
sales.sortBy(_.saleDate)

How to update rows based on condition in spark-sql

I am working on spark-sql for data preparation.
The problem I am facing is after getting the result of sql query. How should I update rows based on the If-then-else condition.
What I am doing
val table_join = sqlContext.sql(""" SELECT a.*,b.col as someCol
from table1 a LEFT JOIN table2 b
on a.ID=b.ID """)
table_join.registerTempTable("Table_join")
Now when I have final joined table which is in df format. How should I update rows?
//Final filtering operation
val final_filtered_table = table_join.map{ case record=>
if(record.getAs[String]("col1") == "Y" && record.getAs[String]("col2") == "") record.getAs[String]("col2")="UNKNOWN"
else if (record.getAs[String]("col1") == "N") record("col1")=""
else record
}
In the above map the if syntax works properly but the moment I apply the update condition to modify It gives me error.
But why the below query is working
if(record.getAs[String]("col1") == "Y" && record.getAs[String]("col2") == "") "UNKNOWN"
But the moment I change "UNKNOWN" to record.getAs[String]("col2")="UNKNOWN" It gives me error at at .getAs
Another approach I tried is this:
val final_filtered_sql = table_join.map{row =>
if(row.getString(6) == "Y" && row.getString(33) == "") row.getString(6) == "UNKNOWN"
else if(row.getString(6) == "N") row.getString(6) == ""
else row
}
This is working but is this the right approach as I should not call the columns by their no's but instead their names. What approach should I follow to get names of the column and then update ??
Please help me regarding this. What syntax should I do to update rows based on the condition in dataframe in spark-sql
record.getAs[String]("col2")="UNKNOWN" won't work because record.getAs[String](NAME) will return a String which doesn't have a = method and assigning a new value to a string doesn't make sense.
DataFrame records don't have any setter methods because DataFrames are based on RDD which are immutable collections, meaning you cannot change their state and that's how you're trying to do here.
One way would be to create a new DataFrame using selectExpr on table_join and put that if/else logic there using SQL.

Play Framework 2.0 correct way to represent a set in a query using Anorm

I am trying to return a list of results using Anorm using a query that returns matching rows for a set of ids. Eg.
select *
from example
where id in (1,2,3,4,5)
If I try
SQL(
"""
select *
from example
where id in ({ids})
"""
).on('ids -> ids).as(int("id") ~ str("name") *)
where ids is the String "1,2,3,4,5" it will only return the first row. What is the correct way to inject the set of ids?
There's no simple way of doing it AFAIK.
This is how I solved it:
def findSomething(ids: String) = {
// Split up the comma separated values
val sids = ids split ","
// Create a list of keys (id0, id1, id2, ...)
val keys = for ( i <- 0 until sids.size ) yield ("id" + i)
// Create a seq of parameterized values
val values = sids map (toParameterValue(_))
// Now zip together the keys and values into list of tuples
val params = keys zip values
DB.withConnection { implicit connection =>
SQL(
"""
select *
from example
where id in ({%s})
""".format(keys.mkString("},{"))
).on(
params: _*
).as(
int("id") ~ str("name") *
)
}
}
NB
The cruical part here is the string formatting in the SQL statement. It is vulnerable for SQL injection if you don't have total control of your input parameters.

Grouping and processing groups inside plpgsql functions

I need to perform a sophisticated group processing, like here. I get some rows from a complex query, the row set looks like this:
key val
-------
foo 1
foo 2
foo 3
bar 10
bar 15
baz 22
baz 44
...
And here is a pseudocode I want to implement in plpgsql:
result = new array()
group = new array()
current_key = null
for (record in (select * from superComplexQuery())) {
if (current_key == null) {
current_key = record.key
}
if (current_key != record.key) {
result.add(processRows(group))
group.clear()
current_user = record.key
}
group.add(record)
}
if (group.size() > 0) {
result.add(processRows(group))
}
return result
I.e., we must process 3 "foo" rows, then 2 "bar" rows, then 2 "baz rows" etc. And result of each processRows is added to resulting collection.
Maybe I should use another approach, but I don't know what it must be.
EDIT: processRows should output a record. Thus, the output of the whole procedure will be a set of rows, where each row is a result of processRows(group). One example of such calculation is given in first sentence of this question: Selecting positive aggregate value and ignoring negative in Postgres SQL , i.e. the calculation involves some iteration and aggregation with some complex rules.
The right approach was to use User-Defined Aggregates
I.e. I successfully implemented my own aggregate function and the code looks like
select my_complex_aggregation((input_field_1, input_field_2, input_field_3))
from my_table
group by key