The UPSERT operation either updates or inserts a row in a table, depending if the table already has a row that matches the data:
if table t has a row exists that has key X:
update t set mystuff... where mykey=X
else
insert into t mystuff...
Since Oracle doesn't have a specific UPSERT statement, what's the best way to do this?
The MERGE statement merges data between two tables. Using DUAL
allows us to use this command. Note that this is not protected against concurrent access.
create or replace
procedure ups(xa number)
as
begin
merge into mergetest m using dual on (a = xa)
when not matched then insert (a,b) values (xa,1)
when matched then update set b = b+1;
end ups;
/
drop table mergetest;
create table mergetest(a number, b number);
call ups(10);
call ups(10);
call ups(20);
select * from mergetest;
A B
---------------------- ----------------------
10 2
20 1
The dual example above which is in PL/SQL was great becuase I wanted to do something similar, but I wanted it client side...so here is the SQL I used to send a similar statement direct from some C#
MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name")
VALUES ( 2097153,"smith", "john" )
However from a C# perspective this provide to be slower than doing the update and seeing if the rows affected was 0 and doing the insert if it was.
An alternative to MERGE (the "old fashioned way"):
begin
insert into t (mykey, mystuff)
values ('X', 123);
exception
when dup_val_on_index then
update t
set mystuff = 123
where mykey = 'X';
end;
Another alternative without the exception check:
UPDATE tablename
SET val1 = in_val1,
val2 = in_val2
WHERE val3 = in_val3;
IF ( sql%rowcount = 0 )
THEN
INSERT INTO tablename
VALUES (in_val1, in_val2, in_val3);
END IF;
insert if not exists
update:
INSERT INTO mytable (id1, t1)
SELECT 11, 'x1' FROM DUAL
WHERE NOT EXISTS (SELECT id1 FROM mytble WHERE id1 = 11);
UPDATE mytable SET t1 = 'x1' WHERE id1 = 11;
None of the answers given so far is safe in the face of concurrent accesses, as pointed out in Tim Sylvester's comment, and will raise exceptions in case of races. To fix that, the insert/update combo must be wrapped in some kind of loop statement, so that in case of an exception the whole thing is retried.
As an example, here's how Grommit's code can be wrapped in a loop to make it safe when run concurrently:
PROCEDURE MyProc (
...
) IS
BEGIN
LOOP
BEGIN
MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name")
VALUES ( 2097153,"smith", "john" );
EXIT; -- success? -> exit loop
EXCEPTION
WHEN NO_DATA_FOUND THEN -- the entry was concurrently deleted
NULL; -- exception? -> no op, i.e. continue looping
WHEN DUP_VAL_ON_INDEX THEN -- an entry was concurrently inserted
NULL; -- exception? -> no op, i.e. continue looping
END;
END LOOP;
END;
N.B. In transaction mode SERIALIZABLE, which I don't recommend btw, you might run into
ORA-08177: can't serialize access for this transaction exceptions instead.
I'd like Grommit answer, except it require dupe values. I found solution where it may appear once: http://forums.devshed.com/showpost.php?p=1182653&postcount=2
MERGE INTO KBS.NUFUS_MUHTARLIK B
USING (
SELECT '028-01' CILT, '25' SAYFA, '6' KUTUK, '46603404838' MERNIS_NO
FROM DUAL
) E
ON (B.MERNIS_NO = E.MERNIS_NO)
WHEN MATCHED THEN
UPDATE SET B.CILT = E.CILT, B.SAYFA = E.SAYFA, B.KUTUK = E.KUTUK
WHEN NOT MATCHED THEN
INSERT ( CILT, SAYFA, KUTUK, MERNIS_NO)
VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO);
I've been using the first code sample for years. Notice notfound rather than count.
UPDATE tablename SET val1 = in_val1, val2 = in_val2
WHERE val3 = in_val3;
IF ( sql%notfound ) THEN
INSERT INTO tablename
VALUES (in_val1, in_val2, in_val3);
END IF;
The code below is the possibly new and improved code
MERGE INTO tablename USING dual ON ( val3 = in_val3 )
WHEN MATCHED THEN UPDATE SET val1 = in_val1, val2 = in_val2
WHEN NOT MATCHED THEN INSERT
VALUES (in_val1, in_val2, in_val3)
In the first example the update does an index lookup. It has to, in order to update the right row. Oracle opens an implicit cursor, and we use it to wrap a corresponding insert so we know that the insert will only happen when the key does not exist. But the insert is an independent command and it has to do a second lookup. I don't know the inner workings of the merge command but since the command is a single unit, Oracle could execute the correct insert or update with a single index lookup.
I think merge is better when you do have some processing to be done that means taking data from some tables and updating a table, possibly inserting or deleting rows. But for the single row case, you may consider the first case since the syntax is more common.
A note regarding the two solutions that suggest:
1) Insert, if exception then update,
or
2) Update, if sql%rowcount = 0 then insert
The question of whether to insert or update first is also application dependent. Are you expecting more inserts or more updates? The one that is most likely to succeed should go first.
If you pick the wrong one you will get a bunch of unnecessary index reads. Not a huge deal but still something to consider.
Try this,
insert into b_building_property (
select
'AREA_IN_COMMON_USE_DOUBLE','Area in Common Use','DOUBLE', null, 9000, 9
from dual
)
minus
(
select * from b_building_property where id = 9
)
;
From http://www.praetoriate.com/oracle_tips_upserts.htm:
"In Oracle9i, an UPSERT can accomplish this task in a single statement:"
INSERT
FIRST WHEN
credit_limit >=100000
THEN INTO
rich_customers
VALUES(cust_id,cust_credit_limit)
INTO customers
ELSE
INTO customers SELECT * FROM new_customers;
I am looking to insert blank records into a table to get a final value of 57,816,000 records. I am starting with a table of 550 unique identifiers and for each distinct TMC name and i want to add 105,119 records for each distinct value. So each TMC would have 105,120 records. (105,119 * 550 = 57,815,450 (+ original 550 = 57,816,000))
However, the problem i am having is an error within TOAD saying this.
ORA-03113: end-of-file on communication channel
Process ID: 10272
Session ID: 247 Serial number: 1959
Script will end
It works on a small scale but i suspect its not the best way to go. Below is my script. Any advice?
declare i number(10) :=1;
begin
for i in 1..105119
Loop
insert into npmrds_dummy
select distinct tmc,
travel_time_passenger_vehicles,
travel_time_freight_trucks,
travel_time_all_vehicles,
road_number,
road_name,
epoch,
distance,
date1,
admin_level_1,
admin_level_2,
admin_level_3
from npmrds_dummy;
end loop;
end;
You could do this with one SQL statement:
insert into npmrds_dummy
select npmrds_dummy.*
from npmrds_dummy
inner join ( select 1 from dual connect by level <= 105119 )
The join with the sub select is a neat trick to multiply the number of records.
As at the start your records are unique, there is no need for the distinct, and you can just do select npmrds_dummy.*.
I am trying to write a small pl/sql script and need some help.
first, I have 2 tables called project1 , project2. both tables have a column called cust_code.
cust_code values are varchar2 type. values always begin with 1. (number 1, decimal point) and 8 digits. for example 1.10002332
when I import data into project1 table, if the last digit is 0, for example 1.22321630, the last zero is dropped and then theres only seven digits beyond the decimal point. in that case it will be 1.2232163
the script I want to write will check whether there are only 7 digits beyond the decimal point and will insert that record into the project2 table.
this is what I came up with
DECLARE
CURSOR dif IS
SELECT CUST_CODE, CUST_ID, CONTRACT_NUM, MSISDN
FROM project1
WHERE CUST_CODE IN (SELECT CUST_CODE FROM CUST_ALL);
BEGIN
FOR a in dif LOOP
IF SUBSTR(a.CUST_CODE, 10)=null
THEN
INSERT INTO project2 (cust_code)
VALUES(a.CUST_CODE);
END IF;
END LOOP;
commit;
END;
the script runs with no errors but nothing happens. on the substr function, when I chose different value than NULL, then it works. I cant figure out how to check if the 8th digit is missing.
Assaf.
Your script doesn't work because of the line:
IF SUBSTR(a.CUST_CODE, 10)=null
In plsql <something> = null will always be FALSE.
You should write:
IF SUBSTR(a.CUST_CODE, 10) IS null
But actually you don't really nead plsql, you can do it with one sql command:
INSERT INTO project2 (cust_code)
SELECT CUST_CODE, CUST_ID, CONTRACT_NUM, MSISDN
FROM project1
WHERE CUST_CODE IN (SELECT CUST_CODE FROM CUST_ALL)
AND SUBSTR(a.CUST_CODE, 10) IS null;
Try this for your condition:
IF LENGTH(a.CUST_CODE) = 10 AND SUBSTR(a.CUST_CODE,-1,10) = '0'
(check if length is 10 and also last character is 0)
Why am I getting this error and what does it mean by no statement parsed.
ORA-01003: no statement parsed
Here is the code:
PROCEDURE ORIGINAL_TABLE.UPDATE_GROUPS IS
-- cursor loaded with the swam groups
CURSOR cursor1 IS
SELECT ID, NEW_DESCRIPTION
FROM NEW_TABLE.NEW_GROUP_TABLE#DB_LINK.X;
BEGIN
FOR C1_REC IN cursor1 LOOP
UPDATE
ORIGINAL_TABLE."GROUPS"
SET
GROUP_ID = C1_REC.ID
WHERE
ORIGINAL_TABLE."GROUPS".DESCRIPTION = C1_REC.NEW_DESCRIPTION;
IF (SQL%ROWCOUNT = 0) THEN
INSERT INTO
ORIGINAL_TABLE.GROUPS("GROUP_ID", "DESCRIPTION")
VALUES (C1_REC.ID, C1_REC.NEW_DESCRIPTION);
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
END;
What I try to do with the code above is to update and old table with the values from a new table and in case that the new group doesn't exist insert it.
Update: Changed %ROWCOUNT > 0 for %ROWCOUNT = 0
Use MERGE statement, it does update/insert stuff more efficiently and pay attention your plsql doesn't provide it is intended for. It tries to make an update statement and if a record found it inserts another record. In order to fix it use
IF (SQL%ROWCOUNT = 0)
I presume the reason of the issue is the . in DBLINK name.
Moreover I would suggest to get rid of quotes for tables/fields just in case as well as schema name.
Another words delete all ORIGINAL_TABLE.
merge into groups g
using (
SELECT ID, NEW_DESCRIPTION
FROM NEW_TABLE.NEW_GROUP_TABLE#DB_LINK.X
) nt
on (nt.NEW_DESCRIPTION = g.description )
when matched then update set g.group_id = nt.id
when non matched then insert(GROUP_ID, DESCRIPTION)
values(nt.id, nt.NEW_DESCRIPTION)
Hopefully this is the last of many questions about triggers! Still working with the same database where the Order_line entity is a link entity between Order and Products. With this trigger I just want to check if the current order quantity is greater than the stock in Products. At the moment I would be doing this by using two variables, Ordered(quantity) and Total(Stock) and comparing them, but this isn't working.
If the quantity is greater than the stock the record being inserted must be deleted and an error is raised.
CREATE OR REPLACE TRIGGER Checks_Order
BEFORE INSERT ON order_line
FOR EACH ROW
DECLARE
ordered int;
total INT;
BEGIN
SELECT ol.quantity INTO ordered FROM order_line ol WHERE
ol.product_no = :new.product_no;
if(ordered>0) then
SELECT p.stock INTO total FROM
products p WHERE p.product_no = :new.product_no;
IF (ordered < total) then
DELETE FROM order_line ol where ol.order_no = :new.order_no;
RAISE_APPLICATION_ERROR(-20103, 'Not enough stock!');
END IF;
END IF;
END;
.
run
Help, please?
The trigger will not work because you cannot select or even delete from the table that the trigger belongs to.
But you don't need to actually, the value that is ordered can be obtained through :new.quantity.
And if you raise an error, the INSERT will not happen, no need to DELETE the row.
So - assuming I understood your intention correctly - the following should do what you want:
CREATE OR REPLACE TRIGGER Checks_Order
BEFORE INSERT ON order_line
FOR EACH ROW
DECLARE
total INT;
BEGIN
if (:new.quantity > 0) then
SELECT p.stock
INTO total
FROM products p
WHERE p.product_no = :new.product_no;
IF (:new.quantity > total) then
RAISE_APPLICATION_ERROR(-20103, 'Not enough stock!');
END IF;
END IF;
END;
/
Btw: I guess you want :new.quantity > total not < total