How to get data from joined tables when performing a merge? - sql

I am trying to use a merge to a table.
What I am having trouble with is getting the matching name from the code that exists in the original table. I will put my code and explain further:
MERGE INTO ResultTable R
USING InitialTable IT
ON (false)
WHEN MATCHED THEN -- do some stuff
WHEN NOT MATCHED THEN
INSERT (PrimaryKey,..., ThingFromJoinedTable)
VALUES (Seq.NEXTVAL, ..., ??? );
So the Initial table has a foreign key and I want to get the matching value in the Joined table.
Anyone have any idea on how to do so, I have tried having a nested select with a join, but it gives me a single-row subquery returns more than one row error.

Something like this:
MERGE INTO ResultTable R
USING ( SELECT it.this, it.that, third.this, third.that
FROM InitialTable it
JOIN ThirdTable third ON <your join criteria> ) SRC
/* depending on which columns you want for the join */
ON (r.col1 = src.col1 and r.col2 = src.col2)
WHEN MATCHED THEN -- do some stuff
/* depending on which columns you need to merge */
UPDATE SET
r.col4 = src.col4,
r.col5 = src.col5,
etc.
WHEN NOT MATCHED THEN
INSERT (PrimaryKey,..., colThis, colThat, ....)
VALUES (Seq.NEXTVAL, ..., src.colThis, src.colThat );

Related

MERGE statement to update or insert rows into a table

My task is to insert or update rows in a table2. Table1 contains id's of all employees. That id matches the ID in the table2. Some of the employees in table2 already have the rows I need but some don't. Table2 doesn't contain the ID's of the employees that don't have those rows.
My task is to update the rows for the existing ID's and insert for the ones that don't have those rows.
I have tried the following statement:
MERGE INTO dbo.table2 AS TGT
USING (SELECT table1ID FROM dbo.table1) AS SRC
ON SRC.table1ID = TGT.table2ID
WHEN MATCHED
AND table2Code = 'ValueToInsertOrUpdateCode'
THEN
UPDATE
SET table2Value= 'ValueToInsertOrUpdateValue'
WHEN NOT MATCHED BY TARGET
THEN
INSERT (table2Code, table2ID, table2Value)
VALUES ('ValueToInsertOrUpdateCode', src.table1ID, 'ValueToInsertOrUpdateValue');
This currently only updates the rows that exist, but doesn't insert the rows for ID's that don't have existing rows.
Based on your comments is sounds like you want this so that the WHEN NOT MATCHED BY TARGET is executed:
MERGE INTO dbo.table2 AS TGT
USING (SELECT table1ID FROM dbo.table1) AS SRC
ON (SRC.table1ID = TGT.table2ID AND table2Code = 'ValueToInsertOrUpdateCode') -- This is the difference
WHEN MATCHED
AND table2Code = 'ValueToInsertOrUpdateCode'
THEN
UPDATE
SET table2Value= 'ValueToInsertOrUpdateValue'
WHEN NOT MATCHED BY TARGET
THEN
INSERT (table2Code, table2ID, table2Value)
VALUES ('ValueToInsertOrUpdateCode', src.table1ID, 'ValueToInsertOrUpdateValue');
WHEN NOT MATCHED BY TARGET would not execute when SRC.table1ID = TGT.table2ID (i.e. they match).
Updating the ON clause to ON (SRC.table1ID = TGT.table2ID AND table2Code = 'ValueToInsertOrUpdateCode') will give you the inserts you are expecting.
However you should probably not do this:
ON <merge_search_condition> Caution
It's important to specify only the columns from the target table to use for matching purposes. That is, specify columns from the target table that are compared to the corresponding column of the source table. Don't attempt to improve query performance by filtering out rows in the target table in the ON clause; for example, such as specifying AND NOT target_table.column_x = value. Doing so may return unexpected and incorrect results.
For this reason and what others have suggested it would be safer to do separate update and insert statements.
I would, honestly, suggest avoiding the MERGE operator and doing an Upsert here instead. For your scenario, what you need is most likely the following:
SET XACT_ABORT ON;
BEGIN TRANSACTION;
UPDATE T2 WITH (UPDLOCK, SERIALIZABLE)
SET table2Value = 'ValueToInsertOrUpdateValue'
FROM dbo.Table2 T2
JOIN dbo.Table1 T1 ON T1.table1ID = T2.table2ID;
-- You could honestly use an EXISTS here, considering that you're updating the table
-- with a literal, rather than a value from the table Table1.
INSERT INTO dbo.Table2 (table2Code , table2ID, table2Value)
SELECT 'ValueToInsertOrUpdateCode',
T1.table1ID,
'ValueToInsertOrUpdateValue'
FROM dbo.Table1 T1
WHERE NOT EXISTS (SELECT 1
FROM dbo.Table2 T2
WHERE T2.table2ID = T1.table1ID);
COMMIT;
db<>fiddle

SQL MERGE: Is it possible to ignore a MATCH?

Here's my merge statement:
MERGE PE_TranslationPhrase T
USING PE_TranslationPhrase_Staging S
ON (T.CultureName = S.CultureName AND T.Phrase = S.Phrase)
WHEN MATCHED
THEN UPDATE SET T.TranslationId = T.TranslationId -- do nothing
WHEN NOT MATCHED BY TARGET
THEN INSERT (TranslationId, CultureName, Phrase)
VALUES (S.TranslationId, S.CultureName, S.Phrase);
I seem to be having trouble with the WHEN MATCHED section. Based on this question and answer, I changed the WHEN MATCHED to:
THEN UPDATE SET T.TranslationId = T.TranslationId
But I am still getting this error:
The MERGE statement attempted to UPDATE or DELETE the same row more than once. This happens when a target row matches more than one source row. A MERGE statement cannot UPDATE/DELETE the same row of the target table multiple times. Refine the ON clause to ensure a target row matches at most one source row, or use the GROUP BY clause to group the source rows.
I get the reason why. Based on the ON statement, I am getting duplicate matches. But... I don't care. If a record from the source table already exists in the target table, then I want to just do nothing. Don't update anything at all. Skip it.
Is that possible?
Oh. Turns out you don't need the 'WHEN MATCHED' statement. I thought it was required.
MERGE PE_TranslationPhrase T
USING PE_TranslationPhrase_Staging S
ON (T.CultureName = S.CultureName AND T.Phrase = S.Phrase)
WHEN NOT MATCHED BY TARGET
THEN INSERT (TranslationId, CultureName, Phrase)
VALUES (S.TranslationId, S.CultureName, S.Phrase);
If no update is being done, have you considered just doing the insert using rows from PE_TranslationPhrase_Staging that aren't in PE_TranslationPhrase?
CTE Approach:
WITH CTE AS (
SELECT
S.TranslationId,
S.CultureName,
S.Phrase
FROM PE_TranslationPhrase_Staging S
LEFT JOIN PE_TranslationPhrase T on S.Phrase = T.Phrase and S.CultureName = T.CultureName
WHERE T.PHRASE IS NULL
)
INSERT INTO PE_TranslationPhrase (TranslationId, CultureName, Phrase)
SELECT
TranslationId,
CultureName,
Phrase
FROM CTE
Subquery Approach:
INSERT INTO PE_TranslationPhrase (TranslationId, CultureName, Phrase)
SELECT
TW.TranslationId,
TW.CultureName,
TW.Phrase
FROM (
SELECT
S.TranslationId,
S.CultureName,
S.Phrase
FROM PE_TranslationPhrase_Staging S
LEFT JOIN PE_TranslationPhrase T on S.Phrase = T.Phrase and S.CultureName = T.CultureName
WHERE T.PHRASE IS NULL ) TW
Unfortunately, what can not be done in the MERGE statement to omit this error. But I have solved this by generating an identity column (NOT persistent) that allows me to add a unique value to each record for both the source and target of the sentence and compare the form. Something like this:
ROW_NUMBER () OVER (PARTITION BY CultureName, Phrase ORDER BY CultureName, Phrase)
No, you cannot use Merge ... When Matched ... in such cases that there are multiple matches and as you could find out, you can simply remove the When Matched section. However if you needed this section for update, instead of using Merge you can use the following code:
update PE_TranslationPhrase
set TranslationId = S.xxx
from
PE_TranslationPhrase T, PE_TranslationPhrase_Staging S
where T.CultureName = S.CultureName AND T.Phrase = S.Phrase

Oracle SQL, trying to get one value from a select/join to use to update one column in one table?

I have one table with the following columns:
T_RESOLVED_DATE
I_HOUSEHOLD_NUMBER
I_RESOLVED_SET_NUMBER
I_STATION_CODE
I_RESOLVED_START_MIN
I_DURATION
I_PERSON_NUMBER
I_COVIEW_DEMO_ID
Initially, I_COVIEW_DEMO_ID is set to null.
Then I have another table with the following columns:
T_RESOLVED_DATE
I_HOUSEHOLD_NUMBER
I_PERSON_NUMBER
I_AGE
T_GENDER
I_COVIEW_DEMO_ID
I am trying to update I_COVIEW_DEMO_ID in the first table by using the value of I_COVIEW_DEMO_ID in the second table where the T_RESOLVED_DATE, I_HOUSEHOLD_NUMBER, and I_PERSON_NUMBER are equal in both tables. The first table may contain multiple rows with the same DATE, HOUSEHOLD_NUMBER, and PERSON_NUMBER, because the rows can vary by the rest of the columns.
I have tried to do a select and a group by which seems to get me part way there, but I am getting a "single-row subquery returns more than one row" error when I try to update the columns in the first table. This is what I've tried, along with variations of it:
UPDATE
Table1
SET
I_COVIEW_DEMO_ID =
(SELECT
b.I_COVIEW_DEMO_ID
FROM Table1 a,
Table2 b
WHERE a.I_HOUSEHOLD_NUMBER = b.I_HOUSEHOLD_NUMBER AND
a.I_PERSON_NUMBER = b.I_PERSON_NUMBER AND
a.T_RESOLVED_DATE = b.T_RESOLVED_DATE
GROUP BY b.I_COVIEW_DEMO_ID);
Any suggestions?
I was able to get it to work using this statement:
MERGE INTO table1 a
USING
(
SELECT DISTINCT
T_RESOLVED_DATE,
I_HOUSEHOLD_NUMBER,
I_PERSON_NUMBER,
I_COVIEW_DEMO_ID
FROM
table2
) b
ON
(
a.T_RESOLVED_DATE = b.T_RESOLVED_DATE
AND a.I_HOUSEHOLD_NUMBER = b.I_HOUSEHOLD_NUMBER
AND a.I_PERSON_NUMBER = b.I_PERSON_NUMBER
) WHEN MATCHED THEN
UPDATE SET
a.I_COVIEW_DEMO_ID = b.I_COVIEW_DEMO_ID;
As per our discussion on the comments this would be a simple PLSQL block to do what you need. I'm doing direct from my head without test, so you may need to fix some sintaxe mistake.
BEGIN
FOR rs IN ( SELECT I_HOUSEHOLD_NUMBER,
I_PERSON_NUMBER,
I_COVIEW_DEMO_ID,
T_RESOLVED_DATE
FROM Table2 ) LOOP
UPDATE Table1
SET I_COVIEW_DEMO_ID = rs.I_COVIEW_DEMO_ID
WHERE I_PERSON_NUMBER = rs.I_PERSON_NUMBER
AND I_HOUSEHOLD_NUMBER = rs.I_HOUSEHOLD_NUMBER
AND T_RESOLVED_DATE = rs.T_RESOLVED_DATE;
END LOOP;
--commit after all updates, if there is many rows you should consider in
--making commits by blocks. Define a count and increment it whithin the for
--after some number of updates you commit and restart the counter
COMMIT;
END;

SQL With... Update

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.

Prevent the insertion of duplicate rows using SQL Server 2008

I am trying to insert some data from one table into another but I would like to prevent the insertion of duplicate rows. I have currently the following query:
INSERT INTO Table1
(
Table1Col1,
Table1Col2,
Table1Col3,
Table1Col4,
Table1Col5
)
SELECT
Table2Col1,
Table2Col2 = constant1,
Table2Col3 = constant2,
Table2Col4 = constant3,
Table2Col5 = constant4
FROM Table2
WHERE
Condition1 = constant5
AND
Condition2 = constant6
AND
Condition3 = constant7
AND
Condition4 LIKE '%constant8%'
What I do not know is that the row I am trying to insert from Table2 into Table1 might already exist and I would like to prevent this possible duplication from happening and skip the insertion and just move onto inserting the next unique row.
I have seen that I can use a WHERE NOT EXISTS clause and use of the INTERSECT keyword but I did not fully understand how to apply it to my particular query as I only want to use some of the selected data from Table2 and then some constant values to insert into Table1.
EDIT:
I should add that the columns TableCol2 through to TableCol5 don't actually exist in the result set and I am just populating these columns alongside Table2Col1 that is returned.
Since you are on SQL Server 2008, you can use a merge statement.
You can easily check if a row exists base on a key
something like this:
merge TableMain AS target
using TableA as source
ON <join tables here>
WHEN MATCHED THEN <update>
WHEN NOT MATCHED BY TARGET <Insert>
WHEN NOT MATCHED BY SOURCE <delete>
Intersect (minus in Sql Server's terms) is out of question because it compares whole row. Other two options are not in/not exists/left join and merge. Not In is for single-column prinary key only, so it is out of question in this instance. In/Exists/Left join should have the same performance in Sql Server, so I'll just use exists:
INSERT INTO Table1
(
Table1Col1,
Table1Col2,
Table1Col3,
Table1Col4,
Table1Col5
)
SELECT
Table2Col1,
Table2Col2 = constant1,
Table2Col3 = constant2,
Table2Col4 = constant3,
Table2Col5 = constant4
FROM Table2
WHERE
Condition1 = constant5
AND
Condition2 = constant6
AND
Condition3 = constant7
AND
Condition4 LIKE '%constant8%'
AND NOT EXISTS
(
SELECT *
FROM Table1 target
WHERE target.Table1Col1 = Table2.Table2Col1
AND target.Table1Col2 = Table2.Table2Col2
AND target.Table1Col3 = Table2.Table2Col3
)
Merge is used to sync two tables; it has ability to insert, update and delete records from target table.
merge into table1 as target
using table2 as source
on target.Table1Col1 = source.Table2Col1
AND target.Table1Col2 = source.Table2Col2
AND target.Table1Col3 = source.Table2Col3
when not matched by target then
insert (Table1Col1,
Table1Col2,
Table1Col3,
Table1Col4,
Table1Col5)
values (Table2Col1,
Table2Col2,
Table2Col3,
Table2Col4,
Table2Col5);
If columns from table2 are computed during transfer, in not exists() case you might use derived table in place of table2, and the same applies to merge example - just place your query in place of reference to table2.
we have check the whether the data is already exist or not in table. For this we have to use If condition to avoid the duplicate insertion