oracle "merge into" too slow - sql

i'm trying to update a column in a table using the id of another table only if one or two field match each other. Sadly the query run very slowly and i don't understand why.
PS:(the checked fields for table A may be null or have leading/trailing empty spaces )
MERGE INTO B B1
USING (
SELECT B2.LUSERINVENTORYID LUSERINVENTORYID, a1.lastid lastid
FROM B B2,
(SELECT lastid,
TRIM(UPPER(serialno)) AS serialno,
TRIM(UPPER(barcode)) AS barcode
FROM A) a1
WHERE (B2.loaded_serialno = a1.serialno AND B2.loaded_barcode = a1.barcode)
OR (B2.loaded_serialno = a1.serialno AND B2.loaded_barcode IS NULL)
OR (B2.loaded_serialno IS NULL AND B2.loaded_barcode = a1.barcode)
) res
ON (B1.luserinventoryid = res.luserinventoryid)
WHEN MATCHED THEN
UPDATE SET B1.lassetinvolvedid = res.lastid
please somebody can tell me how i can improve the execution time of this merge?

Without looking at your execution plan or knowing your data, we can only guess. That being said, at first glance I can tell you that you are almost certain to have problems stemming from those OR clauses in your join. If you can rewrite this to use a definite join column instead of all these conditions, you'll be much better off.
If you can't, you may also try the hint /*+ use_concat */ and Oracle might rewrite it as three UNION ALL sets with a single-column definite join in each one, which is basically rewriting it for you.
MERGE INTO b b1
USING (
SELECT /*+ use_concat */ b2.id id, a1.id lastid
FROM b b2,
(SELECT a1.id,
TRIM(UPPER(a1.serialno)) AS serialno,
TRIM(UPPER(a1.barcode)) AS barcode
FROM a) a1
WHERE (b2.loaded_serialno = a1.serialno AND b2.loaded_barcode = a1.barcode)
OR (b2.loaded_serialno = a1.serialno AND b2.loaded_barcode IS NULL)
OR (b2.loaded_serialno IS NULL AND b2.loaded_barcode = a1.barcode)
) res
ON (a1.luserinventoryid = res.luserinventoryid)
WHEN MATCHED THEN
UPDATE SET b1.lassetinvolvedid = res.lastid;

Without your data it is difficult to determine but you appear to perform a self-join on B in the ON clause of the merge and if there is a 1-to-1 correspondence (i.e. you are joining on a field with a UNIQUE key) then you could possibly skip that and merge A and B directly:
MERGE INTO B
USING (
SELECT lastid,
TRIM(UPPER(serialno)) AS serialno,
TRIM(UPPER(barcode)) AS barcode
FROM A
) A
ON ( (B.loaded_serialno = a.serialno AND B.loaded_barcode = a.barcode)
OR (B.loaded_serialno = a.serialno AND B.loaded_barcode IS NULL)
OR (B.loaded_serialno IS NULL AND B.loaded_barcode = a.barcode)
)
WHEN MATCHED THEN
UPDATE SET B.lassetinvolvedid = A.lastid;

Related

Concatenating two columns in Oracle giving performance issue

I have a select query where I am doing inner joins and in AND clause I am checking like this
AND UPPER (b.a1||b.a2) IN
(
select a.a1||a.a2
from a
where a.a3 =
(
select UPPER(decode('609',null,c.c1,'609'))
from dual
)
)
but so because of || opertaor it is taking more than 2 minutes. Can we have any other solution to this?
How about using EXISTS clause?
AND EXISTS (
SELECT
1
FROM
a
WHERE
a.a3 = (SELECT UPPER(DECODE('609',c.c1,'609')) FROM dual) -- this condition is pretty odd
AND a.a1 = UPPER(b.a1)
AND a.a2 = UPPER(b.a2)
)
Adding Function Based Indexes on UPPER(b.a1) AND UPPER(b.a2) may help as well.
Speaking of that odd condition: (SELECT UPPER(DECODE('609',c.c1,'609')) FROM dual):
Why do you perform a SELECT from dual there?
What you check is - if '609' equals c.c1 ('609') then a.a3 must equal '609', in any other case your SELECT returns NULL, thus no value from table a is returned. So you can just change the entire condition to a.a3 = '609'.
Try with a JOIN
SELECT *
FROM b
JOIN ( select UPPER(a.a1), UPPER(a.a2)
from a
where a.a3 = (select UPPER(decode('609',c.c1,'609')) from dual)
) a
ON UPPER(b.b1) = a.a1
AND UPPER(b.b2) = a.a2
But the problem is when you do UPPER(b.b1) or b1||b2 you cant use the index anymore.
You may need a function index
You don't need to concatenate. In fact: concatenating the values is a bug waiting to happen: consider the the values foob and ar in table b and foo and bar in a - the concatenation treats those as the same tuple although they are not.
You also don't need the select from dual for a constant:
AND (UPPER (b.a1), upper(b.a2)) IN
(
select a.a1. a.a2
from a
where a.a3 = UPPER(decode('609',null,c.c1,'609'))
)
An index on b(a1,a2) can't be used for this, but you can create an index on b (UPPER (b.a1), upper(b.a2)) which would be used.

update rows from joined tables in oracle

I'm trying to migrate some tables into an existing table, I need to perform the updates only where DET_ATTACHMENT_ID equals DET_ATTACHMENT.ID, here's the query I have so far.
UPDATE DET_ATTACHMENT
SET attachment_type = 'LAB', -- being added by the query, to replace the table difference
payer_criteria_id = (
SELECT PAYER_CRITERIA_ID
FROM DET_LAB_ATTACHMENT
WHERE DET_LAB_ATTACHMENT.DET_ATTACHMENT_ID = DET_ATTACHMENT.ID)
WHERE exists(
SELECT DET_ATTACHMENT_ID
FROM DET_ATTACHMENT
JOIN DET_LAB_ATTACHMENT ON (ID = DET_ATTACHMENT_ID)
WHERE DET_ATTACHMENT_ID = DET_ATTACHMENT.ID
the problem with the existing query is that it's setting every row to have an attachment_type of "LAB", and nulling out the payer_criteria_id where it didn't match. What am I doing wrong?
The problem might be that your exists(...) predicate always evaluates to true, thus making the update run for all rows of det_attachment. Try it this way:
UPDATE DET_ATTACHMENT X
SET X.attachment_type = 'LAB',
X.payer_criteria_id = (
SELECT C.PAYER_CRITERIA_ID
FROM DET_LAB_ATTACHMENT C
WHERE C.DET_ATTACHMENT_ID = X.ID
)
WHERE
exists(
SELECT 1
FROM DET_ATTACHMENT A
JOIN DET_LAB_ATTACHMENT B
ON B.DET_ATTACHMENT_ID = A.ID
where B.det_attachment_id = X.id
)
;

Update statement in SQL

I am writing an Update trigger and am struggling with the Update statement:
The statement is as below:
UPDATE ARGUS_APP.CMN_REG_REPORTS CARR
SET CARR.DATE_SUBMITTED =
(
SELECT To_Date(M.ACKNOWLEDGMENTHEADER.MESSAGEDATE,'YYYYMMDDHH24MISS') Messagedate
FROM esm_owner.MESSAGES M
WHERE M.ACKNOWLEDGMENTHEADER.MESSAGESENDERIDENTIFIER='PMDA'
)
WHERE CARR.DATE_SUBMITTED =
(
SELECT CARR.DATE_SUBMITTED
FROM esm_owner.safetyreport sr,esm_owner.MESSAGES M,ARGUS_APP.CMN_REG_REPORTS CARR
WHERE sr.report_id=CARR.esm_report_id
AND M.msg_id = sr.msg_id
AND M.ACKNOWLEDGMENTHEADER.MESSAGESENDERIDENTIFIER='PMDA'
)
I get ORA:01427 everytime.
The Table structure is as below:
I have 3 tables
ARGUS_APP.CMN_REG_REPORTS CARR .............having the columns DATE_SUBMITTED(which I want to update) and esm_report_id which joins with the report_id of safety report
ESM_OWNER.SAFETYREPORT SR............having the columns report_id and MSG_ID(joined with the msg_id of the MESSAGES table)
MESSAGES M ..........having the columns MSG_ID and ACKNOWLEDGMENTHEADER.MESSAGESENDERIDENTIFIER
Please help me resolve this.
I'm going to take a wild stab and guess that this is what you are after. They key feature is correlating the subselects with the update (the carr in the subselects refer to the table in the outer statement).
Update
argus_app.cmn_reg_reports carr
set
carr.date_submitted = (
Select
To_Date(m.AcknowledgmentHeader.MessageDate, 'YYYYMMDDHH24MISS') Messagedate
from
esm_owner.Messages m
inner join
esm_owner.SafetyReport sr
on m.msg_id = sr.msg_id
where
carr.esm_report_id = sr.report_id And
m.AcknowledgmentHeader.MessageSenderIdentifier = 'PMDA'
)
Where
Exists (
Select
'x'
From
esm_owner.Messages m
Inner Join
esm_owner.SafetyReport sr
on m.msg_id = sr.msg_id
Where
carr.esm_report_id = sr.report_id and
m.AcknowledgmentHeader.MessageSenderIdentifier = 'PMDA'
)
Here's an example showing the basic principle works:
Example Fiddle
It looks like one of your subqueries is probably returning more than one row of data. You could perhaps check this by running each on its own.
If you want the update to apply to them all, change the
... = (SELECT...
to
... IN (SELECT ...

SQLite inner join - update using values from another table

This is quite easy and has been asked multiple times but I can't get it to work.
The SQL query I think should work is:
UPDATE table2
SET dst.a = dst.a + src.a,
dst.b = dst.b + src.b,
dst.c = dst.c + src.c,
dst.d = dst.d + src.d,
dst.e = dst.e + src.e
FROM table2 AS dst
INNER JOIN table1 AS src
ON dst.f = src.f
Using the update statement it is not possible because in sqlite joins in an update statement are not supported. See docs:
update statement
If you only wanted to update a single column to a static value, you could use a subquery in the update statement correctly. See this example: How do I make an UPDATE while joining tables on SQLite?
Now in your example, making an assumption that there is a unique key on "column f" - a workaround/solution I have come up with is using the replace statement:
replace into table2
(a, b, c, d, e, f, g)
select src.a, src.b, src.c, src.d, src.e, dest.f, dest.g
from table1 src
inner join table2 dest on src.f = dest.f
I also added an extra column to table2 "column g" to show how you'd "update" only some of the columns with this method.
One other thing to be cautious about is if you use "PRAGMA foreign_keys = ON;" it's possible to have issues with this as the row is effectively deleted and inserted.
I came up with an alternative technique using a TRIGGER and "reversing" the direction of the update, albeit at the cost of a dummy field in the source table.
In general terms, you have a Master table and an Updates table. You want to update some/all fields of records in Master from the corresponding fields in Updates linked by a key field Key.
Instead of UPDATE Master SET ... FROM Master INNER JOIN Updates ON Mater.Key = Updates.Key you do the following:
Add a dummy field TriggerField to the Updates table to act as the focus of the trigger.
Create a trigger on this field:
CREATE TRIGGER UpdateTrigger AFTER UPDATE OF TriggerField ON Updates
BEGIN
UPDATE Master SET
Field1 = OLD.Field1,
Field2 = OLD.Field2,
...
WHERE Master.Key = OLD.Key
END;
Launch the update process with the following:
UPDATE Updates SET TriggerField = NULL ;
Notes
The dummy field is merely an anchor for the trigger so that any other UPDATE Updates SET ... won't trigger the update into Master. If you only ever INSERT into Updates then you don't need it (and can remove the OF TriggerField clause when creating the trigger).
From some rough-and-ready timings, this seems to work about the same speed as REPLACE INTO but avoids the feels-slightly-wrong technique of removing and adding rows. It is also simpler if you are only updating a few fields in Master as you only list the ones you want to change.
It is orders of magnitude faster than the other alternative I've seen to UPDATE ... FROM which is:
UPDATE Master SET
Field1 = ( SELECT Field1 FROM Updates WHERE Mater.Key = Updates.Key ),
Field1 = ( SELECT Field1 FROM Updates WHERE Mater.Key = Updates.Key ),
...
;
Updating six fields over 1700 records was roughly 0.05s for Tony and my methods but 2.50s for the UPDATE ... ( SELECT... ) method.
AFTER UPDATE triggers on Master seem to fire as expected.
As Tony says, the solution is the replace into way but you can use the sqlite hidden field rowid to simulate full update with join like:
replace into table2
(rowid,a, b, c, d, e, f, g)
select dest.rowid,src.a, src.b, src.c, src.d, src.e, dest.f, dest.g
from table1 src
inner join table2 dest on src.f = dest.f
With this you recreate full rows if you don't have primary key for the replace or as standard method to do the updates with joins.
SQLITE does not support UPDATE with INNER JOIN nor do several other DB's. Inner Joins are nice and simple however it can be accomplished using just a UPDATE and a subquery select. By using a where clause and the 'IN' with a subquery and a additional subquery for the 'SET' the same result can always be accomplished. Below is how it's done.
UPDATE table2
SET a = a + (select a from table1 where table1.f = table2.f),
b = b + (select b from table1 where table1.f = table2.f),
c = c + (select c from table1 where table1.f = table2.f),
d = d + (select d from table1 where table1.f = table2.f),
e = e + (select e from table1 where table1.f = table2.f)
WHERE RowId IN (Select table2.RowId from table1 where table1.f = table2.f)
Use below query:
UPDATE table2
SET a = Z.a,
b = Z.b,
c = Z.c,
d = Z.d,
e = Z.e
FROM (SELECT dst.id,
dst.a + src.a AS a,
dst.b + src.b AS b,
dst.c + src.c AS c,
dst.d + src.d AS d,
dst.e + src.e AS e
FROM table2 AS dst
INNER JOIN table1 AS src ON dst.f = src.f
)Z
WHERE table2.id = z.id

Updating A Column Base on Another Column

I'm wondering if it's possible to do something like the following:
UPDATE SomeTable st
SET MyColumn1 = (SELECT SomeValue
FROM WhereverTable),
MyColumn2 = ((SELECT AnotherValue
FROM AnotherTable)
*
MyColumn1);
WHERE MyColumn4 = 'condition'
I'm thinking that when I multiply AnotherValue with MyColumn1, it will still have the old value of MyColumn1 rather than the new one which is supposed to be SomeValue.
I'm using DB2 if that matters.
Count on the multiplication expression using the original value of MyColumn1, not the value specified in the update. If you want to use the new value for MyColumn1 in the multiplication formula, then specify the new expression there, too. Also, you should place a MIN, MAX, or FETCH FIRST ROW ONLY in the subqueries to prevent multiple rows from being returned.
Without more specifics than that, it is hard to give you a solid answer. But I'll take a stab at it:
UPDATE SomeTable
SET MyColumn1 = wt.SomeValue
MyColumn2 = at.AnotherValue
FROM SomeTable st
CROSS JOIN (
SELECT SomeValue FROM WhereverTable
) wt
CROSS JOIN (
SELECT AnotherValue FROM AnotherTable
) at
WHERE MyColumn4 = 'condition'
If they truly aren't related, then CROSS JOIN Is what you want. But beware that the subqueries (in this case, wt and at) that are cross-joined need to have only 1 record in them, or the JOINs will cause more than one record to be generated in the FROM clause. Not sure what it would do to this query, but it would probably make the resultset non-updateable.
Note that I'm using SQL Server's T-SQL syntax, as that is what I'm more familiar with. But a quick google found that DB2 does support cross joins (see here).
Try this (untested):
UPDATE SomeTable
SET MyColumn1 = (SELECT SomeValue
FROM WhereverTable),
MyColumn2 = MyColumn1 * (SELECT AnotherValue
FROM AnotherTable)
WHERE MyColumn4 = 'condition';
This Standard SQL-92 syntax requires scalar subqueries i.e. both WhereverTable and AnotherTable must each consist of zero or one row. It is more often the case that that rows need to be 'correlated' using identifiers (or conditions or similar) in the subqueries and do so in both the SET clause and the WHERE clause in the UPDATE statement e.g. (SQL-92, untested):
UPDATE SomeTable
SET MyColumn1 = (
SELECT wt.SomeValue
FROM WhereverTable AS wt
WHERE wt.some_table_ID = SomeTable.some_table_ID
),
MyColumn2 = MyColumn1 * (
SELECT av.AnotherValue
FROM AnotherTable AS av
WHERE wt.another_table_ID = SomeTable.another_table_ID
)
WHERE MyColumn4 = 'condition'
AND EXISTS (
SELECT *
FROM WhereverTable AS wt
WHERE wt.some_table_ID = SomeTable.some_table_ID
)
OR EXISTS (
SELECT *
FROM AnotherTable AS av
WHERE wt.another_table_ID = SomeTable.another_table_ID
);
This can be rewritten using SQL-99's MERGE statement given less 'repetitive' code.
Assuming that the tables always return one (and only one) row, the following should work just fine:
UPDATE SomeTable st SET (MyColumn1, MyColumn2) = (SELECT SomeValue,
AnotherValue * MyColumn1
FROM WhereverTable wt
CROSS JOIN AnotherTable at)
WHERE MyColumn4 = 'condition'
This will update MyColumn2 as desired (using the old value of MyColumn1).
Obviously if there are more/optional rows things get more complicated.