Insert or update multiples rows - sql

I have two tables where TableA has latest data and TableB has some old data. I want to update TableB's data if it matches id with TableA and if doesn't match insert new row in TableB.
I got a solution from stackOverflow
begin tran
if exists (select * from t with (updlock,serializable) where pk = #id)
begin
update t set hitCount = hitCount+1
where pk = #id
end
else
begin
insert t (pk, hitCount)
values (#id, 1)
end
commit tran
But it seems I need to pass #id each time, may be I am not getting it in the correct way. I have hundreds of row to update/insert from tableA.

Think relationally.
SQL Server always operates sets. A single row is just a set of 1 row.
Here is a simple example of two step update - insert operations
create table #tableA(id int, [year] int, updated_value int)
insert #tableA(id,[year],updated_value)
values
(1,1990,85),
(2,1991,70),
(3,1992,80)
create table #tableB(id int, [year] int, score int)
insert #tableB(id,[year],score)
values
(1,1990,50),
(4,1995,20)
update #tableA set
updated_value=b.score
from #tableA a
inner join #tableB b on a.id=b.id --inner is important
insert #tableA(id,[year],updated_value)
select b.id,b.[year],b.score
from #tableB b
left join #tableA a on a.id=b.id --left is important
where a.id is null -- and this line too
select * from #tableA
If you wish you can combine update and insert in a single merge operation.
merge #tableA as tgt
using #tableB as src
on src.id=tgt.id
when matched then
update set updated_value=src.score
when not matched then
insert(id,[year],updated_value)
values(id,[year],score)
; -- semicoloumn is required
select * from #tableA

Related

set value using a conditional of a subquery

Sorry if I am not explaining my issue the best, but basically I have two tables.
Table A has a reference column to table B. On table B there is column X where for each referenced row, there is an unreferenced row with that same value of column X (table B has double the rows of table A). I want to update the reference on table A to be the row of table B that is not currently referenced of the two rows that have the same value on column X.
In pseudo code...
update tableA
set refCol = (select tableB.refCol
from tableB
where colX = (select colX
from tableB
where tableB.refCol = tableA.refCol)
and tableB.refCol != tableA.refCol)
The innermost query returns two rows, the outer query returns one row
sample tables:
Table A
refCol
1
3
Table B
refCol
colX
1
hello
2
hello
3
hi
4
hi
expected output:
Table A
refCol
2
4
Any help would be much appreciated.
Refer it below working example
create table #tableA(
id int)
create table #tableB(
id int,
name varchar(10)
)
insert into #tableA values(1)
insert into #tableA values(3)
insert into #tableA values(5)
insert into #tableA values(6)
insert into #tableA values(7)
insert into #tableA values(8)
insert into #tableB values (1,'A')
insert into #tableB values (2,'A')
insert into #tableB values (3,'C')
insert into #tableB values (4,'C')
select * from #tableA
select * from #tableB
update aa set aa.id=ab.id from #tableA aa inner join (
select b.id,b.name,a.id as ta from (
select B.* from #tableB b left join #tableA a on a.id=b.id where a.id is null)b
inner join (
select b.* from #tableA a inner join #tableB b on a.id=b.id)a on a.name=b.name)ab on aa.id=ab.ta

Inserting into a Table the result between a variable and a table parameter

Having the following procedure:
CREATE PROCEDURE [dbo].[Gest_Doc_SampleProc]
#Nome nvarchar(255),
#Descritivo nvarchar(255),
#SampleTable AS dbo.IDList READONLY
AS
DECLARE #foo int;
SELECT #foo=a.bar FROM TableA a WHERE a.Nome=#Nome
IF NOT EXISTS (SELECT a.bar FROM TableA a WHERE a.Nome=#Nome)
BEGIN
INSERT INTO TableA VALUES (#Nome,#Descritivo)
INSERT INTO TableB VALUES (scope_identity(),#SampleTable)
END
I am trying, as shown, inserting into TableB all the values of SampleTable, together with the scope_identity.
SampleTable is as:
CREATE TYPE dbo.SampleTable
AS TABLE
(
ID INT
);
GO
How can I correctly achieve this?
The right way to do this type of work is the OUTPUT clause. Although technically not needed for a single row insert, you might as well learn how to do it correctly. And even what looks like a single row insert can have an insert trigger that does unexpected things.
PROCEDURE [dbo].[Gest_Doc_SampleProc] (
#Nome nvarchar(255),
#Descritivo nvarchar(255),
#SampleTable AS dbo.IDList
) READONLY AS
BEGIN
DECLARE #ids TABLE (id int);
DECLARE #foo int;
SELECT #foo = a.bar
FROM TableA a
WHERE a.Nome = #Nome;
IF NOT EXISTS (SELECT 1 FROM TableA a WHERE a.Nome = #Nome)
BEGIN
INSERT INTO TableA (Nome, Descritive)
OUTPUT Inserted.id -- or whatever the id is called
INTO #ids;
VALUES (#Nome,#Descritivo)
INSERT INTO TableB (id, sampletable)
SELECT id, #SampleTable
FROM #ids;
END;
END; -- Gest_Doc_SampleProc
In addition to using OUTPUT, this code also adds column lists to the INSERTs. That is another best practice.

How to optimize a trigger?

CREATE TRIGGER T
ON TABLE_2
AFTER INSERT
AS
DECLARE #bought_t int,
#name_t varchar(20)
SELECT #name_t = name_t
FROM inserted
SELECT #bought_t = bought_t
FROM TABLE_1
WHERE name_t = #name_t
IF #bought_t < 100
BEGIN
UPDATE TABLE_1
SET bought_t = #bought_t + 1
WHERE TABLE_1.name_t = #name_t
END
ELSE
ROLLBACK TRANSACTION
The column (TABLE_1) I'm making the update to after the insert happens in the 'TABLE_2' is supposed to hold values between 50 and 100. So I'm asking If this trigger is as professional and optimized as It could be? or I have some flaws that could lead to bugs/security issues.
Basically, you need to completely rewrite your trigger to be set-based and to be able to work with multiple rows in the Inserted pseudo table.
Fortunately, that also makes it easier - in my opinion - try something like this:
CREATE TRIGGER T
ON TABLE_2
AFTER INSERT
AS
UPDATE T1
SET bought_t = bought_t + 1
FROM TABLE_1 T1
INNER JOIN Inserted i ON i.name_t = T1.name_t
WHERE T1.bought_t < 100
UPDATE: demo to prove this works:
-- create the two tables
CREATE TABLE TABLE_2 (ID INT NOT NULL IDENTITY(1,1), ProdName VARCHAR(50))
CREATE TABLE TABLE_1 (ProdName VARCHAR(50), Bought INT)
GO
-- create trigger on "TABLE_2" to update "TABLE_1"
CREATE TRIGGER T2Insert
ON TABLE_2
AFTER INSERT
AS
UPDATE T1
SET Bought = Bought + 1
FROM TABLE_1 T1
INNER JOIN Inserted i ON T1.ProdName = i.ProdName
WHERE T1.Bought < 100
GO
-- initialize TABLE_1 with some seed data
INSERT INTO dbo.TABLE_1 (ProdName, Bought)
VALUES ( 'Prod1', 0), ('Prod2', 20), ('Prod3', 40), ('Prod4', 40), ('Prod100', 100)
-- insert new values into TABLE_2
INSERT INTO dbo.TABLE_2 (ProdName)
VALUES ('Prod1'), ('Prod100'), ('Prod2'), ('Prod4')
-- get data to check
SELECT * FROM dbo.TABLE_1
This renders output:
As you can easily see:
Prod1, Prod2, Prod4 that were inserted caused an update of the value Bought
Prod100 which was also inserted did not cause an update of Bought
UPDATE #2: if you need to be able to insert multiple identical values at once, you need to slightly enhance your trigger like this:
CREATE TRIGGER T2Insert
ON TABLE_2
AFTER INSERT
AS
-- declare table variable to hold names and update counts
DECLARE #UpdateCount TABLE (Name VARCHAR(50), UpdCount INT)
-- from the "Inserted" table, determine which names are being
-- inserted how many times using GROUP BY
INSERT INTO #UpdateCount (Name, UpdCount)
SELECT ProdName, COUNT(*)
FROM Inserted
GROUP BY ProdName
-- now join to this temporary table, and update as many times
-- as needed (instead of +1 for all cases)
UPDATE T1
SET Bought = Bought + uc.UpdCount
FROM TABLE_1 T1
INNER JOIN #UpdateCount uc ON uc.Name = T1.ProdName
WHERE T1.Bought < 100
GO

SQL Server Merge - Getting matched records to another temp table

I have a MERGE query to update data. In case of no match I am inserting records to source getting the output to a temporary table.
Would it be possible to get the matched records to temporary table as well? Basically to avoid duplication of data in further processing I need to have copy of matched records.
This is my MERGE command:
MERGE Product.ProductHeaderRepository AS t
USING (SELECT GETDATE() as d, c1, c2, c3,
Name FROM Supplier.ProductHeaderImport
WHERE (BatchID = #BatchID) ) AS s
ON dbo.GetProductHeaderId(s.c1,S.c2,S.c3) <0
WHEN NOT MATCHED BY TARGET THEN
INSERT (Name, c1,c2,c3) VALUES (Name, c2,c2,c3)
OUTPUT INSERTED.iD, s.c1, s.c2, s.c3 INTO #TmpTable;
You could create a MATCHED clause that does not change anything and just updates a variable, e.g.
DECLARE #T1 TABLE (A INT, B INT);
DECLARE #T2 TABLE (A INT, B INT);
DECLARE #T3 TABLE (Action VARCHAR(20), A INT, B INT);
INSERT #T1 VALUES (1, 1), (2, 2), (3, 3);
INSERT #T2 VALUES (1, 0), (2, NULL), (4, 0);
DECLARE #I INT; -- VARIABLE TO UPDATE
MERGE #T2 B
USING #T1 A
ON A.A = B.A
WHEN MATCHED THEN
UPDATE SET #I = 1 -- DO NOTHING MEANINGFUL IN THE UPDATE;
WHEN NOT MATCHED BY TARGET THEN
INSERT (A, B) VALUES (A.A, A.B)
OUTPUT $action, ISNULL(inserted.A, deleted.A), ISNULL(inserted.B, deleted.B) INTO #T3;
SELECT *
FROM #T3;
Will return:
Action A B
INSERT 3 3
UPDATE 1 0
UPDATE 2 NULL
So if you add a new column to #TmpTable to store the action you can get your matched rows using:
SELECT *
FROM #TmpTable
WHERE Action = 'UPDATE';
And your new rows using:
SELECT *
FROM #TmpTable
WHERE Action = 'INSERT';

SQL:Updating a value (coming from destination table) in the source table after copying the data from source to destination table

The tables I have are;
TableA {TableA_OID, TableB_OID, SomeFields} //Source Table
TableB{TableB_OID, SomeFields} //Destination Table
I have to copy some data from source table to destination table, and on success i want to take the primary key identity field(TableB_OID) of destination table back to update (TableB_OID) field in the source table.
I think the following will work, but I'd play with it with some reasonable size data sets first, to be sure:
DECLARE #TA TABLE (ID INT IDENTITY(1,1), AID INT)
INSERT #TA(AID) SELECT TableA_OID FROM TABLEA -- ORDER BY data desc
DECLARE #TB TABLE (ID INT IDENTITY(1,1), BID INT)
INSERT TableB( data )
OUTPUT Inserted.TableB_OID INTO #TB(BID)
SELECT data
FROM #TA TA JOIN TableA ON TA.AID=TableA.TableA_OID ORDER BY TA.ID
SELECT * FROM #TA
SELECT * FROM #TB
UPDATE TableA
SET TableB_OID=TB.BID
FROM #TB TB
JOIN #TA TA ON TB.ID=TA.ID
JOIN TableA ON TA.AID=TableA.TableA_OID
SELECT * FROM TableA
SELECT * FROM TableB
First of all we're going to impose an order on the data we pull from table A, and use an identity column in a temporary table to record that order, linked to the original table A records. We'll then insert data into table B using that order, and record the resulting output into another temporary table. Again, we'll use an identity to record the sequence. We'll then use the identity values from the two temporary tables to link the tableA and tableB rows
I think you want to select the scope_identity()?
This will do a single row:
INSERT INTO TableB (
something
)
VALUES (
'Some Value'
)
DECLARE #Id int
SET #Id = scope_identity()
UPDATE TableA SET tableB_OID = #Id WHERE TableA_OID = TableAId
If you need to copy more than one row at once, something like the following will work:
DECLARE #data TABLE(ID int, data varchar(50))
INSERT TableB( data )
OUTPUT Inserted.TableB_OID, INSERTed.data INTO #data
SELECT data FROM TableA
UPDATE TableA
SET TableB_OID=D.ID
FROM #data D JOIN TableA ON D.DATA=TableA.data
It does make an assumption though, that there is a unique key in your "SomeField" (column "data") in my example, otherwise you can't relate the identity data back into tableA. If there is, then fine, otherwise, as Steph said, you'll need to add a TableA_OID field into TableB to be able to do the join to write back the data