SQL With... Update - sql

Is there any way to do some kind of "WITH...UPDATE" action on SQL?
For example:
WITH changes AS
(...)
UPDATE table
SET id = changes.target
FROM table INNER JOIN changes ON table.id = changes.base
WHERE table.id = changes.base;
Some context information: What I'm trying to do is to generate a base/target list from a table and then use it to change values in another table (changing values equal to base into target)
Thanks!

You can use merge, with the equivalent of your with clause as the using clause, but because you're updating the field you're joining on you need to do a bit more work; this:
merge into t42
using (
select 1 as base, 10 as target
from dual
) changes
on (t42.id = changes.base)
when matched then
update set t42.id = changes.target;
.. gives error:
ORA-38104: Columns referenced in the ON Clause cannot be updated: "T42"."ID"
Of course, it depends a bit what you're doing in the CTE, but as long as you can join to your table withint that to get the rowid you can use that for the on clause instead:
merge into t42
using (
select t42.id as base, t42.id * 10 as target, t42.rowid as r_id
from t42
where id in (1, 2)
) changes
on (t42.rowid = changes.r_id)
when matched then
update set t42.id = changes.target;
If I create my t42 table with an id column and have rows with values 1, 2 and 3, this will update the first two to 10 and 20, and leave the third one alone.
SQL Fiddle demo.
It doesn't have to be rowid, it can be a real column if it uniquely identifies the row; normally that would be an id, which would normally never change (as a primary key), you just can't use it and update it at the same time.

Related

Improving insert query for SCD2 solution

I have two insert statements. The first query is to inserta new row if the id doesn't exist in the target table. The second query inserts to the target table only if the joined id hash value is different (indicates that the row has been updated in the source table) and the id in the source table is not null. These solutions are meant to be used for my SCD2 solution, which will be used for inserts of hundreds thousands of rows. I'm trying not to use the MERGE statement for practices.
The columns "Current" value 1 indicates that the row is new and 0 indicates that the row has expired. I use this information later to expire my rows in the target table with my update queries.
Besides indexing is there a more competent and effective way to improve my insert queries in a way that resembles the like of the SCD2 merge statement for inserting new/updated rows?
Query:
Query 1:
INSERT INTO TARGET
SELECT Name,Middlename,Age, 1 as current,Row_HashValue,id
from Source s
Where s.id not in (select id from TARGET) and s.id is not null
Query 2:
INSERT INTO TARGET
SELECT Name,Middlename,Age,1 as current ,Row_HashValue,id
FROM SOURCE s
LEFT JOIN TARGET t ON s.id = t.id
AND s.Row_HashValue = t.Row_HashValue
WHERE t.Row_HashValue IS NULL and s.ID IS NOT NULL
You can use WHERE NOT EXISTS, and have just one INSERT statement:
INSERT INTO TARGET
SELECT Name,Middlename,Age,1 as current ,Row_HashValue,id
FROM SOURCE s
WHERE NOT EXISTS (
SELECT 1
FROM TARGET t
WHERE s.id = t.id
AND s.Row_HashValue = t.Row_HashValue)
AND s.ID IS NOT NULL;

What am I doing wrong with this MERGE / INSERT query?

I have a database table that I want to update with SQL. Basically, it carries a set of description information for parts of a timetable booklet, but that isn't important. Some of the data is entered already via an application, but it is time consuming and blocks of it are "boiler plate" that want to be updated each week. In some cases, I may have missed entering the data via the application, and so also want to create data automatically if it doesn't exist.
That leads me to use a MERGE query, as follows:
MERGE INTO TTP_LINE_DESCRIPTION o
USING
(SELECT DISTINCT lv.lv_nv_id TLDE_NV_ID,
lv.lv_id TLDE_LV_ID,
dir.dir_id TLDE_DIR_ID,
8 TLDE_MED_FLAG,
1 TLDE_TYPE,
0 TLDE_SORT_NO,
'Timetable valid from ' || to_char(lv.lv_valid_from,'DD/MM/YYYY') || ' until ' || nvl2(lv.lv_valid_until,to_char(lv.lv_valid_until,'DD/MM/YYYY'),'further notice') TLDE_TEXT,
0 TLDE_ALIGNMENT,
null TLDE_FONT_SIZE,
null TLDE_FONT_STYLE
FROM LINE_VERSION lv
JOIN line_point_sequence lps ON (lv.lv_id = lps.lps_lv_id)
JOIN direction dir ON (dir.dir_id = lps.lps_dir_id)
where lv.lv_nv_id=3799 and lv.lv_id=10455244) n
ON (o.TLDE_NV_ID=n.TLDE_NV_ID
and o.TLDE_LV_ID=n.TLDE_LV_ID
and o.TLDE_DIR_ID=n.TLDE_DIR_ID
and o.TLDE_TYPE=n.TLDE_TYPE
and o.TLDE_SORT_NO=n.TLDE_SORT_NO)
WHEN MATCHED THEN
UPDATE SET o.TLDE_TEXT=n.TLDE_TEXT,
o.TLDE_ALIGNMENT=n.TLDE_ALIGNMENT,
o.TLDE_FONT_SIZE=n.TLDE_FONT_SIZE,
o.TLDE_FONT_STYLE=n.TLDE_FONT_STYLE
WHEN NOT MATCHED THEN
INSERT (o.tlde_id, o.tlde_nv_id, o.tlde_lv_id, o.tlde_dir_id,
o.tlde_med_flag, o.tlde_type, o.tlde_sort_no, o.tlde_text,
o.tlde_alignment, o.tlde_font_size, o.tlde_font_style,
o.updated_by, o.updated_on, o.updated_prog)
VALUES ((select max(tld.tlde_id)+1 from TTP_LINE_DESCRIPTION tld),
n.tlde_nv_id, n.tlde_lv_id, n.tlde_dir_id, n.tlde_med_flag,
n.tlde_type, n.tlde_sort_no, n.tlde_text, n.tlde_alignment,
n.tlde_font_size, n.tlde_font_style, 'STUARTR',
SYSDATE, 'PL/SQL Developer');
There is one thing to note here, which is the WHERE clause in the SELECT DISTINCT. The lv.lv_id=10455244 is a reference that I know will force the select to return only a single pair of rows in that SELECT, so that I can limit my testing. For this purpose, 10455244 is a valid value that is NOT currently in the TTP_LINE_DESCRIPTION table.
When I use a value that is in the table, the WHEN MATCHED code executes correctly, and updates a pair of rows in 0.016s.
Running the SELECT statement on its own using the value I have shown above returns the two rows that need adding in 0.109s.
Getting the max id and adding one to it (this is the primary key) as per the first item in the VALUES line at the end takes 0s.
Finally, if I write an INSERT INTO and explicitly write all of the values that I want to write for one of the rows, I can do the INSERT of one row in 0.016s.
But put it all together and ... nothing. The execution just sits there, executing, and doesn't appear to end. Or I get nervous waiting to see if it will end. I've left it a reasonable time, and nothing seems to go in.
So what's going on, and why won't it do what I think that it should?
You are doing this:
create table t (id, nv_id, val) as (select 1, 101, 'A' from dual);
merge into t o
using (
select 102 nv_id, 'P' val from dual union all
select 103 nv_id, 'Q' val from dual ) n
on (o.nv_id = n.nv_id)
when matched then update set val = n.val
when not matched then
insert (o.id, o.nv_id, o.val)
values ((select max(id) + 1 from t), n.nv_id, n.val);
In my case it worked, but id for inserted rows was the same: 2. When I did rollback and added primary key constraint:
alter table t add constraint t_pk primary key(id);
merge caused error - unique constraint violated. I suspect that this is something connected to your tlde_id, maybe not constraint but something else. Generating values this way is a big no-no. If your Oracle version is 12c or higher you can change this column to auto generated
generated by default on null as identity
or in older versions use sequence and trigger (find max id, add 1 and set it as starting value for sequence)
create sequence seq_t_id start with 2;
create or replace trigger t_on_insert
before insert on t for each row
begin
select seq_t_id.nextval into :new.id from dual;
end;
Then modify your merge, either by removing id from insert clause or just insert null and trigger will set proper values:
merge into t o
using (
select 102 nv_id, 'P' val from dual union all
select 103 nv_id, 'Q' val from dual ) n
on (o.nv_id = n.nv_id)
when matched then update set val = n.val
when not matched then
insert (o.nv_id, o.val)
values (n.nv_id, n.val);
Even if this does not solve your problem you should not generate ids as max() + 1, this causes problems with concurent sessions, commits, multi inserts etc.

Update columns in DB2 using randomly chosen static values provided at runtime

I would like to update rows with values chosen randomly from a set of possible values.
Ideally I would be able to provide this values at runtime, using JdbcTemplate from Java application.
Example:
In a table, column "name" can contain any name. The goal is to run through the table and change all names to equal to either "Bob" or "Alice".
I know that this can be done by creating a sql function. I tested it and it was fine but I wonder if it is possible to just use simple query?
This will not work, seems that the value is computed once, and applied to all rows:
UPDATE test.table
SET first_name =
(SELECT a.name
FROM
(SELECT a.name, RAND() idx
FROM (VALUES('Alice'), ('Bob')) AS a(name) ORDER BY idx FETCH FIRST 1 ROW ONLY) as a)
;
I tried using MERGE INTO, but it won't even run (possible_names is not found in SET query). I am yet to figure out why:
MERGE INTO test.table
USING
(SELECT
names.fname
FROM
(VALUES('Alice'), ('Bob'), ('Rob')) AS names(fname)) AS possible_names
ON ( test.table.first_name IS NOT NULL )
WHEN MATCHED THEN
UPDATE SET
-- select random name
first_name = (SELECT fname FROM possible_names ORDER BY idx FETCH FIRST 1 ROW ONLY)
;
EDIT: If possible, I would like to only focus on fields being updated and not depend on knowing primary keys and such.
Db2 seems to be optimizing away the subselect that returns your supposedly random name, materializing it only once, hence all rows in the target table receive the same value.
To force subselect execution for each row you need to somehow correlate it to the table being updated, for example:
UPDATE test.table
SET first_name =
(SELECT a.name
FROM (VALUES('Alice'), ('Bob')) AS a(name)
ORDER BY RAND(ASCII(SUBSTR(first_name, 1, 1)))
FETCH FIRST 1 ROW ONLY)
or may be even
UPDATE test.table
SET first_name =
(SELECT a.name
FROM (VALUES('Alice'), ('Bob')) AS a(name)
ORDER BY first_name, RAND()
FETCH FIRST 1 ROW ONLY)
Now that the result of subselect seems to depend on the value of the corresponding row in the target table, there's no choice but to execute it for each row.
If your table has a primary key, this would work. I've assumed the PK is column id.
UPDATE test.table t
SET first_name =
( SELECT name from
( SELECT *, ROW_NUMBER() OVER(PARTITION BY id ORDER BY R) AS RN FROM
( SELECT *, RAND() R
FROM test.table, TABLE(VALUES('Alice'), ('Bob')) AS d(name)
)
)
AS u
WHERE t.id = u.id and rn = 1
)
;
There might be a nicer/more efficient solution, but I'll leave that to others.
FYI I used the following DDL and data to test the above.
create table test.table(id int not null primary key, first_name varchar(32));
insert into test.table values (1,'Flo'),(2,'Fred'),(3,'Sue'),(4,'John'),(5,'Jim');

Oracle Merge statement not inserting data

I have came up with below sql statement on oracle 11g for merge the data.
MERGE INTO myTable tgt
USING ( SELECT myTable.ROWID AS rid
FROM myTable
WHERE myTable.myRef = 'uuuu' or
myTable.name = 'sam'
--union
--select rowId,null from dual
) src
ON (tgt.rowid = src.rid)
WHEN MATCHED THEN
update set tgt.myRef = 'uuuu',tgt.name='name_worked'
when not matched then
insert (
tgt.myRef,tgt.name) values ('RRRR','HHH');
I need to insert data except ID column here which will manage from the trigger for primary key insertion. due to error ORA-38104: Columns referenced in the ON Clause cannot be updated i used RowId approach here.
now my issue is merge statement works fine when it comes to update but fail in inserting data in this situation.
I went through this post and added the union statement. but still it fails when goes to insert duplicate record due to the constraint in my table instead of running it smoothly.
Can anyone please help me out here? Appreciate it a lot. thank you in advanced.
==========Edited===========
Please find the complete code sample and the error messages below.
MERGE INTO myTable tgt
USING ( SELECT myTable.ROWID AS rid
FROM myTable
WHERE myTable.myRef = 'RRRR' or
myTable.mytablename = 'sam'
union
select rowId from dual
) src
ON (tgt.rowid = src.rid)
WHEN MATCHED THEN
update set tgt.myRef = 'uuuu',tgt.mytablename='myt_name', tgt.name='nameA'
when not matched then
insert (
tgt.mytableid,tgt.mytablename,tgt.name,tgt.myRef) values (11,'RRRR','HHH','myref1');
and my table is -
CREATE TABLE "sa"."MYTABLE"
(
"MYTABLEID" NUMBER NOT NULL ENABLE,
"MYTABLENAME" VARCHAR2(20 BYTE) NOT NULL ENABLE,
"NAME" VARCHAR2(20 BYTE),
"MYREF" VARCHAR2(20 BYTE),
CONSTRAINT "MYTABLE_PK" PRIMARY KEY ("MYTABLEID", "MYTABLENAME")
)
if i run this first time it will insert the record as expected.
when i run it the second time my expectation is it should match the myRef = 'RRRR' and do the update. i intentionally put 'or' between condition because if i find any value exist in the table it should go and update the existing record.
but instead of doing that update it will throw this error because merge statement try to insert again.
SQL Error: ORA-00001: unique constraint (sa.MYTABLE_PK) violated
00001. 00000 - "unique constraint (%s.%s) violated"
my requirement is when it run the first time it should insert and when i run the same again it should pick that record and update it. Please let me know what to adjust in my merge statement in order to work as expected here. Thanks in advance.
The below MERGE query is what you are looking for.
MERGE INTO myTable tgt
USING ( select x.rid from
(SELECT myTable.ROWID AS rid
FROM myTable
WHERE myTable.myRef IN ('myref1','uuuu')) x
right outer join dual
on x.rowid <> dual.rowid
) src
ON (tgt.rowid = src.rid)
WHEN MATCHED THEN
update set tgt.myRef = 'uuuu',tgt.mytablename='myt_name', tgt.name='nameA'
when not matched then
insert (tgt.mytableid,tgt.mytablename,tgt.name,tgt.myRef) values (mytable_seq.nextval,'RRRR','HHH','myref1');
There have been 3 changes made from the query that you provided.
Inside the 'using' subquery 'RRRR' was being checked in myTable.myRef column, but while inserting 'myref1' was being inserted in the same. Hence this has been changed in the using subquery to check 'myref1'.
'uuuu' has been introduced in the same check.
DUAL tas been introduced in right outer join.
The query will process as below -
1.During first run, there would be no row in mytable. Hence the right outer join with dual will prevail. This will enable the insert to happen.
During 2nd run, there will be a row with myRef column of 'myref1'. Its Rowid will be picked up and the Update will happen. After update, myRef column will be updated to 'uuuu'.
3.During all subsequent runs, 1 row will always be returned in the inside using subquery, this time because of the column value of 'uuuu'. This will enable the update to happen , which will again update the columns with the same existing values.
Thus in effect, 1st time INSERT will happen and in all subsequent times UPDATE will take place.
It appears you want something like the following:
MERGE INTO MYTABLE t
USING (SELECT newID, newTable_name from DUAL) d
ON (t.MYTABLEID = d.newID AND
t.MYTABLENAME = d.newTable_name)
WHEN MATCHED THEN
UPDATE SET NAME = newName,
MYREF = newRef
WHEN NOT MATCHED THEN
INSERT (MYTABLEID, MYTABLENAME, NAME, MYREF)
VALUES (newID, newTable_name, newName, newRef)
where the variables newID, newTable_name, newName, and newRef are populated from whatever source of data you're using.
Also - do you really want the primary key to be (MYTABLEID, MYTABLENAME)? Can you actually have multiple rows with the same MYTABLEID value? And do you really want to allow multiple rows having the same MYTABLENAME value? My thought is that the primary key should be MYTABLEID, with a UNIQUE constraint on MYTABLENAME. If this is the case then MYTABLEID is redundant, and MYTABLENAME could be used as the primary key. ????
Best of luck.
I was able to make this work on below query.
MERGE INTO myTable tgt
USING ( SELECT (SELECT myTable.ROWID AS rid
FROM myTable
WHERE myTable.myRef = '2' or
myTable.mytablename = 'sam'
) as rid from dual) src
ON (tgt.rowid = src.rid)
WHEN MATCHED THEN
update set tgt.myRef = 'uuuu',tgt.mytablename='test1', tgt.name='nameA'
when not matched then
insert (
tgt.mytableid,tgt.mytablename,tgt.name,tgt.myRef) values (11,'RRRR1','1','2');
first time it will insert the row and for subsequent runs it will update the row.

Updating a table row multiple values oracle 11g

I m struggling to update one column for a table with a sub query. I have a table where currently one of the values is null.
Currently I have:
UPDATE DW1_PURCHASES SET DW1_PURCHASES.TOTAL_AMT =
(
SELECT DW1_PURCHASES.QUANTITY * DW1_PRODUCTS.PRICE
FROM DW1_PURCHASES, DW1_PRODUCTS
WHERE DW1_PURCHASES.PRODUCT_ID = DW1_PRODUCTS.PRODUCT_ID
)
Although subquery returns data which I need to insert I get a error of single row subquery returns multiple rows.How do I basically shift subquery result to the table?
Thanks.
You don't have to JOINthe update table inside the sub-query. Just correlate the sub-query with update table
UPDATE DW1_PURCHASES
SET DW1_PURCHASES.TOTAL_AMT =
(
SELECT DW1_PURCHASES.QUANTITY * DW1_PRODUCTS.PRICE
FROM DW1_PRODUCTS
WHERE DW1_PURCHASES.PRODUCT_ID = DW1_PRODUCTS.PRODUCT_ID
)
Note : If your DW1_PRODUCTS table has duplicated PRODUCT_ID then even now there is a possibility to get the same error