How can I improve this conditional UPDATE query? - sql

I have a table t with several columns, let's name them a, b and c. I also have a state column which indicates the current state. There is also an id column.
I want to write the following query: update column a always, but b and c only if the application state is still equal to the database state. Here, the state column is used for optimistic locking.
I wrote this query as following:
UPDATE t
SET a = $a$,
b = (CASE WHEN state = $state$ THEN $b$ ELSE b END),
c = (CASE WHEN state = $state$ THEN $c$ ELSE c END)
WHERE id = $id$ AND
(
a != $a$ OR
b != (CASE WHEN state = $state$ THEN $b$ ELSE b END) OR
c != (CASE WHEN state = $state$ THEN $c$ ELSE c END)
)
Here, $id$, $a$, ... are input variables from the application. The second part of the WHERE clause is to avoid updates which do not effectively update anything.
This query works as expected, but is very clumsy. I am repeating the same condition several times. I am looking for a way to rewrite this query in a more elegant fashion. If this was a simple SELECT query, I could do something with a LATERAL JOIN, but I cannot see how to apply this here.
How can I improve this query?

Split the query in two:
UPDATE t
SET a = $a$
WHERE id = $id$
UPDATE t
SET b = $b$,
c = $c$
WHERE id = $id$ AND
state = $state$
If you need atomicity, wrap in a transaction.

This seems a bit cleaner(untested):
WITH src AS (
SELECT $a$ AS a
, (CASE WHEN state = $state$ THEN $b$ ELSE b END) AS b
, (CASE WHEN state = $state$ THEN $c$ ELSE c END) AS c
FROM t
WHERE id = $id$
)
UPDATE t dst
SET a=src.a, b=src.b, c=src.c
FROM src
WHERE dst.id = src.id
AND (src.a, src.b, src.c) IS DISTINCT FROM (dst.a, dst.b, dst.c)
;

EDIT: It Took me a while to realize my fault here: The question obviously targets at a single update, while my answer tried to update many rows. However, if you need to execute this Update for a set of rows you could:
Insert the needed parameters in a temporary table
Join that table within the "t2" subquery
Select it's columns (e.g. tempTable.b As tempB)
Replace the Parameters (e.g. $b$ -> t2.tempB)
.
UPDATE t
SET a=source.a,
b=source.b,
c=source.c
FROM
(
SELECT
id,
a,
(CASE WHEN UpdateCondition THEN $b$ ELSE b END) AS b,
(CASE WHEN UpdateCondition THEN $c$ ELSE c END) AS c
FROM
(
SELECT state = $state$ As UpdateCondition, * FROM t
) As t2
WHERE
id = $id$ AND
(
a != $a$ OR
b != (CASE WHEN UpdateCondition THEN $b$ ELSE b END) OR
c != (CASE WHEN UpdateCondition THEN $c$ ELSE c END)
) AS source
WHERE t.id=source.id;
The Sub query for t2 gives you your state Condition and executes the calculation for it only once per row.
The subquery for "source" gives you the mapped values and filters those without changes.

The only filter you need is on ID = $id
The case statement says don't change it in the update if the state doesn't match, so you don't need to filter it.
EDIT
where Id = $id and a !=$a
Or (state = $state and (b !=b or c!= $c))
If you do any more than that then"always update a" will not necessary be true.
3rd attempt checks for the possibility of a remaining the same, but b or c updating.

Related

Conditional IN Statement to be used inside Postgres function

I am working on Postgres and I have two tables vehicles and vehicles_flag. There are no relations between the two tables and hence we can not join two tables to fetch the required data.
The table structure is below (vehicle_flag table may not contain all the id present in the vehicle table) :
[Table structure]
I am writing a function that will accept multiple input parameters. I have to select vehicle id from the vehicle_flag table only if the flag value is true: otherwise, I have to ignore the vehicel_flag table. My aim is to achieve something like this, but turns out the case statement expects scaler output:
select count(id) from vehicles
where
vehicles.id in (case
when #hasbluetooth =1 then (select distinct id from vehicle_flags where flag='bluetooth' and value = '1')
else
(select distinct id from vehicles)
end)
and
vehicles.id in (case
when #hasac =1 then (select distinct id from vehicle_flags where flag='ac' and value = '1')
else
(select distinct id from vehicles)
end)
Kindly suggest any solution to achieve this.
I suspect you want:
select v.*
from vehicle v
left join vehicle_flags vf on vf.id = v.id
group by v.id
having
(#hasbluetooth = 0 or bool_or(vf.flag = 'bluetooth' and vf.value = 1)
and (#hasac = 0 or bool_or(vf.flag = 'ac' and vf.value = 1)

How to declare a constant in an SQL query?

I am using Oracle 11g R2. Is there a way to give a name (alias) to a single value selected from a table before an SQL query in the same expression? That is a single SQL command, I mean, and no PL/SQL.
The closest I've come to is:
WITH
Approved AS (SELECT c.value FROM configuration c WHERE c.code = 'Approved'),
Arrived AS (SELECT c.value FROM configuration c WHERE c.code = 'Arrived'),
Scheduled AS (SELECT c.value FROM configuration c WHERE c.code = 'Scheduled')
SELECT *
FROM list l WHERE l.status_key > (SELECT value FROM Approved);
I am looking for something similar to, say:
WITH
Approved AS CONSTANT (SELECT c.value FROM configuration c WHERE c.code = 'Approved'),
Arrived AS CONSTANT (SELECT c.value FROM configuration c WHERE c.code = 'Arrived'),
Scheduled AS CONSTANT (SELECT c.value FROM configuration c WHERE c.code = 'Scheduled')
SELECT *
FROM list l WHERE l.status_key > Approved;
The reason I don't want to inline the select statement for the value is that my query is complex enough as it is and I'd rather take some of that complexity out, if possible.
I sometimes use a construct like this:
WITH const as
(select max(case when c.code = 'Approved' then c.value end) as Approved,
max(case when c.code = 'Approved' then c.value end) as Approved,
max(case when c.code = 'Scheduled' then c.value end) as Scheduled
from configuration c
),
. . .
SELECT
FROM const cross join
list l
WHERE status_key > Approved;
Sometimes if I need the constants at different places in the query, then I have to bring in the const CTE more than once.
The short answer, is no - you can't do that.
You could create a view though to hide some of the initial complexity.
...or if you really want you could create a function, and kind of use it as a constant...something like (excluding any error handling):
CREATE OR REPLACE FUNCTION config_code (code IN VARCHAR2)
RETURN configuration.value%TYPE AS
value configuration.value%TYPE;
BEGIN
SELECT c.value INTO value FROM configuration c WHERE c.code = code;
RETURN value;
END;
You could then use it as:
SELECT * FROM list l WHERE l.status_key > config_code('Approved');

Selective update in SQL Server

I've created a junction table like this one:
http://imageshack.us/scaled/landing/822/kantotype.png
I was trying to figure out a query that could able to select some rows - based on the PokémonID - and then updating only the first or second row after the major "filtering".
For example:
Let's suppose that I would like to change the value of the TypeID from the second row containing PokémonID = 2. I cannot simply use UPDATE KantoType SET TypeID = x WHERE PokémonID = 2, because it will change both rows!
I've already tried to use subqueries containing IN,EXISTS and LIMIT, but with no success.
Its unclear what are your trying to do. However, you can UPDATE with JOIN like so:
UPDATE
SET k1.TypeID = 'somethng' -- or some value from k2
FROM KantoType k1
INNER JOIN
(
Some filtering and selecting
) k2 ON k1.PokémonID = k2.PokémonID
WHERE k1.PokémonID = 2;
Or: if you want to UPDATE only the two rows that have PokémonID = 2 you can do this:
WITH CTE
AS
(
SELECT *,
ROW_NUMBER() OVER(ORDER BY TypeID) rownum
FROM KantoType
WHERE PokemonID = 2
)
UPDATE c
SET c.TypeID = 5
FROM CTE c
WHERE c.rownum = 1;
SQL Fiddle Demo
I can suggest something like this if you just need to update a single line in your table:
UPDATE kantotype
SET
type = 2
WHERE pokemon = 2
AND NOT EXISTS (SELECT * FROM kantotype k2
WHERE kantotype.type > k2.type
AND kantotype.pokemon = k2.pokemon)
It would be easier to get the first or last item of the table if you had unique identifier field in your table.
Not sure even if you are trying to update the row with PokemenID =2 by doing a major filtering on TypeID... So just out of assumptiong (big one), you can give a try on Case
UPDATE yourtable a
LEFT JOIN youtable b on a.pokeid = b.pokeid
SET a.typeid = (CASE
WHEN a.typeid < b.typeid THEN yourupdatevalue
WHEN a.typeid > b.typeid THEN someothervalue
ELSE a.typeid END);
If you know the pokemon ID and the type id then just add both to the where clause of your query.
UPDATE KantoType
SET TypeID = x
WHERE PokémonID = 2
AND TypeID=1
If you don't know the type ID, then you need to provide more information about what you're trying to accomplish. It's not clear why you don't have this information.
Perhaps think about what is the unique identifier in your data set.

Updating a COLUMN Based on a Field Value

All, I have four columns (A INT, B INT, C INT and D VARCHAR(1)) in a table TableName. I want to move the values from column C to either A or B based upon the value in D. So if D = 'A' I want to move the value in C to A. How can I achieve this?
DECLARE #Column COLUMN;
UPDATE TableName
SET (#Column =
(CASE
WHEN D = 'A' THEN A
WHEN D = 'B' THEN B
END)) = C;
Note. I understand the above is madness, but I am trying to express the problem as clearly as possible. I have also exhausted my search for answers. I am not new to SQL but this one has me stumped. Any help is as always, most appreciated.
Here,
UPDATE TableName
SET A = (CASE WHEN D = 'A' THEN C ELSE A END),
B = (CASE WHEN D = 'B' THEN C ELSE B END)
in this case, only 1 column will be changed since D has only one value at a time.
SQLFiddle Demo

Compare tables and identify new or changed fields

New to SQL, would like to compare fields between a stg and src table. The identify any differences between the tables and assign transaction status of 'C' for change. Any new records will be set with 'A' for add.
STG_DM_CLIENT and SRC_DM_CLIENT
What is the best way to do it, would it be best to do a some form of union all. Unsure how to proceed, any assistance welcomed.
You can identify new records by using NOT IN or NOT EXISTS
update STG_DM_CLIENT SET TransactionStatus = 'A' WHERE ID IN
(select Id from STG_DM_CLIENT
where Id not in (select Id from SRC_DM_CLIENT))
Then, you can identify changed records by comparing fields:
update STG_DM_CLIENT SET TransactionStatus = 'C' WHERE ID IN
(select STG_DM_CLIENT.Id from STG_DM_CLIENT
join SRC_DM_CLIENT on SRC_DM_CLIENT.Id = STG_DM_CLIENT.Id
where (SRC_DM_CLIENT.Field1 != STG_DM_CLIENT.Field1
OR SRC_DM_CLIENT.Field2 != STG_DM_CLIENT.Field2 ...))
update
[STG]
set
TransactionStatus = CASE WHEN [SRC].id IS NULL THEN 'A' ELSE 'C' END
from
STG_DM_CLIENT AS [STG]
left join
SRC_DM_CLIENT AS [SRC]
ON [STG].id = [SRC].id -- Or whatever relates the records 1:1
WHERE
[SRC].id IS NULL
OR [STG].field1 <> [SRC].field1
OR [STG].field2 <> [SRC].field2
OR [STG].field3 <> [SRC].field3
...
OR [STG].fieldn <> [SRC].fieldn