upsert into oracle - sql

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)

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

MERGE UPSERT DOES NOT WORK WHEN NOT MATCHED

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.

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.

How to determine what fields were update in an update trigger

UPDATE: Using Update_Columns() is not an answer to this question, as the fields may change in the order which will break the trigger (Update_Columns depends on the column order).
UPATE 2: I already know that the Deleted and Inserted tables hold the data. The question is how to determine what has changed without having to hard code the field names as the field names may change, or fields may be added.
Lets say I have a table with three fields.
The row already exists, and now the user updates fields 1 and 2.
How do I determine, in the Update Trigger, what the field were updated, and what the before and after values where?
I want to then log these to a log table. If there were two fields update, it should result in two rows in the history table.
Table
Id intField1 charField2 dateField3
7 3 Fred 1995-03-05
Updated To
7 3 Freddy 1995-05-06
History Table
_____________
Id IdOfRowThatWasUpdated BeforeValue AfterValue (as string)
1 7 Fred Freddy
2 7 1995-03-05 1995-05-06
I know I can use the Deleted table to Get the old values, and the inserted table to get the new values. The question however, is how to do this dynamically. In other words, the actual table has 50 columns, and I don't want to hard code 50 fields into a SQL statement, and also if the fields change, and don't want to have to worry about keeping the SQL in sync with table changes.
Greg
you can use one of my favorite XML-tricks to do this:
create trigger utr_Table1_update on Table1
after update, insert, delete
as
begin
with cte_inserted as (
select id, (select t.* for xml raw('row'), type) as data
from inserted as t
), cte_deleted as (
select id, (select t.* for xml raw('row'), type) as data
from deleted as t
), cte_i as (
select
c.ID,
t.c.value('local-name(.)', 'nvarchar(128)') as Name,
t.c.value('.', 'nvarchar(max)') as Value
from cte_inserted as c
outer apply c.Data.nodes('row/#*') as t(c)
), cte_d as (
select
c.ID,
t.c.value('local-name(.)', 'nvarchar(128)') as Name,
t.c.value('.', 'nvarchar(max)') as Value
from cte_deleted as c
outer apply c.Data.nodes('row/#*') as t(c)
)
insert into Table1_History (ID, Name, OldValue, NewValue)
select
isnull(i.ID, d.ID) as ID,
isnull(i.Name, d.Name) as Name,
d.Value,
i.Value
from cte_i as i
full outer join cte_d as d on d.ID = i.ID and d.Name = i.Name
where
not exists (select i.value intersect select d.value)
end;
sql fiddle demo
In this post:
How to refer to "New", "Old" row for Triggers in SQL server?
It is mentioned that/how you can access the original and the new values, and if you can access, you can compare them.
"INSERTED is the new row on INSERT/UPDATE. DELETED is the deleted row on DELETE and the updated row on UPDATE (i.e. the old values before the row was updated)"

Can I select the data of a given row and column while executing a sql statement

To clarify the title, in a select statement, in the where clause, I need to verify to table on which I am doing using another select. In that second select, I have to find all the secondary ID. Here is what I have worked out so far
Declare #id INT
--inserting values in temp table
SELECT
rn = ROW_NUMBER() OVER (ORDER BY adt_trl_dt_tm),
*
INTO #Temp
FROM dbo.EVNT_HSTRY
ORDER BY adt_trl_dt_tm DESC
--Searching for items that are deleted and have not been restored
SELECT *
FROM dbo.EVNT_HSTRY hstry
WHERE evnt_hstry_cd LIKE '3' and
adt_trl_dt_tm > (SELECT adt_trl_dt_tm FROM dbo.EVNT_HSTRY WHERE evnt_id = evnt_id
DROP TABLE #Temp
To clarify the code, evnt_id is a foreign key. The primary key is evnt_Hstry_id. The evnt_hstry_cd 3 means deleted. What I am trying to do is to see if the field adt_trl_dt_tm (lastest date modified) of the row being read is the latest by comparing it with all the adt_trl_dt_tm fields that have the same evnt_id.
The table I am doing the select on is the table where we store the history of the events. It is where we say when the event has been added, modified, deleted and or restored.
Sadly, I cannot do that into my application as this statement is being run in an SSIS.
Overall, I need to compare the adt_trl_dt_tm with the other adt_trl_dt_tm that have the same evnt_id and select the latest.
Can you test this with your data ?
SELECT *
FROM dbo.EVNT_HSTRY hstry
WHERE evnt_hstry_cd LIKE '3' and
not exists (select 1 from EVNT_HSTRY WHERE hstry.evnt_id = evnt_id
AND Hstry.adt_trl_dt_tm > adt_trl_dt_tm)
SELECT *
FROM dbo.EVNT_HSTRY hstry
WHERE evnt_hstry_cd = '3' and
adt_trl_dt_tm = (
SELECT max(adt_trl_dt_tm) FROM dbo.EVNT_HSTRY WHERE evnt_id = hstry.evnt_id
)
will result in a row read if the code 3 is the most recent entry in hstry and no row if there is a more recent row not having code 3
Change LIKE in = if it matches exactly