Update multiple columns in MERGE statement ORACLE - sql

I want to update multiple columns in MERGE statement,but for each column the conditions are different.How can I achieve it.
I have more than 1 million rows in both the tables.All columns are number.Except Id all 3 columns have number with precision around 18 digits
eg: 1.34255353433230675
Is there a better way to Update.Around 50,000 rows might update daily So I have to merge the updates values to target table.
I tried UPDATE and FORALL but its slow.
I basically want to merge the difference based on common ID column.Any other approach is better?
DECLARE
TYPE test1_t IS TABLE OF test.score%TYPE INDEX BY PLS_INTEGER;
TYPE test2_t IS TABLE OF test.id%TYPE INDEX BY PLS_INTEGER;
TYPE test3_t IS TABLE OF test.Crank%TYPE INDEX BY PLS_INTEGER;
TYPE test4_t IS TABLE OF test.urank%TYPE INDEX BY PLS_INTEGER;
vscore test1_t;
vid test2_t;
vcrank test3_t;
vurank test4_t;
BEGIN
SELECT id,score,crank,urank
BULK COLLECT INTO vid,vscore,vcrank,vurank
FROM test;
FORALL i IN 1 .. vid.COUNT
MERGE INTO final T
USING (SELECT vid (i) AS o_id,
vcrank (i) AS o_crank,
vurank (i) AS o_urank
vscore (i) AS o_score
FROM DUAL) S
ON (S.o_id = T.id)
WHEN MATCHED THEN
UPDATE SET T.crank = S.o_crank
WHERE T.crank <> S.o_crank;
UPDATE SET T.crank = S.o_crank
WHERE T.crank <> S.o_crank;
UPDATE SET T.crank = S.o_crank
WHERE T.crank <> S.o_crank;
UPDATE SET T.score = S.score
WHERE T.score <> S.score;
-- I tried the below case its not working either...
-- UPDATE SET T.crank = (CASE WHEN T.crank <> S.o_crank
-- THEN S.o_crank
-- END),
-- T.urank = (CASE WHEN T.urank <> S.o_urank
-- THEN S.o_urank
-- END);
COMMIT;
END;
/

I don't think you need the loop. I'm assuming your id's are primary keys and you didn't mean to repeat crank several times in your example.
Would something like this work?
Edit per Raj A's comment. This will only update rows where one of the other fields has changed. Note that this will not update rows where one is NULL and the other is not NULL.
MERGE INTO final T
USING ( SELECT id, score, crank, urank FROM test ) S
ON ( S.vid = T.id AND
( S.crank != T.crank OR S.score != T.score OR S.urank != T.urank ))
WHEN MATCHED SET crank = S.crank, score = S.score,
crank = S.crank, urank = S.urank
WHEN NOT MATCHED THEN INSERT
[... not sure what you want to do in this case ...]

Related

Update multiple rows with 'CASE WHEN EXISTS' too slow

i have this query
update t_reconcile_biller b set status = case when exists (
SELECT i.credit_amount, i.recipt_no,
i.credit_acct_no, i.recon_date
FROM t_reconcile_t24 i
WHERE i.debit_acct_no = 'IDR1720600010001'
and b.col_4 = i.credit_amount
AND b.col_7 = i.recipt_no
AND b.col_2 = i.card_no
AND i.status <> 5
) then 1 else 2 end
where b.status <> 5 AND b.file_name in ('20200923-bnf', '20200922-bnf')
AND b.col_13 = 'DESTIONATION' AND b.col_4 <> '0'
AND b.col_14 in ('A', 'FC')
which take 52 second to run with postgres, where t_reconcile_biller has 3.900 rows and t_reconcile_t24 has 32.000 rows.
How can i make this query faster and more efficient ?
The following index on the table appearing inside the exists clause might help:
CREATE INDEX rec_idx ON t_reconcile_t24 (
credit_amount, recipt_no, card_no, status );
If used, Postgres could use the above index to quickly lookup each record in the target table. In addition, the following index on the outer table might also be helpful:
CREATE INDEX biller_idx ON t_reconcile_biller (
status, file_name, col_4, col_13, col_14 );

SQL Update all rows for one column with a list of values

How can I set all rows of a column by manually typing the values?
My table has 3 rows, I want the column named mycolumn to have 3 values a, b and c (currently those values are NULL):
update mytable set mycolumn = ('a','b','c')
ORA-00907 missing right parenthesis
EDIT: my table is very simple, I have one column ID INT NOT NULL with values 1, 2, 3 and another column mycolumn with all NULL values and I want those values to become 'a' where ID = 1, 'b' where ID=2 etc.
EDIT2: I might have a huge amount of rows, so I want to avoid typing every single ID value where to replace mycolumn. Isn't it possible to match the ID values of 1 to 3 to the values 'a', 'b', 'c' in an automatic way, something like match(ID, ('a','b','c')) perhaps
I just want to replace all values of mycolumn by increasing order of ID. ID being strictly equivalent to what I call a row number in a matrix
EDIT3: I'd like a solution which would work in a general case with all sorts of values, not only the letters of the alphabet given here for simplicity. What if for example my values to replace in mycolumn are ('oefaihfoiashfe', 'fiaohoawdihoiwahopah', 'aefohdfaohdao')? However the ID row numbers will always be a sequence from 1 to N by 1.
Obviously, you should do this in a single update. Like this:
update mytable
set mycolumn = case id when 1 then 'a' when 2 then 'b' when 3 then 'c' end
;
More compact (but also more cryptic, and only works in Oracle, while case expressions are in the SQL standard):
update mytable
set mycolumn = decode(id, 1, 'a', 2, 'b', 3, 'c')
;
Note - this only works if there really are only three rows. If you have many more rows, make sure to add where id in (1, 2, 3) at the end. Otherwise all the OTHER values (in the other rows) will be updated to null!
You can try an update like the one below. This will update 1 > a, 2 > b, 3 > c, 4 > d, etc. When you reach ID 27, since there are no more letters, it will begin at a again and continue down the alphabet.
UPDATE mytable
SET mycolumn = CASE MOD (id, 26)
WHEN 0 THEN 'z'
ELSE CHR (MOD (id, 26) + 96)
END;
Update
To update based on any list of values, you can try an update statement like the one below. If you add a 4th item to the comma delimited list, ID 4 in mytable will be set to whatever you specified as the 4th value.
UPDATE mytable
SET mycolumn =
(SELECT COLUMN_VALUE
FROM (SELECT ROWNUM AS row_num, t.COLUMN_VALUE
FROM TABLE (
sys.odcivarchar2list ('oefaihfoiashfe',
'fiaohoawdihoiwahopah',
'aefohdfaohdao')) t)
WHERE row_num = id);
Hmmm . . . A row can only have one value. Perhaps something like this to assign random values:
update mytable
set mycolumn = (case floor(dbms_random.random * 3)
case 0 then 'a' case 1 then 'b' else 'c'
end)
if you want the 3 rows to have different values a, b and c then you will have to write 3 update statements.
update mytable set mycolumn = 'a' where id = 1;
update mytable set mycolumn = 'b' where id = 2;
update mytable set mycolumn = 'c' where id = 3;

Insert if not exists else update then set flag on old records

Can someone suggest the best method or a Stored Proc that allows updating a directory.
Say we receive a new list of barristers for a chambers every 2 months. We need to
Insert new records where they don't already exist - Based on First name, Surname
If they exist then update certain fields that may be blank e.g. phone number
Set any that weren't on the list to Live = 'N'
Generally getting lists in excel or an email, so having to clean up data.
select top 0 * into #temp from *table*
Insert new data #temp
If not exists (select * from *table* where forename = 'X' and surname = 'Y' and ChambersID = 12) Insert into *table* (Title, forename, surname, yearofcall, ChambersID) values ('Mr','X', 'Y', 2018, 35) else update *table* set yearofcall = 2018 where forename = 'x' and surname = 'Y' and ChambersID = 12
This seems to work. Then i do another query.
If not exists (select * from #temp where ChambersID = 12) update *table* set live = 'N' where ChambersID = 35
but long winded as have to build half query in excel... much prefer a stored proc where i can pass forename, surname and chambersid
Please try Merge in SQL. It suits your requirements. Here is a snippet
MERGE target_table USING source_table
ON merge_condition
WHEN MATCHED
THEN update_statement
WHEN NOT MATCHED
THEN insert_statement
WHEN NOT MATCHED BY SOURCE
THEN DELETE; --Instead of delete, set Live='N' in your case

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;

In SQL Server, create a sequence that assigns same value for duplicates

The duplicates can be recognized based on different conditions. For example, in this table:
ColumnA|ColumnB|ColumnC|ColumnD|DupSequence
if (ColA and ColC) are the same in two records or (ColA and ColB) is the same in two records, then they have the same value in DupSequence. Dupsequence is different for different combinations.
How can I do this in SQL Server?
EDIT
The logic is:
Record1.ColA=Record2.ColA AND Record1.ColC=Record2.ColC
OR
Record1.ColA=Record2.ColA and Record1.ColB=Record2.ColB
DupSequence is a numeric field. Those that satisfy above conditions would have the same number.
UPDATE Table
SET DupSequence = 1
FROM Table AS x
WHERE EXISTS
(
SELECT 0 FROM Table AS y
WHERE x.Id != y.Id -- whatever primary key you have here
AND ((x.ColumnA = y.ColumnA
AND x.ColumnC = y.ColumnC)
OR (x.ColumnA = y.ColumnA
AND x.ColumnB = y.ColumnB))
)
Try this (the "trick" is to select the conditions when updating:
Update table
set DupSequence = 'True'
from table t2
where table.ColumnA = t2.ColumnA
and ( table.ColumnB = T2.ColumnB
or table.ColumnC = T2.ColumnC
)
If you want to assign a unique DupSequence number .. that is a bit more difficult.