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
Related
Very simplified, I have two tables Source and Target.
declare #Source table (SourceID int identity(1,2), SourceName varchar(50))
declare #Target table (TargetID int identity(2,2), TargetName varchar(50))
insert into #Source values ('Row 1'), ('Row 2')
I would like to move all rows from #Source to #Target and know the TargetID for each SourceID because there are also the tables SourceChild and TargetChild that needs to be copied as well and I need to add the new TargetID into TargetChild.TargetID FK column.
There are a couple of solutions to this.
Use a while loop or cursors to insert one row (RBAR) to Target at a time and use scope_identity() to fill the FK of TargetChild.
Add a temp column to #Target and insert SourceID. You can then join that column to fetch the TargetID for the FK in TargetChild.
SET IDENTITY_INSERT OFF for #Target and handle assigning new values yourself. You get a range that you then use in TargetChild.TargetID.
I'm not all that fond of any of them. The one I used so far is cursors.
What I would really like to do is to use the output clause of the insert statement.
insert into #Target(TargetName)
output inserted.TargetID, S.SourceID
select SourceName
from #Source as S
But it is not possible
The multi-part identifier "S.SourceID" could not be bound.
But it is possible with a merge.
merge #Target as T
using #Source as S
on 0=1
when not matched then
insert (TargetName) values (SourceName)
output inserted.TargetID, S.SourceID;
Result
TargetID SourceID
----------- -----------
2 1
4 3
I want to know if you have used this? If you have any thoughts about the solution or see any problems with it? It works fine in simple scenarios but perhaps something ugly could happen when the query plan get really complicated due to a complicated source query. Worst scenario would be that the TargetID/SourceID pairs actually isn't a match.
MSDN has this to say about the from_table_name of the output clause.
Is a column prefix that specifies a table included in the FROM clause of a DELETE, UPDATE, or MERGE statement that is used to specify the rows to update or delete.
For some reason they don't say "rows to insert, update or delete" only "rows to update or delete".
Any thoughts are welcome and totally different solutions to the original problem is much appreciated.
In my opinion this is a great use of MERGE and output. I've used in several scenarios and haven't experienced any oddities to date.
For example, here is test setup that clones a Folder and all Files (identity) within it into a newly created Folder (guid).
DECLARE #FolderIndex TABLE (FolderId UNIQUEIDENTIFIER PRIMARY KEY, FolderName varchar(25));
INSERT INTO #FolderIndex
(FolderId, FolderName)
VALUES(newid(), 'OriginalFolder');
DECLARE #FileIndex TABLE (FileId int identity(1,1) PRIMARY KEY, FileName varchar(10));
INSERT INTO #FileIndex
(FileName)
VALUES('test.txt');
DECLARE #FileFolder TABLE (FolderId UNIQUEIDENTIFIER, FileId int, PRIMARY KEY(FolderId, FileId));
INSERT INTO #FileFolder
(FolderId, FileId)
SELECT FolderId,
FileId
FROM #FolderIndex
CROSS JOIN #FileIndex; -- just to illustrate
DECLARE #sFolder TABLE (FromFolderId UNIQUEIDENTIFIER, ToFolderId UNIQUEIDENTIFIER);
DECLARE #sFile TABLE (FromFileId int, ToFileId int);
-- copy Folder Structure
MERGE #FolderIndex fi
USING ( SELECT 1 [Dummy],
FolderId,
FolderName
FROM #FolderIndex [fi]
WHERE FolderName = 'OriginalFolder'
) d ON d.Dummy = 0
WHEN NOT MATCHED
THEN INSERT
(FolderId, FolderName)
VALUES (newid(), 'copy_'+FolderName)
OUTPUT d.FolderId,
INSERTED.FolderId
INTO #sFolder (FromFolderId, toFolderId);
-- copy File structure
MERGE #FileIndex fi
USING ( SELECT 1 [Dummy],
fi.FileId,
fi.[FileName]
FROM #FileIndex fi
INNER
JOIN #FileFolder fm ON
fi.FileId = fm.FileId
INNER
JOIN #FolderIndex fo ON
fm.FolderId = fo.FolderId
WHERE fo.FolderName = 'OriginalFolder'
) d ON d.Dummy = 0
WHEN NOT MATCHED
THEN INSERT ([FileName])
VALUES ([FileName])
OUTPUT d.FileId,
INSERTED.FileId
INTO #sFile (FromFileId, toFileId);
-- link new files to Folders
INSERT INTO #FileFolder (FileId, FolderId)
SELECT sfi.toFileId, sfo.toFolderId
FROM #FileFolder fm
INNER
JOIN #sFile sfi ON
fm.FileId = sfi.FromFileId
INNER
JOIN #sFolder sfo ON
fm.FolderId = sfo.FromFolderId
-- return
SELECT *
FROM #FileIndex fi
JOIN #FileFolder ff ON
fi.FileId = ff.FileId
JOIN #FolderIndex fo ON
ff.FolderId = fo.FolderId
I would like to add another example to add to #Nathan's example, as I found it somewhat confusing.
Mine uses real tables for the most part, and not temp tables.
I also got my inspiration from here: another example
-- Copy the FormSectionInstance
DECLARE #FormSectionInstanceTable TABLE(OldFormSectionInstanceId INT, NewFormSectionInstanceId INT)
;MERGE INTO [dbo].[FormSectionInstance]
USING
(
SELECT
fsi.FormSectionInstanceId [OldFormSectionInstanceId]
, #NewFormHeaderId [NewFormHeaderId]
, fsi.FormSectionId
, fsi.IsClone
, #UserId [NewCreatedByUserId]
, GETDATE() NewCreatedDate
, #UserId [NewUpdatedByUserId]
, GETDATE() NewUpdatedDate
FROM [dbo].[FormSectionInstance] fsi
WHERE fsi.[FormHeaderId] = #FormHeaderId
) tblSource ON 1=0 -- use always false condition
WHEN NOT MATCHED
THEN INSERT
( [FormHeaderId], FormSectionId, IsClone, CreatedByUserId, CreatedDate, UpdatedByUserId, UpdatedDate)
VALUES( [NewFormHeaderId], FormSectionId, IsClone, NewCreatedByUserId, NewCreatedDate, NewUpdatedByUserId, NewUpdatedDate)
OUTPUT tblSource.[OldFormSectionInstanceId], INSERTED.FormSectionInstanceId
INTO #FormSectionInstanceTable(OldFormSectionInstanceId, NewFormSectionInstanceId);
-- Copy the FormDetail
INSERT INTO [dbo].[FormDetail]
(FormHeaderId, FormFieldId, FormSectionInstanceId, IsOther, Value, CreatedByUserId, CreatedDate, UpdatedByUserId, UpdatedDate)
SELECT
#NewFormHeaderId, FormFieldId, fsit.NewFormSectionInstanceId, IsOther, Value, #UserId, CreatedDate, #UserId, UpdatedDate
FROM [dbo].[FormDetail] fd
INNER JOIN #FormSectionInstanceTable fsit ON fsit.OldFormSectionInstanceId = fd.FormSectionInstanceId
WHERE [FormHeaderId] = #FormHeaderId
Here's a solution that doesn't use MERGE (which I've had problems with many times I try to avoid if possible). It relies on two memory tables (you could use temp tables if you want) with IDENTITY columns that get matched, and importantly, using ORDER BY when doing the INSERT, and WHERE conditions that match between the two INSERTs... the first one holds the source IDs and the second one holds the target IDs.
-- Setup... We have a table that we need to know the old IDs and new IDs after copying.
-- We want to copy all of DocID=1
DECLARE #newDocID int = 99;
DECLARE #tbl table (RuleID int PRIMARY KEY NOT NULL IDENTITY(1, 1), DocID int, Val varchar(100));
INSERT INTO #tbl (DocID, Val) VALUES (1, 'RuleA-2'), (1, 'RuleA-1'), (2, 'RuleB-1'), (2, 'RuleB-2'), (3, 'RuleC-1'), (1, 'RuleA-3')
-- Create a break in IDENTITY values.. just to simulate more realistic data
INSERT INTO #tbl (Val) VALUES ('DeleteMe'), ('DeleteMe');
DELETE FROM #tbl WHERE Val = 'DeleteMe';
INSERT INTO #tbl (DocID, Val) VALUES (6, 'RuleE'), (7, 'RuleF');
SELECT * FROM #tbl t;
-- Declare TWO temp tables each with an IDENTITY - one will hold the RuleID of the items we are copying, other will hold the RuleID that we create
DECLARE #input table (RID int IDENTITY(1, 1), SourceRuleID int NOT NULL, Val varchar(100));
DECLARE #output table (RID int IDENTITY(1,1), TargetRuleID int NOT NULL, Val varchar(100));
-- Capture the IDs of the rows we will be copying by inserting them into the #input table
-- Important - we must specify the sort order - best thing is to use the IDENTITY of the source table (t.RuleID) that we are copying
INSERT INTO #input (SourceRuleID, Val) SELECT t.RuleID, t.Val FROM #tbl t WHERE t.DocID = 1 ORDER BY t.RuleID;
-- Copy the rows, and use the OUTPUT clause to capture the IDs of the inserted rows.
-- Important - we must use the same WHERE and ORDER BY clauses as above
INSERT INTO #tbl (DocID, Val)
OUTPUT Inserted.RuleID, Inserted.Val INTO #output(TargetRuleID, Val)
SELECT #newDocID, t.Val FROM #tbl t
WHERE t.DocID = 1
ORDER BY t.RuleID;
-- Now #input and #output should have the same # of rows, and the order of both inserts was the same, so the IDENTITY columns (RID) can be matched
-- Use this as the map from old-to-new when you are copying sub-table rows
-- Technically, #input and #output don't even need the 'Val' columns, just RID and RuleID - they were included here to prove that the rules matched
SELECT i.*, o.* FROM #output o
INNER JOIN #input i ON i.RID = o.RID
-- Confirm the matching worked
SELECT * FROM #tbl t
I have two tables:
tblDepartment:
Id Name
and
tblEmployee:
Id FullName Dept_ID
Dept_ID is a foreign key for tblDepartment
I want to insert a new record into both tables.
I tried this:
declare #id int
insert dbo.tblDepartment
select Name='Name1'
select id = scope_Identity()
insert dbo.tblEmployee
select FullName = 'Name1'
select Dept_Id=#id
select Id=#id
However, this is not working. I searched through other posts, but they contain solutions for inserting existing data from one table into another, not creating a new record. How can I do this in one query?
You need to use variables properly along with column lists for inserts. Assuming you are using SQL Server:
declare #id int ;
insert dbo.tblDepartment(Name)
select 'Name1';
select #id = scope_Identity();
insert dbo.tblEmployee(FullName, Dept_Id)
select 'Name1', #id;
Also, scope_identity() is okay for learning about such id's. The safe way to really get this information is to use the output clause.
declare #id int
insert dbo.tblDepartment(Name)
select 'Name1'
-- Don't insert any other statements before next line...
select #id=scope_Identity()
insert dbo.tblEmployee(Fullname, Dept_ID)
select 'Name1', #id
I have 2 tables with some fields like this:
CompanyTable (companyCode, name, contactCode)
ContactTable (contactCode, address, phone)
CompanyTable.contactCode is foregin key that assign to ContactTable.contactCode
and also CompanyTable.companyCode and CompanyTable.contactCode are autoNumbers.
when I insert new record in contactTable, I want to insert it's contactCode into CompanyTable.contactCode like this:
insert into contactTable ( address, phone) values ('x', 'x')
update companyTable set company.contactCode = ---------
How to get latest identity value after insert query?
Thanks
Use ##IDENTITY to retrieve latest generated Identity value.
execute it after your insert query.
insert into contactTable ( address, phone) values ('x', 'x')
DECLARE #contactCode INT;
SELECT #contactCode = ##IDENTITY;
update companyTable set company.contactCode = #contactCode Where companyCode=1
You can add a trigger to add the contactCode. It would look like this:
CREATE TRIGGER trgUpdateCompanyTable
ON dbo.ContactTable
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #id INTEGER
SET #id = (SELECT IDENT_CURRENT('ContactTable'))
INSERT INTO CompanyTable (contactCode)
VALUES (#id)
END
GO
I have a trigger I am working on that will insert rows into a table when another table has inserts or updates applied to it. So far the Update portion works (the column that I'm most concerned with is the Balance column), but when the first row is added for an insert on the Account table, in my AuditTrailCustomerBalance table OldBalance, NewBalance and CustNo are set to NULL. How can I get NewBalance and CustNo to reference to the values that were just inserted into the table from the trigger?
Here is the trigger:
ALTER TRIGGER AuditTrigger
ON Accounts
FOR INSERT, UPDATE
AS
IF UPDATE( Balance )
BEGIN
IF EXISTS
(
SELECT 'True'
FROM Inserted i
JOIN Deleted d
ON i.AccountID = d.AccountID
)
BEGIN
--1. Declare temp variables.
DECLARE #OldBalance NUMERIC( 18, 0 )
DECLARE #NewBalance NUMERIC( 18, 0 )
DECLARE #CustNo INT
--2. Set the variables.
SELECT #OldBalance = Balance FROM deleted
SELECT #NewBalance = Balance FROM inserted
SELECT #CustNo = CustNo FROM inserted
INSERT INTO AuditTrailCustomerBalance( TimeChanged, ChangedBy, OldBalance, NewBalance, CustNo )
VALUES( GETDATE(), SUSER_SNAME(), #OldBalance, #NewBalance, #CustNo )
END
END
GO
And the test statement:
INSERT INTO Custs( CustNo, GivenName, Surname, DOB, SIN )
VALUES( 1, 'Peter', 'Griffen', 'January 15, 1950', '555555555')
INSERT INTO Accounts( CustNo, Type, Balance, AccruedInt, WithdrawalCount )
VALUES( 1, 'Savings', 0, 0, 0 )
UPDATE Accounts SET Balance = 100
WHERE CustNo = 1
I believe that you want something like this:
ALTER TRIGGER AuditTrigger
ON Accounts
FOR INSERT, UPDATE
AS
INSERT INTO AuditTrailCustomerBalance(TimeChanged, ChangedBy,
OldBalance, NewBalance, CustNo )
SELECT GETDATE(), SUSER_SNAME(),
COALESCE(d.Balance,0), i.Balance, i.CustNo
FROM inserted i
left join
deleted d
on
i.AccountNo = d.AccountNo
WHERE
i.Balance <> d.Balance OR
d.Balance IS NULL
As I said in my comments, inserted and deleted can contain multiple rows (or no rows) and so you need to take that into account and write a set-based query that deals with all of those rows - also some rows may have had balance changes and some not - so deciding whether to write any entries based on UPDATE(Balance) was also flawed.
you can if you are sure of your code write something like this :
if (select count(*) from inserted) = 1
and execute your code.
You can for the insert do like this :
insert into AuditTrailCustomerBalance (.....)
select .... from inserted
as already posted, the problem with your trigger is in the calling if you update one row or multiple (same for insert)
I need to create trigger in SQL Server 2008 that gone insert all values from one row in which some value was changed into Log table!
For example if i have table Employees that have column id, name , password, and i update this table and insert new value for column name, than i need to insert values that was in table Employees after update in table Log.
How I can do this? Thanks!
In your trigger, you have two pseudo-tables available, Inserted and Deleted, which contain those values.
In the case of an UPDATE, the Deleted table will contain the old values, while the Inserted table contains the new values.
So if you want to log the ID, OldValue, NewValue in your trigger, you'd need to write something like:
CREATE TRIGGER trgEmployeeUpdate
ON dbo.Employees AFTER UPDATE
AS
INSERT INTO dbo.LogTable(ID, OldValue, NewValue)
SELECT i.ID, d.Name, i.Name
FROM Inserted i
INNER JOIN Deleted d ON i.ID = d.ID
Basically, you join the Inserted and Deleted pseudo-tables, grab the ID (which is the same, I presume, in both cases), the old value from the Deleted table, the new value from the Inserted table, and you store everything in the LogTable
Here's an example update trigger:
create table Employees (id int identity, Name varchar(50), Password varchar(50))
create table Log (id int identity, EmployeeId int, LogDate datetime,
OldName varchar(50))
go
create trigger Employees_Trigger_Update on Employees
after update
as
insert into Log (EmployeeId, LogDate, OldName)
select id, getdate(), name
from deleted
go
insert into Employees (Name, Password) values ('Zaphoid', '6')
insert into Employees (Name, Password) values ('Beeblebox', '7')
update Employees set Name = 'Ford' where id = 1
select * from Log
This will print:
id EmployeeId LogDate OldName
1 1 2010-07-05 20:11:54.127 Zaphoid
In SQL Server 2008 you can use Change Data Capture for this. Details of how to set it up on a table are here http://msdn.microsoft.com/en-us/library/cc627369.aspx
createTRIGGER [dbo].[Table] ON [dbo].[table]
FOR UPDATE
AS
declare #empid int;
declare #empname varchar(100);
declare #empsal decimal(10,2);
declare #audit_action varchar(100);
declare #old_v varchar(100)
select #empid=i.Col_Name1 from inserted i;
select #empname=i.Col_Name2 from inserted i;
select #empsal=i.Col_Name2 from inserted i;
select #old_v=d.Col_Name from deleted d
if update(Col_Name1)
set #audit_action='Updated Record -- After Update Trigger.';
if update(Col_Name2)
set #audit_action='Updated Record -- After Update Trigger.';
insert into Employee_Test_Audit1(Col_name1,Col_name2,Col_name3,Col_name4,Col_name5,Col_name6(Old_values))
values(#empid,#empname,#empsal,#audit_action,getdate(),#old_v);
PRINT '----AFTER UPDATE Trigger fired-----.'
ALTER trigger ETU on Employee FOR UPDATE
AS
insert into Log (EmployeeId, LogDate, OldName)
select EmployeeId, getdate(), name
from deleted
go