I have temporary table that contains multiple rows and original table with below structure :
temp : desc,id
Original :name,id,rate
I have to check if id of one row matches the original one then i have to update the rate in Original else i have to insert row in original and this all should be happening in a loop as we have multiple records in temporary table.
I am working in SQL SERVER 2008
You don't need a loop; you can do this with an update followed by an insert:
UPDATE Original
SET rate = /* whatever the new rate is */
FROM Original
INNER JOIN temp on Original.id = temp.id
INSERT INTO Original (name, id, rate)
SELECT desc, id, /* whatever the rate is */ FROM temp
WHERE id NOT IN (SELECT id FROM Original)
Looks like MERGE can be a good option for you :
-- MERGE statement with the join conditions specified correctly.
USE tempdb;
GO
BEGIN TRAN;
MERGE Target AS T
USING Source AS S
ON (T.EmployeeID = S.EmployeeID)
WHEN NOT MATCHED BY TARGET AND S.EmployeeName LIKE 'S%'
THEN INSERT(EmployeeID, EmployeeName) VALUES(S.EmployeeID, S.EmployeeName)
WHEN MATCHED
THEN UPDATE SET T.EmployeeName = S.EmployeeName
WHEN NOT MATCHED BY SOURCE AND T.EmployeeName LIKE 'S%'
THEN DELETE
OUTPUT $action, inserted.*, deleted.*;
ROLLBACK TRAN;
GO
Straight pick from below link
https://technet.microsoft.com/en-us/library/bb522522(v=sql.105).aspx
Related
I have data flowing into one table from multiple other tables lets say: Table_A
Then I have a Merge stored proc that takes data from table A merges it with Table B.
However, something doesn't seem to be right. If i truncate and load the data it works fine, but if i dont truncate and load, and just fetch the query by eachh hour I get the error message saying
Msg 8672, Level 16, State 1, Procedure Merge_Table_A, Line 4 [Batch Start Line 0]
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.
How can I overcome this?
I want to be able to incrementally load the data and not do truncate loads, but at the same time have a stored proc that updates or inserts or doesnt care if the row already exists.
Seems you have duplicate rows in your target table which are loaded from your previous runs.
Note: Matching in a Merge does not consider the rows inserted (even duplicate) while running the Merge itself.
Below is my repro example with a sample data:
Table1: Initial data
Table2: Taget table
Merge Statement:
MERGE tb2 AS Target
USING tb1 AS Source
ON Source.firstname = Target.firstname and
Source.lastname = Target.lastname
-- For Inserts
WHEN NOT MATCHED BY Target THEN
INSERT (firstname, lastname, updated_date)
VALUES (Source.firstname, Source.lastname, source.updated_date)
-- For Updates
WHEN MATCHED THEN UPDATE SET
Target.updated_date = Source.updated_date
-- For Deletes
WHEN NOT MATCHED BY Source THEN
DELETE;
When Merge is executed, it inserts all data without any errors.
New data in tb1:
When I run the Merge statement, it gives me the same error as yours.
As a workaround using one of the below options,
Add additional conditions if possible in the ON clause to uniquely identify the data.
Remove the duplicates from the source and merge the data into tb2 as below.
--temp table
drop table if exists #tb1;
select * into #tb1 from (
select *, row_number() over(partition by firstname, lastname order by firstname, lastname, updated_date desc) as rn from tb1) a
where rn = 1
MERGE tb2 AS Target
USING #tb1 AS Source
ON Source.firstname = Target.firstname and
Source.lastname = Target.lastname
-- For Inserts
WHEN NOT MATCHED BY Target THEN
INSERT (firstname, lastname, updated_date)
VALUES (Source.firstname, Source.lastname, source.updated_date)
-- For Updates
WHEN MATCHED THEN UPDATE SET
Target.updated_date = Source.updated_date
-- For Deletes
WHEN NOT MATCHED BY Source THEN
DELETE;
Data merged into tb2 successfully.
I have two tables table_1 and table_2. After inserting some data into table_1(insert not in the example below), it gives Auto_Increment ID to table_1. Then I need to put this new generated ID into table_2 in NOT MATCHED section.
I am trying to use T-SQL's MERGE, to UPDATE table if data already exists (if matched) or INSERT INTO table if there is no such data(not matched), but in second case insert by using one column selected from another table.
Here is what I have already tried:
MERGE
INTO table_2 WITH (HOLDLOCK) AS target
USING (SELECT
'42' AS person_id
,2 AS skill_id
) AS source
(person_id,skill_id )
ON (target.person_id = source.person_id
AND target.skill_id = source.skill_id)
WHEN MATCHED
THEN UPDATE
SET skill_lvl=4,already_have=0
WHEN NOT MATCHED
--section below doesn't work,because insert inside MERGE has to be without select (?)
THEN INSERT (person_id, skill_id, skill_lvl,already_have)
SELECT 42, id,3,1 FROM table_1;
Not matched section gives me an error that he waits values or default, but it seems kind of tricky to select with values or default.
Edit_1
Insert query to table_1 (happens before previous MERGE. Both MERGES within one loop):
MERGE
INTO table_1 WITH (HOLDLOCK) AS target
USING (SELECT
'skill_1' AS skill_name
) AS source
(skill_name)
ON (target.skill_name = source.skill_name)
WHEN NOT MATCHED
THEN INSERT (category_id,skill_name) values (0,'skill_1');
this query in the loop, compares skill_names, if name is not inside this table_1, it inserts this value. Then compare next skill_name and so on. ID's are generating automatically after inserting.
I'm now using the sqldiff tool to calculate the difference between my database before a transaction was executed and after it was executed. I'm doing this as follows:
Copy the entire sqlite file to a tmp location (old-database.sqlite)
Perform the transaction (for ex. INSERT, SCHEMA CHANGE, ...)
Than sqldiff --primary-key old-database.sqlite database.sqlite
This outputs the changes that the transaction has done. Although this works perfectly because my database isn't that large, a few megabytes maximum. I think this could be done more efficiently.
Is there any mechanism that I can use to get the same output without copying the database. Maybe something with the journal?
Kind regards,
Daan
Perhaps a little awkward to implement and perhaps at some stage (size wise) inefficient, but you could use a combination of TRIGGERS (4 per monitored table is the awkward part) and a before after snapshot of sqlite_master.
That is you could, per table to be monitored, have a copy of that table (structure wise) appended with two columns. 1 to indicate the action (inserted, before update, after update or deletion), the 2nd (optional) to record the timestamp.
Then for each table to be monitored have AFTER INSERT, BEFORE DELETE and AFTER and BEFORE UPDATE TRIGGERS to copy the add an entry to the table's log table, which would be emptied before the transaction (if wanted, if not then timestamp would probably be a required column).
For schema changes again you could have a copy of sqlite_master before and then compare it against sqlite_master after the transaction (you can't have TRIGGERS applied to system tables).
Example, consider the following example/demo:-
CREATE TABLE IF NOT EXISTS mytable (id INTEGER PRIMARY KEY, mycolumn TEXT);
CREATE TABLE IF NOT EXISTS mytablelog AS SELECT * FROM mytable WHERE 0 = 1;
ALTER TABLE mytablelog ADD COLUMN logtype INTEGER;
ALTER TABLE mytablelog ADD COLUMN timestamp INTEGER TEXT;
CREATE TABLE IF NOT EXISTS schema_log AS SELECT * FROM sqlite_master WHERE 0=1;
CREATE TABLE IF NOT EXISTS pretrans_schema AS SELECT * FROM sqlite_master WHERE 0=1;
ALTER TABLE schema_log ADD COLUMN logtype INTEGER;
ALTER TABLE schema_log ADD COLUMN timestamp INTEGER TEXT;
CREATE TRIGGER IF NOT EXISTS mytable_inserts AFTER INSERT ON mytable
BEGIN
INSERT INTO mytablelog SELECT *,0,strftime('%s','now') FROM mytable WHERE id = new.id;
END
;
CREATE TRIGGER IF NOT EXISTS mytable_deletions BEFORE DELETE ON mytable
BEGIN
INSERT INTO mytablelog SELECT *,1,strftime('%s','now') FROM mytable WHERE id = old.id;
END
;
CREATE TRIGGER IF NOT EXISTS mytable_preupdates BEFORE UPDATE ON mytable
BEGIN
INSERT INTO mytablelog SELECT *,2,strftime('%s','now') FROM mytable WHERE id = old.id;
END
;
CREATE TRIGGER IF NOT EXISTS mytable_postupdates AFTER UPDATE ON mytable
BEGIN
INSERT INTO mytablelog SELECT *,3,strftime('%s','now') FROM mytable WHERE id = new.id;
END
;
-- SELECT * FROM sqlite_master WHERE name LIKE 'sqlite_%';
/* BEFORE TRANSACTION PREPATION */
DELETE FROM mytablelog;
DELETE FROM schema_log;
DELETE FROM pretrans_schema;
INSERT INTO pretrans_schema SELECT * FROM sqlite_master;
/* DO SOMETHING AKA THE TRANSACTIONS */
CREATE TABLE IF NOT EXISTS newtable (id INTEGER PRIMARY KEY, acolumn TEXT);
INSERT INTO mytable (mycolumn) VALUES ('Mary')
-- ,('Fred'),('Jane'),('Anne'),('Alfred'),('George'),('Alan')
,('Susan'),('Betty'),('Catherine'),('John')
,(100),(200)
;
UPDATE mytable SET mycolumn = mycolumn||' has the detected letter.' WHERE mycolumn LIKE '%n%';
DELETE FROM mytable WHERE CAST(mycolumn AS INTEGER) > 0;
/* AFTER TRANSACTION */
SELECT rowid,'sm',* FROM sqlite_master UNION ALL SELECT rowid,'pt',* FROM pretrans_schema ORDER BY type,name; /* FOR DEMO/TESTING */
/* Get items added to the schema */
INSERT INTO schema_log SELECT *,4,strftime('%s','now') FROM sqlite_master WHERE name NOT IN (SELECT name FROM pretrans_schema);
/* Get items deleted from the schema */
INSERT INTO schema_log SELECT *,5,strftime('%s','now') FROM pretrans_schema WHERE name NOT IN (SELECT name FROM sqlite_master);
/* get original schema if schema updates */
INSERT INTO schema_log SELECT *,6,strftime('%s','now') FROM sqlite_master AS s
WHERE (sql <> (SELECT sql FROM pretrans_schema WHERE type = s.type AND name = s.name )) AND type IS NOT NULL /* AND NAME <> 'pretrans_schema' optional */
;
/* get current schema if schema updates */
INSERT INTO schema_log SELECT *,7,strftime('%s','now') FROM pretrans_schema AS s
WHERE (sql <> (SELECT sql FROM sqlite_master WHERE type = s.type AND name = s.name)) AND type IS NOT NULL /* AND NAME <> 'pretrans_schema' */
;
SELECT * FROM schema_log;
SELECT * FROM mytablelog;
Results
1 pre and post schema (part of)
Note the highlighted line, there is no pt entry, showing that the table has been added (no sm entry then it would have been deleted).
2 - The schema changes log (schema_log table)
4 indicates new item in the schema (5 deleted, 6 before update, 7 after update)
So the table newtable has been added as part of the transaction.
3 - The mytable transactions;
O's are insertions, 1's are deleted 2 and 3 (paired) pre and post update respectively.
Thank you for thinking along and especially #MikeT for the long and thourough answer. I have tried your answer and while it works it seemed not elegant to me and have a lot of overhead.
After a lot of search I have found that the solution is baked into SQLite but it must be enabled at compile time. It is called 'The Session Extension' and is exacly what I needed:
https://www.sqlite.org/sessionintro.html
I am using a trigger on my table for update.
When I was updating 1 row, it works. But when I update 20 rows at once, the log only show 1 history update.
I want to show all updated rows. How to make it?
This my simple sql:
create trigger Triger_Update_Product
on product
for update
as begin
declare #first char(10)
declare #after char(10)
select #first = name from deleted
select #after = name from inserted
insert into historyproduk
select #first, #after, getdate()
end
You need to write the trigger in a set-based fashion and be aware that inserted and deleted will contain multiple rows - so these lines of code are really bad:
select #first = name from deleted
select #after = name from inserted
These select one arbitrary row from the update set - and they ignore all other rows. Don't do this!!
Try this instead:
create trigger Trigger_Update_Product
on product
for update
as begin
insert into historyproduk
select d.name, i.name, getdate()
from deleted d
inner join inserted i on d.PrimaryKey = i.PrimaryKey
end
You need to join the Inserted and Deleted pseudo tables on the primary key and then grab the Name from the old (deleted) and new (Inserted) and insert those - along with the date/time stamp - into the history table in a single INSERT ... SELECT .... statement to handle multiple rows being updated
I am coming from Oracle background where I have used cursors for row updates.
Needed the easiest way to write looping logic. Here is the scenario
I have table1_intermediate which has Id,eligVal along with others
and table2_intermediatehas Id,inEligVal along with others
I have a main table table_main which has 3 columns Id,eligVal,inEligVal along with others
I want to insert records from both table1_intermediate and table2_intermediate into main table.
But if Id is already present( due to insert from one table), I want to update the same record coming from the 2nd intermediate table.
Any suggestions appreciated.
I prefer using two stored procedures. One to insert the value from table1 like ?:
Begin
If Not Exists (Select * from table_main where Id=#Id)
Begin
Insert into table_main(Id,eligVal) values(#Id,#eligVal)
End
End
and then insert a new row if Id is new or else update the same like :
Begin
If Not Exists (Select * from table_main where Id=#Id)
Begin
Insert into table_main(Id,inEligVal) values(#Id,#inEligVal)
End
Else
Begin
Update table_main Set inEligVal=#inEligVal where Id=#Id
End
End