Oracle Merge statement with conditional insert - oracle

I use following statement to insert or update current document versions our customers have signed:
MERGE INTO schema.table ccv
USING (select :customer_id as customer_id, :doc_type as doc_type, :version as Version FROM DUAL) n
ON (ccv.customer_id = n.customer_id and ccv.doc_type = n.doc_type)
WHEN MATCHED
THEN UPDATE
set ccv.version = n.version
DELETE WHERE ccv.version is null
WHEN NOT MATCHED
THEN INSERT
( customer_id, doc_type, version)
VALUES
(:customer_id,:doc_type,:version)
Basically I want to avoid inserting on the same condition when I am deleting with DELETE WHERE statement.
The thing is, that there is three different document types (doc_type), but only one or two might be simulatenously signed.
If a client signed a document, then I want to store it's version, if not, then I do not want a record with that document in database.
So, when the new :version is null I delete the existing row. That works great.
The problem is, when there were no documents of that customer stored, then oracle actually inserts a record with version = NULL.
How can I avoid inserting records when :version is null?
Is splitting the merge to separate delete, update and insert statement the only way to do that?

If you're using 10g, you may use conditions for the insert as well:
http://www.oracle-developer.net/display.php?id=310
Rgds.

Related

Combine MERGE and INSERT ALL statment

I have an issue that could be solved by implementing full aduting either with triggers or flash data archive but that's much more than is required.
So right now we are performing a merge which is updating a row when its present or inserting when its not. It works well and was easy to write. We now have a new requirement which is that users must know which rows have been updated or inserted. Yes this could be accomplished by introducing another field into the table, but thats not allowed because that would change the table. So we are forced to create one or two tables which will identify which rows are updated or inserted via the PK.
What I am hoping to do is take the existing MERGE statement and add the ability to insert into the secondary table, but I have not been able to find any merge statements which work that way, and INSERT ALL lacks the more complicated conditionals of the merge.
Here is the structure of the MERGE statement thats currently being used.
MERGE INTO EXISTING_TABLE ET USING TMP_TABLE TMP ON (HRR.ID = TMP.ID)
WHEN MATCHED THEN UPDATE SET
ET.ID = TMP.ID,
ET.TITLE_EN = TMP.TITLE_EN,
ET.TITLE_FR = TMP.TITLE_FR,
WHEN NOT MATCHED THEN INSERT (ID, TITLE_EN, TITLE_FR)
VALUES (TMP.ID, TMP.TITLE_EN, TMP.TITLE_FR);
Below is the way Im hoping to accomplish the MERGE INSERT ALL.
MERGE INTO EXISTING_TABLE ET USING TMP_TABLE TMP ON (HRR.ID = TMP.ID)
WHEN MATCHED THEN UPDATE
SET
ET.ID = TMP.ID,
ET.TITLE_EN = TMP.TITLE_EN,
ET.TITLE_FR = TMP.TITLE_FR,
INSERT INTO NEW_TABLE (ID, TYPE) VALUES (TMP.ID, 'U')
WHEN NOT MATCHED THEN INSERT ALL
INTO EXISTING_TABLE (ID, TITLE_EN, TITLE_FR) VALUES (TMP.ID, TMP.TITLE_EN, TMP.TITLE_FR),
INTO NEW_TABLE (ID, TYPE) VALUES (TMP.ID, 'I');
The only other way to reasonably accomplish this that I can see would be with a PLSQL block which works on row statements and would be slower.
At our site, we use after triggers to update audit stuff. It has an advantage of tracking the changes via program, or if someone fat fingers an update statement.
That might do the job for you.

Oracle Merge ignore duplicates

I have a table accessible via an oracle database link, which I'm attempting to pull into a local database table because reasons.
MERGE INTO MEMBERSHIPS LOCAL
USING (
SELECT DISTINCT
REMOTE.GROUP_NAME "GROUP_NAME",
REMOTE.USER_ACCOUNT "USERNAME",
REMOTE.SOME_OTHER_COLUMN "COL3"
FROM MEMBERSHIPS#link REMOTE
) REMOTE
ON (
REMOTE.GROUP_NAME = LOCAL.GROUP_NAME AND
REMOTE.USERNAME = LOCAL.USERNAME
)
WHEN MATCHED THEN
UPDATE SET
LOCAL.COL3 = REMOTE.COL3
LOCAL.UPDATED_AT = sysdate
WHEN NOT MATCHED THEN
INSERT (ID, GROUP_NAME, USERNAME, COl3, CREATED_AT, UPDATED_AT)
VALUES (MEMBERSHIPS_SEQ.NEXTVAL, REMOTE.GROUP_NAME, REMOTE.USERNAME, REMOTE.COl3, sysdate, sysdate)
Most unfortunately, the owner of the original database has not lost much sleep worrying about data integrity, so in the 3 millionish rows, there are 71 duplicates, which blows up my Unique Index on Group Name, Username. The merge will process if I remove the uniqueness constraint, however these rows will then blow up on a subsequent execution of the query with a ORA-30926: unable to get a stable set of rows in the source tables.
This is the sort of thing that will get run on a daily basis, so I need to find a way to ignore duplicates
EDIT:
I would have thought the distinct would have took care of the problem for me, but it doesn't. I still get duplicates:
SELECT DISTINCT
REMOTE.GROUP_NAME,
REMOTE.USER_ACCOUNT
COUNT(*)
FROM MEMBERSHIPS#link REMOTE
GROUP BY
REMOTE.GROUP_NAME,
REMOTE.USER_ACCOUNT
HAVING COUNT(*) > 1;
Shows 71 GROUP_NAME/USER_ACCOUNT combinations that are still duplicated
In similar situation you can always try to rank rows to avoid duplicates, instead of DISTINCT.
In this case it would be something like that:
MERGE INTO MEMBERSHIPS LOCAL
USING (
SELECT rank() over(partition by REMOTE.GROUP_NAME
,REMOTE.USER_ACCOUNT
order by NVL(REMOTE.UPDATED_AT,REMOTE.CREATED_AT) DESC NULLS LAST) r,
REMOTE.GROUP_NAME "GROUP_NAME",
REMOTE.USER_ACCOUNT "USERNAME",
REMOTE.SOME_OTHER_COLUMN "COL3"`
FROM MEMBERSHIPS#link REMOTE ) REMOTE
ON (
REMOTE.GROUP_NAME = LOCAL.GROUP_NAME
AND REMOTE.USERNAME = LOCAL.USERNAME
AND REMOTE.r = 1
)
WHEN MATCHED THEN
UPDATE SET
LOCAL.COL3 = REMOTE.COL3
LOCAL.UPDATED_AT = sysdate
WHEN NOT MATCHED THEN
INSERT (ID, GROUP_NAME, USERNAME, COl3, CREATED_AT, UPDATED_AT)
VALUES (MEMBERSHIPS_SEQ.NEXTVAL, REMOTE.GROUP_NAME, REMOTE.USERNAME, REMOTE.COl3, sysdate, sysdate);
I tried to reproduce the problem you describe, using two local tables. With the merge as you've provided it, I have no problems caused by duplicates in the source table (in Oracle 11.2.0.4). If I remove the DISTINCT keyword from the subquery in the USING clause, then I get exactly the problems you have described - constraint violations in the first attempt, or ORA-30926 in the second attempt if I remove the unique constraint.
The two explanations I can think of for this are that (a) you are hitting some bug in Oracle, possibly involving DISTINCT in remote subqueries, or (b) the merge statement you have actually been running doesn't include that DISTINCT. (I also considered the possibility that NULL values might be causing unexpected results from the DISTINCT operations, but I couldn't come up with a way that would happen.)
EDIT:
Another half-thought-out explanation - if the two databases use different character sets, I wonder if it is possible that values that are distinct in the original table are getting converted in transit to identical values?

how to check the last_modified date with null values in oracle

I have a table called orders in which data is loaded through a CSV file into a loader table for every three hours. I have a column last_modified set to SYSDATE which records the insert and update on the table. Recently, I have observed that the last_modified column has null values for more than 100k records when update happens. Is there any way to fix this issue?
Merge into orders d
using (select * from ods_prm_data ) s
on (d.order_id = s.order_id)
when not matched then
insert (d.order_id ,d.ID, d.last_modified)
values (s.order_id, s.ID,s.order_seq.val,SYSDATE)
when matched then
update set d.last_modified = SYSDATE;
Take a look at the WHEN NOT MATCHED branch of the MERGE statement. I'm rather surprised that this statement even compiled because the INSERT field list (d.order_id ,d.ID, d.last_modified) specifies three fields, but the VALUES list (s.order_id, s.ID,s.order_seq.val, SYSDATE)shows FOUR values. I'm not sure how this slipped past the compiler, and I have no idea how it's being executed; however, it may help to explain the issues you're having.
Best of luck.

hsqldb update on insert

Does anyone know of a solution to make HSQLDB update columns by calling INSERT. I would like my code to be able to insert, and if there's already data, to update that data. I know MySQl, I believe, has 'ON DUPLICATE KEY UPDATE'. I can't seem to find any recent documentation on this.
A good sample is sometimes better the formal documentation on MERGE statement :)
Sample 1
For a table (MY_TABLE) with tho columns (COL_A and COL_B), where the first column is a primary key:
MERGE INTO MY_TABLE AS t USING (VALUES(?,?)) AS vals(a,b)
ON t.COL_A=vals.a
WHEN MATCHED THEN UPDATE SET t.COL_B=vals.b
WHEN NOT MATCHED THEN INSERT VALUES vals.a, vals.b
Sample 2
Let's add another column (COL_C) to our table:
MERGE INTO MY_TABLE AS t USING (VALUES(?,?,?)) AS vals(a,b,c)
ON t.COL_A=vals.a
WHEN MATCHED THEN UPDATE SET t.COL_B=vals.b, t.COL_C=vals.c
WHEN NOT MATCHED THEN INSERT VALUES vals.a, vals.b, vals.c
Sample 3
Now let's change the primary key to consist of first two columns (COL_A and COL_B):
MERGE INTO MY_TABLE AS t USING (VALUES(?,?,?)) AS vals(a,b,c)
ON t.COL_A=vals.a AND t.COL_B=vals.b
WHEN MATCHED THEN UPDATE SET t.COL_C=vals.c
WHEN NOT MATCHED THEN INSERT VALUES vals.a, vals.b, vals.c
Enjoy!
HSQLDB provides the MERGE statement for this purpose.
http://hsqldb.org/doc/2.0/guide/dataaccess-chapt.html#dac_merge_statement
Alternatively, you can write a short stored procedure to insert, then catch any constraint violation exception and perform an update.
http://hsqldb.org/doc/2.0/guide/sqlroutines-chapt.html#src_psm_handlers
Update: From version 2.3.4 HSQLDB also supports the MySQL syntax for ON DUPLICATE KEY

ORA-30926: unable to get a stable set of rows in the source tables

I am getting
ORA-30926: unable to get a stable set of rows in the source tables
in the following query:
MERGE INTO table_1 a
USING
(SELECT a.ROWID row_id, 'Y'
FROM table_1 a ,table_2 b ,table_3 c
WHERE a.mbr = c.mbr
AND b.head = c.head
AND b.type_of_action <> '6') src
ON ( a.ROWID = src.row_id )
WHEN MATCHED THEN UPDATE SET in_correct = 'Y';
I've ran table_1 it has data and also I've ran the inside query (src) which also has data.
Why would this error come and how can it be resolved?
This is usually caused by duplicates in the query specified in USING clause. This probably means that TABLE_A is a parent table and the same ROWID is returned several times.
You could quickly solve the problem by using a DISTINCT in your query (in fact, if 'Y' is a constant value you don't even need to put it in the query).
Assuming your query is correct (don't know your tables) you could do something like this:
MERGE INTO table_1 a
USING
(SELECT distinct ta.ROWID row_id
FROM table_1 a ,table_2 b ,table_3 c
WHERE a.mbr = c.mbr
AND b.head = c.head
AND b.type_of_action <> '6') src
ON ( a.ROWID = src.row_id )
WHEN MATCHED THEN UPDATE SET in_correct = 'Y';
You're probably trying to to update the same row of the target table multiple times. I just encountered the very same problem in a merge statement I developed. Make sure your update does not touch the same record more than once in the execution of the merge.
A further clarification to the use of DISTINCT to resolve error ORA-30926 in the general case:
You need to ensure that the set of data specified by the USING() clause has no duplicate values of the join columns, i.e. the columns in the ON() clause.
In OP's example where the USING clause only selects a key, it was sufficient to add DISTINCT to the USING clause. However, in the general case the USING clause may select a combination of key columns to match on and attribute columns to be used in the UPDATE ... SET clause. Therefore in the general case, adding DISTINCT to the USING clause will still allow different update rows for the same keys, in which case you will still get the ORA-30926 error.
This is an elaboration of DCookie's answer and point 3.1 in Tagar's answer, which from my experience may not be immediately obvious.
How to Troubleshoot ORA-30926 Errors? (Doc ID 471956.1)
1) Identify the failing statement
alter session set events ‘30926 trace name errorstack level 3’;
or
alter system set events ‘30926 trace name errorstack off’;
and watch for .trc files in UDUMP when it occurs.
2) Having found the SQL statement, check if it is correct (perhaps using explain plan or tkprof to check the query execution plan) and analyze or compute statistics on the tables concerned if this has not recently been done. Rebuilding (or dropping/recreating) indexes may help too.
3.1) Is the SQL statement a MERGE?
evaluate the data returned by the USING clause to ensure that there are no duplicate values in the join. Modify the merge statement to include a deterministic where clause
3.2) Is this an UPDATE statement via a view?
If so, try populating the view result into a table and try updating the table directly.
3.3) Is there a trigger on the table? Try disabling it to see if it still fails.
3.4) Does the statement contain a non-mergeable view in an 'IN-Subquery'? This can result in duplicate rows being returned if the query has a "FOR UPDATE" clause. See Bug 2681037
3.5) Does the table have unused columns? Dropping these may prevent the error.
4) If modifying the SQL does not cure the error, the issue may be with the table, especially if there are chained rows.
4.1) Run the ‘ANALYZE TABLE VALIDATE STRUCTURE CASCADE’ statement on all tables used in the SQL to see if there are any corruptions in the table or its indexes.
4.2) Check for, and eliminate, any CHAINED or migrated ROWS on the table. There are ways to minimize this, such as the correct setting of PCTFREE.
Use Note 122020.1 - Row Chaining and Migration
4.3) If the table is additionally Index Organized, see:
Note 102932.1 - Monitoring Chained Rows on IOTs
Had the error today on a 12c and none of the existing answers fit (no duplicates, no non-deterministic expressions in the WHERE clause). My case was related to that other possible cause of the error, according to Oracle's message text (emphasis below):
ORA-30926: unable to get a stable set of rows in the source tables
Cause: A stable set of rows could not be got because of large dml activity or a non-deterministic where clause.
The merge was part of a larger batch, and was executed on a live database with many concurrent users. There was no need to change the statement. I just committed the transaction before the merge, then ran the merge separately, and committed again. So the solution was found in the suggested action of the message:
Action: Remove any non-deterministic where clauses and reissue the dml.
SQL Error: ORA-30926: unable to get a stable set of rows in the source tables
30926. 00000 - "unable to get a stable set of rows in the source tables"
*Cause: A stable set of rows could not be got because of large dml
activity or a non-deterministic where clause.
*Action: Remove any non-deterministic where clauses and reissue the dml.
This Error occurred for me because of duplicate records(16K)
I tried with unique it worked .
but again when I tried merge without unique same proble occurred
Second time it was due to commit
after merge if commit is not done same Error will be shown.
Without unique, Query will work if commit is given after each merge operation.
I was not able to resolve this after several hours. Eventually I just did a select with the two tables joined, created an extract and created individual SQL update statements for the 500 rows in the table. Ugly but beats spending hours trying to get a query to work.
As someone explained earlier, probably your MERGE statement tries to update the same row more than once and that does not work (could cause ambiguity).
Here is one simple example. MERGE that tries to mark some products as found when matching the given search patterns:
CREATE TABLE patterns(search_pattern VARCHAR2(20));
INSERT INTO patterns(search_pattern) VALUES('Basic%');
INSERT INTO patterns(search_pattern) VALUES('%thing');
CREATE TABLE products (id NUMBER,name VARCHAR2(20),found NUMBER);
INSERT INTO products(id,name,found) VALUES(1,'Basic instinct',0);
INSERT INTO products(id,name,found) VALUES(2,'Basic thing',0);
INSERT INTO products(id,name,found) VALUES(3,'Super thing',0);
INSERT INTO products(id,name,found) VALUES(4,'Hyper instinct',0);
MERGE INTO products p USING
(
SELECT search_pattern FROM patterns
) o
ON (p.name LIKE o.search_pattern)
WHEN MATCHED THEN UPDATE SET p.found=1;
SELECT * FROM products;
If patterns table contains Basic% and Super% patterns then MERGE works and first three products will be updated (found). But if patterns table contains Basic% and %thing search patterns, then MERGE does NOT work because it will try to update second product twice and this causes the problem. MERGE does not work if some records should be updated more than once. Probably you ask why not update twice!?
Here first update 1 and second update 1 are the same value but only by accident. Now look at this scenario:
CREATE TABLE patterns(code CHAR(1),search_pattern VARCHAR2(20));
INSERT INTO patterns(code,search_pattern) VALUES('B','Basic%');
INSERT INTO patterns(code,search_pattern) VALUES('T','%thing');
CREATE TABLE products (id NUMBER,name VARCHAR2(20),found CHAR(1));
INSERT INTO products(id,name,found) VALUES(1,'Basic instinct',NULL);
INSERT INTO products(id,name,found) VALUES(2,'Basic thing',NULL);
INSERT INTO products(id,name,found) VALUES(3,'Super thing',NULL);
INSERT INTO products(id,name,found) VALUES(4,'Hyper instinct',NULL);
MERGE INTO products p USING
(
SELECT code,search_pattern FROM patterns
) s
ON (p.name LIKE s.search_pattern)
WHEN MATCHED THEN UPDATE SET p.found=s.code;
SELECT * FROM products;
Now first product name matches Basic% pattern and it will be updated with code B but second product matched both patterns and cannot be updated with both codes B and T in the same time (ambiguity)!
That's why DB engine complaints. Don't blame it! It knows what it is doing! ;-)

Resources