Output Inserted.Id AND another field - sql

I have the following query:
DECLARE #OutProduct TABLE
(
ProductID INT,
BulkProductId INT
)
INSERT INTO dbo.Products
( EanCode ,
ChangedDateTime ,
ChangedById ,
Deleted
)
OUTPUT INSERTED.ID, BulkProducts.Id INTO #OutProduct (ProductID, BulkProductId)
SELECT EanCode ,
GETDATE(),
GETDATE(),
0
FROM dbo.BulkProducts
WHERE ProductId is NULL
Assuming Products.Id & BulkProducts.Id are auto-incrementing identity columns:
What I'm trying to achieve:
#OutProduct temp table contains tuples made up of the just-inserted Products.Id and the Id of the row in BulkProducts.
What I've stumbled upon: BulkProducts.Id cannot be used in the OUTPUT INSERTED.ID, BulkProducts.Id INTO statement, as it's not valid syntax.
How can I solve this?
EDIT: I'm using SQL Server 2012.

You might want to explore MERGE:
MERGE INTO dbo.Products
USING dbo.BulkProducts AS src
ON 1 = 0 -- Never match
WHEN NOT MATCHED THEN
INSERT(EanCode, ChangedDateTime, ChangedById, Deleted)
VALUES(src.EanCode, GETDATE(), GETDATE(), 0)
OUTPUT
inserted.Id,
src.Id
INTO #OutProduct;
Reference:
Dr. OUTPUT or: How I Learned to Stop Worrying and Love the MERGE by Adam Machanic

Related

Insert in to 2 tables from 2 tables from a different database

I am trying to read data from 2 tables and inserting to similar tables in a different database.
Here is my query
BEGIN TRANSACTION
Delete from aspnet_Website.[dbo].[Places]
DECLARE #AutoID int;
insert into aspnet_Website.[dbo].[Places] (ReferenceID, Name, OtherKnownNames, Description, Email)
select ReferenceID, Name, OtherKnownNames, Description, Email from DB_A290D0_places.
[dbo].[places]
where PublishingStatus=0
SELECT #AutoID = scope_identity();
insert into aspnet_Website.[dbo].[Schedules] (Timing, Type, PlaceID)
select [Timing], [Type], #AutoID from DB_A290D0_places.[dbo].[Schedules]
COMMIT
I am getting error
Incorrect syntax near '#AutoID'.
and even I am not sure that it will work or not.
'Places' table has ID field which is auto id and it is used as FK in Schedule table, so for every 'place' row I need to get auto id and insert it into the schedule table along with the corresponding table data from another database.
Update1 : I have fixed the syntax error, I can see records added into the table but last generated AutoID is being used for all rows in child table. I want to use autoid generated for each row.
Update2 : following script worked
BEGIN TRANSACTION
Delete from aspnet_Website.[dbo].[Places]
declare #NewId table (ID int);
insert into aspnet_Website.[dbo].[Places] (ReferenceID, Name, OtherKnownNames, Description, Email)
select ReferenceID, Name, OtherKnownNames, Description, Email from DB_A290D0_places.
[dbo].[places]
where PublishingStatus=0
output Inserted.ID into #NewId (ID)
insert into aspnet_Website.[dbo].[Schedules] (Timing, [Type], PlaceID)
select [Timing], [Type], P.ID
from DB_A290D0_places.[dbo].[Schedules] S
inner join #NewId P on P.ID = S.PlaceId;
COMMIT
You can't store more than one value in a variable, the the line SELECT #AutoID = scope_identity(); will only capture the last id inserted.
To solve you problem have you considered not changing the ids by setting IDENTITY_INSERT ON and inserting the original ids?
Otherwise use the OUTPUT clause to capture the new ids, map them to the old ids, and then insert them into the Schedules table.
begin transaction
delete from aspnet_Website.[dbo].[Places];
declare #AutoID int;
declare #NewId table (ID int, OldID int);
-- In an insert statement you can't access the source table in the output clause unfortunately
/*
insert into aspnet_Website.[dbo].[Places] (ReferenceID, [Name], OtherKnownNames, [Description], Email)
output Inserted.ID, P.ID into #NewId (ID, OldID)
select ReferenceID, [Name], OtherKnownNames, [Description], Email
from DB_A290D0_places.[dbo].[places] P
where PublishingStatus = 0;
*/
-- However in a merge statement you can access both the source and destination tables in the output clause.
merge into aspnet_Website.[dbo].[Places] T
using DB_A290D0_places.[dbo].[places] S on 1 = 0 -- always false
when not matched by target and S.PublishingStatus = 0 then -- happens for every row, because 1 is never 0
insert (ReferenceID, [Name], OtherKnownNames, [Description], Email)
values (S.ReferenceID, S.[Name], S.OtherKnownNames, S.[Description], S.Email)
output Inserted.ID, S.ID into into #NewId (ID, OldID);
insert into aspnet_Website.[dbo].[Schedules] (Timing, [Type], PlaceID)
select [Timing], [Type], P.ID
from DB_A290D0_places.[dbo].[Schedules] S
inner join #NewId P on P.OldId = S.PlaceId;
commit

SSIS Staging Table to Normalized form

I could be down the wrong path with this. However, here goes. I am trying to take multiple excel sheets and load them into SQL Server using SSIS.
Excel sheet:
RQ|Descr|PartNum|Manufacturer|...
I am loading this into a staging table with a couple of derived columns:
RQ|Descr|PartNum|Manufacturer|Origin|DateTime|...
This is no big deal, I am able to do this easily. However, the problem is how to get the data from the staging table to the correct table and ensuring FK constraints are followed. See below for an illustration.
My goal is to take RQ|Descr|PartNum|Manufacturer|Origin|DateTime|...
and populate multiple tables
[t1] id|RQ|Descr|Origin|DateTime
[t2] id|t1_id|PartNum|Manufacturer
[t3] id|t1_id|...
I have tried MERGE however I am unsure how to keep the FK relationship.
MERGE INTO spin_item AS targ
USING ssis_stage AS src ON 1=0 -- always generates "not matched by target"
WHEN NOT MATCHED BY TARGET THEN
-- INSERT into spin_item:
INSERT (description, reqqty, price, origin, datetime, exclude, status, siteid, production, repairable)
VALUES (src.description, src.rq, src.price, src.origin, GETDATE(), 0, 'N', '', 0, 0)
-- INSERT into spin_part:
OUTPUT inserted.ID, src.manufacturer, src.partnum
INTO spin_part (ID, src.manufacturer, src.partnum);
I have looked into this SSIS : Using multicast to enter data into 2 RELATED destinations but this is for a one-to-many relationship. So, I am not sure how to populate my t1 table and use the id to populate t2, t3 from the staging table.
EDIT: Below, seems to be a working solution. However, I am not sure that it is a good solution.
BEGIN
SET IDENTITY_INSERT dbo.spin_item ON
--Insert into spin_item
MERGE INTO spin_item AS targ
USING ssis_stage AS src ON 1=0
WHEN NOT MATCHED BY TARGET THEN
INSERT (id, description, reqqty, price, origin, datetime, exclude, status, siteid, production, repairable)
VALUES (src.id, src.description, src.rq, src.price, src.origin, GETDATE(), 0, 'N', '', 0, 0);
SET IDENTITY_INSERT dbo.spin_item OFF
--Insert into spin_part
MERGE INTO spin_part AS targ
USING ssis_stage AS src ON 1=0
WHEN NOT MATCHED BY TARGET AND src.partnum IS NOT NULL THEN
INSERT (itemid_id, manufacturer, partnum, catalognum, [primary])
VALUES (src.id, src.manufacturer, src.partnum, src.partnum, 1);
--Insert into spin_stock
MERGE INTO spin_stock AS targ
USING ssis_stage AS src ON 1=0
WHEN NOT MATCHED BY TARGET AND src.stock IS NOT NULL THEN
INSERT (itemid_id, stocknum)
VALUES (src.id, src.stock);
--Insert into spin_collaboration
MERGE INTO spin_collaboration AS targ
USING ssis_stage AS src ON 1=0
WHEN NOT MATCHED BY TARGET AND src.notes IS NOT NULL THEN
INSERT (itemid_id, comment, datetime)
VALUES (src.id, src.notes, GETDATE());
DELETE FROM ssis_stage WHERE id > 0 --Instead of Truncate since auto_increment will reset.
END
You can create an ID column on your staging table, based off your target tables that is then used as the FK in each table insert:
declare #source table (ID int, a int, b int, c int);
insert into #source values
(null,1,1,1)
,(null,1,1,2)
,(null,1,2,2)
,(null,5,3,2)
,(null,7,1,2)
,(null,2,1,2)
declare #target1 table (ID int, a int);
insert into #target1 values
(1,5)
,(2,6)
,(3,99);
declare #target2 table (ID int, b int, c int);
insert into #target2 values
(1,3,2)
,(2,9,7)
,(3,57,3);
update s
set ID = ss.IDNew
from #source s
inner join (
select row_number() over (order by a,b,c) + (select max(ID) from #target1) as IDNew
,a
,b
,c
from #source
) ss
on(s.a = ss.a
and s.b = ss.b
and s.c = ss.c
);
select * from #target1;
select * from #source;
insert into #target1
select ID
,a
from #source;
insert into #target2
select ID
,b
,c
from #source;
select * from #target1;
select * from #target2;

Update table row with inserted identity ID

I have the following query:
INSERT INTO dbo.Products
( Code ,
ProducerCode ,
CustomerId ,
EanCode ,
ChangedDateTime ,
ChangedById ,
Deleted
)
SELECT Code ,
ProducerCode ,
CustomerId ,
EanCode ,
GETDATE(),
GETDATE(),
0
FROM dbo.BulkProducts
WHERE ProductId is NULL AND BulkProducts.BulkId = #BulkId
The Products table has an IDENTITY ID column, which is assigned automatically on insert. What I want to achieve is, after the insertion in Products, have the assigned ID of the product in the row from BulkProducts. I have read a bit about##IDENTITY, IDENT_CURRENT and SCOPE_IDENTITY but I cannot seem to get it to work for my example. Any help appreciated. I am using SQL Server 2012.
You can achieve it using Output clause, like this -
DECLARE #OutProduct TABLE
(
ProductID INT,
Code INT
)
INSERT INTO dbo.Products
( Code ,
ProducerCode ,
CustomerId ,
EanCode ,
ChangedDateTime ,
ChangedById ,
Deleted
)
OUTPUT INSERTED.ID, INSERTED.Code INTO #OutProduct (ProductID, Code)
SELECT Code ,
ProducerCode ,
CustomerId ,
EanCode ,
GETDATE(),
GETDATE(),
0
FROM dbo.BulkProducts
WHERE ProductId is NULL AND BulkProducts.BulkId = #BulkId
So Now all your inserted data are in #OutProduct table. Now you can update your BulkProducts table using that temp table like below.
UPDATE bprod
SET productID = tmp.ProductID
FROM BulkProducts bprod
INNER JOIN #OutProduct tmp
ON bprod.Code = tmp.Code
WHERE bprod.ProductId is NULL AND bprod.BulkId = #BulkId
Apparently this cannot be solved without a MERGE. The final solution looks like this:
MERGE INTO dbo.Products
USING dbo.BulkProducts AS src
ON 1 = 0 -- Never match
WHEN NOT MATCHED THEN
INSERT(EanCode, ChangedDateTime, ChangedById, Deleted)
VALUES(src.EanCode, GETDATE(), GETDATE(), 0)
OUTPUT
inserted.Id,
src.Id
INTO #OutProduct;
And like this, my #OutProduct table contains tuples of BulkProductId and ProductId which I can then use to update my BulkProducts table.

Can I use the output clause in the Update command to generate 2 rows

I can use OUTPUT to generate the before and after row values when doing an update to a table. And I can put these values into a table.
But they go into the table in 1 single row.
I need the before values as a row, and the after values as a new row.
So this snippet isn't working for me at the moment, but it shows the thing I am trying to do. Is there any way to do this using OUTPUT.
update dbo.table1
set employee = employee + '_updated'
OUTPUT 'd',DELETED.* INTO dbo.table2,
OUTPUT 'i',INSERTED.* INTO dbo.table2,
WHERE id = 4 OR id = 2;
This snippet below works, but only creates a single row:
update dbo.table1
set employee = employee + '_updated'
OUTPUT 'd', DELETED.*, 'i', INSERTED.* INTO dbo.table2,
WHERE id = 4 OR id = 2;
I can do it using triggers, but that's not allowed in this case.
And I can do it manually (selecting what I'm going to update into table2, then doing the update...)
Any tips or hints appreciated on how to do it just using just OUTPUT in the update?
Rgds, Dave
To elaborate on an answer given in the comments:
declare #TempTable table (
d_id int,
d_employee varchar(50),
d_other varchar(50),
u_id int,
u_employee varchar(50),
u_other varchar(50)
)
update Table1
set employee = employee + '_updated'
output deleted.id d_id, deleted.employee d_employee, deleted.other d_other,
inserted.id u_id, inserted.employee u_employee, inserted.other u_other
into #TempTable
where id = 4 or id = 2;
insert Table2 (change_type, employee_id, employee, other)
select
'd',
d_id,
d_employee,
d_other
from #TempTable
union all
select
'i',
u_id,
u_employee,
u_other
from #TempTable
I made some assumptions about your schema as it wasn't given, but this should get you started.

Merge Statement with two inserts?

Given is a simple MERGE statement. Where I Insert/Update records into traget table. Question: is it possible to also Insert those values in another table with a flag beeing 0 for insert and 1 for update? Eg. when not match do insert into target and another table, when matched do update target and insert into another table.
MERGE dbo.FactBuyingHabits AS Target
USING (SELECT CustomerID, ProductID, PurchaseDate FROM dbo.Purchases) AS Source
ON (Target.ProductID = Source.ProductID AND Target.CustomerID = Source.CustomerID)
WHEN MATCHED THEN
UPDATE SET Target.LastPurchaseDate = Source.PurchaseDate
--and insert into test_tbl values (1, Source.ProductID, Source.CustomerID) --?
WHEN NOT MATCHED BY TARGET THEN
INSERT (CustomerID, ProductID, LastPurchaseDate)
VALUES (Source.CustomerID, Source.ProductID, Source.PurchaseDate)
--and insert into test_tbl values (0, Source.ProductID, Source.CustomerID) --?
you should read about OUTPUT
ex (source);
DECLARE #MergeOutput1 table
(
ActionType nvarchar(10),
BookID int,
OldBookTitle nvarchar(50),
NewBookTitle nvarchar(50),
ModifiedDate datetime
);
-- use MERGE statement to perform update on Book2
MERGE Books2 AS b2
USING Books AS b1
ON (b2.BookID = b1.BookID)
WHEN MATCHED
THEN UPDATE
SET b2.BookTitle = b1.BookTitle
OUTPUT
$action,
INSERTED.BookID,
DELETED.BookTitle,
INSERTED.BookTitle,
INSERTED.ModifiedDate
INTO #MergeOutput1;