MERGE UPSERT DOES NOT WORK WHEN NOT MATCHED - sql

ABC_TABLE holds history data based on UPDATED_TS column.
Requirement is to load data from a CSV file and conditions is as below:
Fetch the latest EMPLOYEE_NAME based on UPDATED_TS (query inside USING condition)
In ON condition check, if the EMPLOYEE_NAME in CSV file does not match the EMPLOYEE_NAME fetched in the USING query, a new row should be inserted
If a new TABLE_ID is present in the CSV file and the TABLE_ID does not exist in ABC_TABLE, a new record should be inserted
When executing below query, no rows get inserted for a new TABLE_ID,
MERGE INTO ABC_TABLE T
USING (SELECT EMPLOYEE_NAME
FROM ABC_TABLE
WHERE TABLE_ID = ?
AND UPDATED_TS =
(SELECT MAX(UPDATED_TS) FROM ABC_TABLE WHERE TABLE_ID = ?)) S
ON ((S.EMPLOYEE_NAME IS NULL AND ? IS NULL) OR ? = S.EMPLOYEE_NAME )
WHEN NOT MATCHED THEN
INSERT
/*
insert statement here
*/
Any help would be greatly appreciated.

The merge documentation says:
Use the ON clause to specify the condition upon which the MERGE operation either updates or inserts. For each row in the target table for which the search condition is true, Oracle Database updates the row with corresponding data from the source table. If the condition is not true for any rows, then the database inserts into the target table based on the corresponding source table row.
For both matched and not-matched, there has to be a row in the source table (your S subquery in this case) for anything to happen. If the passed-in values don't exist then your subquery finds no rows, so source table is empty, and thus nothing happens.
You could add an aggregate function call in your subquery so it always finds something, and use that (e.g. a count of found records) to decide if it's matched; something like:
MERGE INTO ABC_TABLE T
USING (SELECT :table_id AS TABLE_ID, :employee_name AS EMPLOYEE_NAME, count(*) AS FOUND
FROM ABC_TABLE
WHERE TABLE_ID = :table_id
AND ((EMPLOYEE_NAME IS NULL AND :employee_name IS NULL)
OR EMPLOYEE_NAME = :employee_name)
AND UPDATED_TS =
(SELECT MAX(UPDATED_TS) FROM ABC_TABLE WHERE TABLE_ID = :table_id)) S
ON (S.FOUND > 0)
WHEN NOT MATCHED THEN
INSERT (table_id, updated_ts, employee_name)
VALUES (S.TABLE_ID, systimestamp, S.EMPLOYEE_NAME)
But as you're only inserting and never updating, why not just use an insert?
INSERT INTO ABC_TABLE T (table_id, updated_ts, employee_name)
SELECT :table_id, systimestamp, :employee_name
FROM DUAL
WHERE NOT EXISTS (
SELECT null
FROM ABC_TABLE T2
WHERE T2.TABLE_ID = :table_id
AND ((T2.EMPLOYEE_NAME IS NULL AND :employee_name IS NULL)
OR T2.EMPLOYEE_NAME = :employee_name)
AND UPDATED_TS =
(SELECT MAX(UPDATED_TS) FROM ABC_TABLE WHERE TABLE_ID = :table_id)
)
I'm not sure you actually want the UPDATED_TS check in either case though.

Related

SQL - trigger select into after update/insert

i have a table called Audit_Data, and from time to time there is an update coming. Every single update consists of around 300 rows (around 20 columns per row), and all of the rows from the same update share the same Audit_ID.
I have a select, that pulls out only this data, which is relevant for me. It basically transform the 300x20 field data into one row of data.
Is there a way to create a SQL trigger, that would perform the select on the updated Audit_Data table, and insert selected data into table named Audit_Final?
This is my select statement that i use to pull out the relevant data:
SELECT main.Audit_ID
,main.Item_19
,main.Item_1
,main.Item_7
,main.Item_8
,Item_17
,main.Item_13
,macaddr.Item_2
,macaddr.Item_16
,t1.Item_1
FROM dbo.[Audit_Data] AS main
LEFT JOIN
(
SELECT Audit_ID, Item_2, Item_16
FROM dbo.[Audit_Data] AS macaddr
WHERE
(Item_2 NOT LIKE 'Hyper-V%')
AND (Item_17 = 'connected')
AND (Item_18 IN ('10000Mbps', '1000MBps') OR ITEM_9 IS NOT NULL AND ITEM_10 IS NOT NULL)
AND (Item_18 != '100Mbps')
) macaddr ON main.Audit_ID = macaddr.Audit_ID
LEFT JOIN
(
SELECT Audit_ID, Category_ID, Item_1, Record_ordinal
FROM dbo.[Audit_Data] AS t1
WHERE
Item_1 = 'Automatyczna konfiguracja sieci przewodowej' OR Item_1 = 'dot3svc' OR Item_1 = 'Wired AutoConfig'
AND Item_3 = 'Running'
AND Category_ID = '4100'
) t1 ON main.Audit_ID = t1.Audit_ID
WHERE
main.Record_Ordinal = '2'
ORDER BY main.Audit_ID
Based on authors comment this is what is required here:
CREATE TRIGGER [TR_Audit_Data] ON [Audit_Data]
AFTER UPDATE
AS
BEGIN
INSERT INTO [Audit_Final](cloumn_1, colum_2, ... all columns you have on target table)
/*
Paste your select query here
*/
END

If condition in SQL file vertica

I need to execute insertions for around 10 tables, before inserting I have to check for a condition, condition remains the same for each of the tables, instead of giving that condition within insert query, I wish I could give in if condition (a select query), if satisfied then execute insert statements, is there a way to give if condition in Vertica SQL file ? If condition is not satisfied I dont want to execute any of the insert queries.
If the condition is, for example, that you only insert the data on a Sunday, try this:
a) a test table:
CREATE LOCAL TEMPORARY TABLE input(id,name)
ON COMMIT PRESERVE ROWS AS
SELECT 42,'Arthur Dent'
UNION ALL SELECT 43,'Ford Prefect'
UNION ALL SELECT 44,'Tricia McMillan'
KSAFE 0;
From this table, select with a WHERE condition that tests whether it's sunday -- that's all, see here:
SELECT
*
FROM input
WHERE TRIM(TO_CHAR(CURRENT_DATE,'Day'))='Sunday'
;
id|name
42|Arthur Dent
43|Ford Prefect
44|Tricia McMillan
With a different value for the week day (I'm writing this on a Sunday...), you get this:
SELECT
*
FROM input
WHERE TRIM(TO_CHAR(CURRENT_DATE,'Day'))='Monday'
;
id|name
select succeeded; 0 rows fetched
I use this technique in SQL generating SQL to create a script or an empty file determining on circumstances, and then to call that script (full or empty), implementing a conditional SQL execution that way....
I know it is an old post, but i want to share what i recently solved my problem.
I need to insert in one table or another based on some condition. You first should have a field or value what will be your search condition.
create table tmp1 (
Col1 int null
,Col2 varchar(100) null
)
--Insert values
insert into tmp (Col1,Col2) Values
(1,'Text1')
,(2,'Text2')
--Insert into table001
insert into table001
select
t.field1
,t.field2
,......
from table1 t
inner join tmp t2
on t2.col1 = t.ColX
where 1 = case when t2.Col2 = 'Text1' then 1 else 0 end --Search condition; if 1<>0 then it doesn't do anything; otherwise insert.
--Insert into table002
insert into table002
select
t.field1
,t.field2
,......
from table2 t
inner join tmp t2
on t2.col1 = t.ColX
where 1 = case when t2.Col2 = 'Text2' then 1 else 0 end --Search condition; if 1<>0 then it doesn't do anything; otherwise insert.
Or you can use an UNION/UNION ALL based on this if it is the same working table.
Regards!

Populating table from table of value

I've got a largish Oracle table (30M rows) which contains three columns: ID, fieldname, value. I need a query that will update the target table (which contains 93 columns) from the source data. So if the first row of the source table is 1,'first_name','Robert' then this will update the row where ID=1 updating first_name column with the value 'Robert'.
Is this even possible with a query or do I need to process it with another tool?
Hmmm. You can do this with a query. I would suggest building an index on the first table on id, fieldname, value and then running the following update 93 times:
update targettable tt
set field1 = (select max(value) from sourcetable st where st.id = tt.id and st.fieldname = 'field1')
where exists (select 1 from sourcetable st where st.id = tt.id and st.fieldname = 'field1');
You can actually write this all as one query, but it gets complicated to handle rows where only some fields are updated.

upsert into oracle

I have a table table with columns id, name, position, capture_date, modified_date, comments.
I am trying to do a simple upsert which is driving me crazy.
When the table is empty, it has to insert, but when its not empty it has to update the comments column row which has the same position, if its different it has to insert a new row instead of updating the existing one.
When the table is empty, i used this merge statement to create the first row.
This works fine.
But, second row has to be
1, john, 2, 01-JUL-15, 23-JUL-15, 'world'
In this case, the data is almost same except that the position value is 2, so a new row has to be inserted instead of updating the existing row's position to 2.
That is what my merge statement is doing. Any ideas to work on this please.
merge into customers a
using(select 1 as customer_id, 'john' as customer_name, '1' as position, '01-JUL-15' as capture_date,
sysdate as modified_date, 'hello' as comments from dual) b
on(a.customer_id=b.customer_id)
when matched then
update set a.customer_id = b.customer_id, a.customer_name = b.customer_name,
a.position = b.position, a.capture_date= b.capture_date, a.modified_date = b.modified_date,
a.comments=b.comments
when not matched then
insert(a.customer_id, a.customer_name, a.position, a.capture_date, a.modified_date, a.comments)
values(b.customer_id, b.customer_name, b.position, b.capture_date, b.modified_date, b.comments)
I have created the sqlfiddle
So lessons learned:
1 post the original query not some faulty surrogate.
2 post any error message you get.
The error message you get is:
ORA-38104: Columns referenced in the ON Clause cannot be updated: "A"."CUSTOMER_ID"
Solution: remove a.customer_id from the update clause.
merge into customers a
using (select 1 as customer_id
,'john' as customer_name
,'1' as position
,'01-JUL-15' as capture_date
,sysdate as modified_date
,'hello' as comments
from dual) b
on (a.customer_id = b.customer_id)
when matched then
update
set a.customer_name = b.customer_name
,a.position = b.position
,a.capture_date = b.capture_date
,a.modified_date = b.modified_date
,a.comments = b.comments
when not matched then
insert
(a.customer_id
,a.customer_name
,a.position
,a.capture_date
,a.modified_date
,a.comments)
values
(b.customer_id
,b.customer_name
,b.position
,b.capture_date
,b.modified_date
,b.comments)

Tricky MS Access SQL query to remove surplus duplicate records

I have an Access table of the form (I'm simplifying it a bit)
ID AutoNumber Primary Key
SchemeName Text (50)
SchemeNumber Text (15)
This contains some data eg...
ID SchemeName SchemeNumber
--------------------------------------------------------------------
714 Malcolm ABC123
80 Malcolm ABC123
96 Malcolms Scheme ABC123
101 Malcolms Scheme ABC123
98 Malcolms Scheme DEF888
654 Another Scheme BAR876
543 Whatever Scheme KJL111
etc...
Now. I want to remove duplicate names under the same SchemeNumber. But I want to leave the record which has the longest SchemeName for that scheme number. If there are duplicate records with the same longest length then I just want to leave only one, say, the lowest ID (but any one will do really). From the above example I would want to delete IDs 714, 80 and 101 (to leave only 96).
I thought this would be relatively easy to achieve but it's turning into a bit of a nightmare! Thanks for any suggestions. I know I could loop it programatically but I'd rather have a single DELETE query.
See if this query returns the rows you want to keep:
SELECT r.SchemeNumber, r.SchemeName, Min(r.ID) AS MinOfID
FROM
(SELECT
SchemeNumber,
SchemeName,
Len(SchemeName) AS name_length,
ID
FROM tblSchemes
) AS r
INNER JOIN
(SELECT
SchemeNumber,
Max(Len(SchemeName)) AS name_length
FROM tblSchemes
GROUP BY SchemeNumber
) AS w
ON
(r.SchemeNumber = w.SchemeNumber)
AND (r.name_length = w.name_length)
GROUP BY r.SchemeNumber, r.SchemeName
ORDER BY r.SchemeName;
If so, save it as qrySchemes2Keep. Then create a DELETE query to discard rows from tblSchemes whose ID value is not found in qrySchemes2Keep.
DELETE
FROM tblSchemes AS s
WHERE Not Exists (SELECT * FROM qrySchemes2Keep WHERE MinOfID = s.ID);
Just beware, if you later use Access' query designer to make changes to that DELETE query, it may "helpfully" convert the SQL to something like this:
DELETE s.*, Exists (SELECT * FROM qrySchemes2Keep WHERE MinOfID = s.ID)
FROM tblSchemes AS s
WHERE (((Exists (SELECT * FROM qrySchemes2Keep WHERE MinOfID = s.ID))=False));
DELETE FROM Table t1
WHERE EXISTS (SELECT 1 from Table t2
WHERE t1.SchemeNumber = t2.SchemeNumber
AND Length(t2.SchemeName) > Length(t1.SchemeName)
)
Depend on your RDBMS you may use function different from Length (Oracle - length, mysql - length, sql server - LEN)
delete ShortScheme
from Scheme ShortScheme
join Scheme LongScheme
on ShortScheme.SchemeNumber = LongScheme.SchemeNumber
and (len(ShortScheme.SchemeName) < len(LongScheme.SchemeName) or (len(ShortScheme.SchemeName) = len(LongScheme.SchemeName) and ShortScheme.ID > LongScheme.ID))
(SQL Server flavored)
Now updated to include the specified tie resolution. Although, you may get better performance doing it in two queries: first deleting the schemes with shorter names as in my original query and then going back and deleting the higher ID where there was a tie in name length.
I'd do this in multiple steps. Large delete operations done in a single step make me too nervous -- what if you make a mistake? There's no sql 'undo' statement.
-- Setup the data
DROP Table foo;
DROP Table bar;
DROP Table bat;
DROP Table baz;
CREATE TABLE foo (
id int(11) NOT NULL,
SchemeName varchar(50),
SchemeNumber varchar(15),
PRIMARY KEY (id)
);
insert into foo values (714, 'Malcolm', 'ABC123' );
insert into foo values (80, 'Malcolm', 'ABC123' );
insert into foo values (96, 'Malcolms Scheme', 'ABC123' );
insert into foo values (101, 'Malcolms Scheme', 'ABC123' );
insert into foo values (98, 'Malcolms Scheme', 'DEF888' );
insert into foo values (654, 'Another Scheme ', 'BAR876' );
insert into foo values (543, 'Whatever Scheme ', 'KJL111' );
-- Find all the records that have dups, find the longest one
create table bar as
select max(length(SchemeName)) as max_length, SchemeNumber
from foo
group by SchemeNumber
having count(*) > 1;
-- Find the one we want to keep
create table bat as
select min(a.id) as id, a.SchemeNumber
from foo a join bar b on a.SchemeNumber = b.SchemeNumber
and length(a.SchemeName) = b.max_length
group by SchemeNumber;
-- Select into this table all the rows to delete
create table baz as
select a.id from foo a join bat b where a.SchemeNumber = b.SchemeNumber
and a.id != b.id;
This will give you a new table with only records for rows that you want to remove.
Now check these out and make sure that they contain only the rows you want deleted. This way you can make sure that when you do the delete, you know exactly what to expect. It should also be pretty fast.
Then when you're ready, use this command to delete the rows using this command.
delete from foo where id in (select id from baz);
This seems like more work because of the different tables, but it's safer probably just as fast as the other ways. Plus you can stop at any step and make sure the data is what you want before you do any actual deletes.
If your platform supports ranking functions and common table expressions:
with cte as (
select row_number()
over (partition by SchemeNumber order by len(SchemeName) desc) as rn
from Table)
delete from cte where rn > 1;
try this:
Select * From Table t
Where Len(SchemeName) <
(Select Max(Len(Schemename))
From Table
Where SchemeNumber = t.SchemeNumber )
And Id >
(Select Min (Id)
From Table
Where SchemeNumber = t.SchemeNumber
And SchemeName = t.SchemeName)
or this:,...
Select * From Table t
Where Id >
(Select Min(Id) From Table
Where SchemeNumber = t.SchemeNumber
And Len(SchemeName) <
(Select Max(Len(Schemename))
From Table
Where SchemeNumber = t.SchemeNumber))
if either of these selects the records that should be deleted, just change it to a delete
Delete
From Table t
Where Len(SchemeName) <
(Select Max(Len(Schemename))
From Table
Where SchemeNumber = t.SchemeNumber )
And Id >
(Select Min (Id)
From Table
Where SchemeNumber = t.SchemeNumber
And SchemeName = t.SchemeName)
or using the second construction:
Delete From Table t Where Id >
(Select Min(Id) From Table
Where SchemeNumber = t.SchemeNumber
And Len(SchemeName) <
(Select Max(Len(Schemename))
From Table
Where SchemeNumber = t.SchemeNumber))