Case, Select and Update in a single query - sql

I am trying to do the following, but I get an error on DELETE FROM, any idea why? So how can I check if the createts=endts in person_net_contacts is true for certain persondID then Delete... else Update...
SELECT
createts, endts,
CASE WHEN createts = endts
THEN DELETE FROM person_net_contacts where personid='276178';
ELSE UPDATE person_net_contacts SET endts = current_timestamp
WHERE personid='276178';
FROM person_net_contacts

You can do this with a single query if you use writeable CTEs:
with to_check as (
SELECT personid, createts, endts, createts = endts as delete_this
WHERE personid = '276178'
FROM person_net_contacts
), deleted as (
delete from person_net_contacts
where personid = (select personid
from to_check
where delete_this)
)
update person_net_contacts pnc
SET endts = current_timestamp
from to_check tc
where tc.personid = pnc.personid
and not tc.delete_this;
The first CTE selects the row from the table and creates a flag that tells us if the row should be deleted or not. The second CTE then deletes rows based on that flag and the final statement updates the row if needed.
This also works for multiple rows assuming that personid is unique.
You should also compare numbers to numbers '276178' is a string value, not a number. If personid is defined as a number data type (integer, bigint or something similar) you should use where personid = 276178. Never put single quotes around numbers.

You can delete or update a row in one query using with statement, e.g.:
with delete_person as (
delete from person_net_contacts
where personid = '276178'
and createts = endts
returning 'deleted'::text, createts, endts
),
update_person as (
update person_net_contacts
set endts = current_timestamp
where personid = '276178'
and createts is distinct from endts
returning 'updated'::text, createts, endts
)
select *
from delete_person
union all
select *
from update_person;

You can't mix SELECT, DELETE, UPDATE, CASE like this. I assume you want to do this:
DELETE FROM person_net_contacts
WHERE personid='276178' AND createts=endts;
UPDATE person_net_contacts
SET endts = current_timestamp
WHERE personid='276178' AND createts!=endts;
-- the last condition only kicks in if you run the two statements in a different order

This mix of operations (SELECT and DELETE, UPDATE) are not part of a SQL expression. However you can run a SELECT, DELETE and UPDATE operations independently using the right conditions:
All the select fields must be an expression that returns some value: UPDATE or DELETE operations are not intended for that.
SELECT createts, endts
FROM person_net_contacts
WHERE personid='276178';
Delete the record when createts = endts
DELETE FROM person_net_contacts where personid='276178' and createts = endts;
Update the record when createts <> endts (the last condition can be ommited , the update only affects records that are not deleted in the previous command)
UPDATE person_net_contacts SET endts = current_timestamp where personid='276178' and createts <> endts;

IF EXISTS(SELECT 1 FROM person_net_contacts WHERE personid='276178' AND DATEDIFF(DAY,createts,endts) = 0)
BEGIN
DELETE FROM person_net_contacts where personid='276178'
END
ELSE
BEGIN
UPDATE person_net_contacts SET endts = current_timestamp where personid='276178'
END

Related

update value only when value changed

I have to update main_table data from another table. Below is statement
We will have value in any one column among value_string,value_date in a row or for a key, based on type_key. (like if type_key is string then value_string will have value and value_date is null). There is a trigger which ensure this constraint.
update main_table t set
value_string=value_string,
value_date=value_date,
value_int=value_int,
updated_on=now(),
status=status
from import_table imp where t.key=imp.key
even if there was no change in values of value_string or value_date, then updated_on will change. I want updated_on to be updated only if there is change in values. so i'm changing update query to below
update main_table t set
value_string=value_string,
value_date=value_date,
updated_on= (case when type_key='string' and t.value_string!=imp.value_string
then now()
when type_key='date' and t.value_date!=imp.value_date
then now()
when type_key='int' and t.value_int!=imp.value_int
then now()
else updated_on end),
status=status
from import_table imp where t.key=imp.key
Is there a better approach to rewrite above query to improve the performance of query ?
I would add a WHERE condition that only changes the row if at least one of the values is different.
update main_table t
set
value_string = imp.value_string,
value_date = imp.value_date,
value_int = imp.value_int,
updated_on = now(),
status = imp.status
from import_table imp
where t.key = imp.key
and ( t.value_string is distinct from imp.value_string
or t.value_date is distinct from imp.value_date
or t.value_int is distinct from imp.value_int
or t.status is distinct from imp.status);
alternatively you can write it as
where t.key = imp.key
and (t.value_string, t.value_date, t.value_int, t.status)
is distinct from (imp.value_string, imp.value_date, imp.value_int, imp.status);

What is wrong with my select for update skip locked oracle query

I need to update multiple records in a table by matching a particular column (dest_file_path in this case). I'm joining the table with itself. Inner select to get the matching column joined with the same table to get all the records with the one matching column
update job_queue
set status = 'RUNNING', last_updated_time = systimestamp
where rowid in
(
select jq.rowid from job_queue jq,
(select DEST_FILE_PATH from JOB_QUEUE
where status not in ('RUNNING','FINISHED','SKIPPED') and operation_type in ('copy')
order by case when status='FAILED' then 0 else 1 end desc, dest_file_path fetch first 1 rows only) dest
where jq.dest_file_path = dest.dest_file_path and jq.operation_type='copy'
)
for update skip locked;
Unfortunately I'm getting this error:
Error at Command Line : 25 Column : 1
Error report -
SQL Error: ORA-00933: SQL command not properly ended
00933. 00000 - "SQL command not properly ended"
*Cause:
*Action:
Although if I do a simple select instead of update it works just fine. Here is the select query.
select rowid,dest_file_path from job_queue
where rowid in
(
select jq.rowid from job_queue jq,
(select DEST_FILE_PATH from JOB_QUEUE
where dest_file_path='/file/path'
and status not in ('RUNNING','FINISHED','SKIPPED') and operation_type in ('copy')
order by case when status='FAILED' then 0 else 1 end desc, dest_file_path fetch first 1 rows only) dest
where jq.dest_file_path = dest.dest_file_path and jq.operation_type='copy'
)
for update skip locked;
Any suggestions on how to tackle the update.
The for update clause only exists for select statements, not for update statements (which have to lock the rows they update). It sounds like you want to run the select for update first, load the key(s) into a local variable/ collection, then do a subsequent update using the values you saved off.
you can run your query using pl/sql.
BEGIN
FOR R
IN (SELECT JQ.ROWID
FROM JOB_QUEUE JQ,
(SELECT *
FROM ( SELECT DEST_FILE_PATH
FROM JOB_QUEUE
WHERE STATUS NOT IN ('RUNNING',
'FINISHED',
'SKIPPED')
AND OPERATION_TYPE IN ('copy')
ORDER BY (CASE STATUS
WHEN 'FAILED' THEN 0
ELSE 1
END) DESC)
WHERE ROWNUM = 1) DEST
WHERE JQ.DEST_FILE_PATH = DEST.DEST_FILE_PATH
AND JQ.OPERATION_TYPE = 'copy'
FOR UPDATE
SKIP LOCKED)
LOOP
UPDATE JOB_QUEUE
SET STATUS = 'RUNNING', LAST_UPDATED_TIME = SYSTIMESTAMP
WHERE ROWID = R.ROWID;
END LOOP;
END;
/

How to update based on original values in SQL

I'm trying to do a very simple update of table where I want to substitute one string for another in a large table. I'm trying to do it in batches because the table has hundreds of millions of rows.
But I need to replace the string differently depending on what was there, but still meet the requirement of the batch number being in the most recent timestamp.
I don't want to run 5 different scripts separately.
I've gotten this far, but I don't know how to apply the update correctly depending on what was there originally.
Any ideas on how to do it efficiently?
DECLARE #batchsize bigint = 1000;
WHILE 1 = 1
BEGIN
UPDATE TOP (#batchsize) Table1
SET Row1 = 'To1' -- want to set To2, To3, To4, To5, etc. depending on what was in there already
FROM (SELECT TOP (#batchsize) Id
FROM Table1
ORDER BY TimeStamp DESC) tto
WHERE Table1.Row1 in ('From1', 'From2', 'From3', 'From4', 'From5') AND Table1.Id = tto.Id;
if ##ROWCOUNT < #batchsize
BEGIN
PRINT('All Done');
BREAK;
END;
END;
Seems like this should do it:
SET Row1 = CASE
WHEN Row1 = 'From1' THEN 'To1'
WHEN Row1 = 'From2' THEN 'To2'
etc
END
Are you simply looking for a case expression?
UPDATE TOP (#batchsize) Table1
SET Row1 = (CASE table1.Row1
WHEN 'From1' THEN 'To1'
WHEN 'From2' THEN 'To2'
WHEN 'From3' THEN 'To3'
WHEN 'From4' THEN 'To4'
WHEN 'From5' THEN 'To5'
END)
FROM (SELECT TOP (#batchsize) Id
FROM Table1
ORDER BY TimeStamp DESC
) tto
WHERE Table1.Row1 in ('From1', 'From2', 'From3', 'From4', 'From5') AND
Table1.Id = tto.Id;

SQL - Update same table based on a condition in WHERE

I have a requirement to write an SP something like below. This SP is called every second from a .net windows service app.
SELECT TOP 1 ID
FROM TABLE
WHERE GETDATE() < ExpirationDate
ORDER BY ID
If GetDate() > ExpirationDate update the record column with a value.
I do not want to do the below as every time the SP is called, it looks at the whole table for update.
I only want to update the record the SP is currently looking at.
UPDATE [TableName]
SET fiel1=1 , field2 = 'abc'
WHERE ExpirationDate > GETDATE()
Thanks in advance.
Use an Update/Select. Depending upon how your SQL instance is configured, you might have to wrap the inner select with the second example below
UPDATE [TableName] SET [TableName].fiel1= 1 , [TableName].field2 = 'abc'
FROM (
SELECT TOP 1 ID
FROM TABLE
WHERE GETDATE() < ExpirationDate
ORDER BY ID
) a
where [TableName].ID = a.ID
Might have to try this:
UPDATE [TableName] SET [TableName].fiel1= 1 , [TableName].field2 = 'abc'
FROM (
SELECT TOP 100 PERCENT * FROM (
SELECT TOP 1 ID
FROM TABLE
WHERE GETDATE() < ExpirationDate
ORDER BY ID
) b
) a
where [TableName].ID = a.ID
This will update the single row value you return in your original script:
UPDATE [table]
SET
Field1 = 1,
Field2 = abc
WHERE ID =
(
SELECT MIN(ID)
FROM [table]
WHERE GETDATE() < ExpirationDate
)

PG Rule - Using NEW in subquery

I have a rule that runs on update to a source table. The rule queries data across multiple other tables, formats the data, and inserts it into another transform table. Here is an example of what I have so far.
CREATE OR REPLACE RULE
value_insert
AS ON UPDATE TO
source_table
DO ALSO INSERT INTO transform_table(
username
,status
,section
)
SELECT
username
,MAX(status)
,MAX(section)
FROM
(
SELECT
username
,CASE
WHEN item = status
THEN value
ELSE NULL
END AS status
,CASE
WHEN item = section
THEN value
ELSE NULL
END AS section
FROM
(
SELECT
username
,item
,value
FROM
table1
,table2
WHERE
item = status
OR item = section
AND source_table.username = NEW.username
)
)
GROUP BY
username
I am trying to pass the NEW value into the subquery, but I receive the error "ERROR: subquery in FROM cannot refer to other relations of same query level". Using NEW in the outermost where statement works, but the query take a long time due to the large amount of data in the tables.
Is it possible to pass the NEW value into the subquery of this rule? I am using PG 8.3 and PGAdmin 1.12
Solved this by implementing a trigger function. As far as I can tell, you cannot pass the NEW value to a subquery in a rule (PG 8.3).
The script below will collect only data from table1 and table2 that corresponds to updated record in old_table, reformat the data, and insert it into new_table. By switching to a trigger and inserting the argument into the base query, the processing time has dropped from ~2 seconds to ~50 ms.
The function:
CREATE OR REPLACE FUNCTION get_data()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO new_table(
username
,status
,section
)
SELECT
username
,MAX(status)
,MAX(section)
FROM
(
SELECT
username
,CASE
WHEN item = status
THEN value
ELSE NULL
END AS status
,CASE
WHEN item = section
THEN value
ELSE NULL
END AS section
FROM
(
SELECT
username
,item
,value
FROM
table1
,table2
WHERE
(item = status OR item = section)
AND table1.username = table2.username
AND table2.username = old_table.username
AND old_table.username = NEW.username
)
)
GROUP BY
username;
RETURN NEW;
END;
$$
LANGUAGE plpgSQL;
The trigger:
CREATE TRIGGER
old_table_trigger
BEFORE UPDATE ON
old_table
FOR EACH ROW EXECUTE PROCEDURE get_data();