How to manually replicate two tables with identity fields - sql-server

-- DUPLICATE OF Best way to get identity of inserted row? --
Well, I got an hard time finding a good title for this one.
I have two tables, with microsoft SQL server. Here's the structure:
TableA ( Id identity, Name varchar(20))
TableB ( Id identity, Name varchar(20))
Mapping ( IdTableA int, IdTableB int)
On TableA, I have an After Insert trigger. I want to "replicate" the insert into TableB, gets the new Ids and insert the mapping between the TableA's id and the TableB's id into the table Mapping.
I have no problem with inserting the data into TableB... but I have an hard time finding how to create a "map" between the two Ids...
Any help would be very appreciated.

Related

T-SQL OUTPUT clause to update a temp table

I have a utility script that is used to insert data into tables in my database. The script has a number of temp table in it that stores the new data to be inserted and a lot of it is related.
So, for example I have tables like so
DECLARE #Table1 TABLE
(
Table1ID INT
Table1Description VARCHAR(50)
Table1Code VARCHAR(5)
)
DECLARE #Table2 TABLE
(
Table2ID INT
Table2Description VARCHAR(50)
Table2Code VARCHAR(5)
)
DECLARE #Relationships TABLE
(
Table1Code VARCHAR(5)
Table2Code VARCHAR(5)
)
So the script populates the data in #Table1 and #Table2, but doesn't populate the ID fields. Once the data has been MERGEd into the database tables, I update the Table1ID and Table2ID fields in a separate statement as they are auto incrementing fields. Then when I use the #Relationships table to populate the database table, I can join to #Table1 and #Table2 to get the actual ID values.
I'm updating the script and I'm wondering if I can MERGE the data from #Table1/#Table2 into the database and update the ID fields in the temp table as part of the MERGE statement using the OUTPUT clause all in one statement?
I think the answer is no as I can't find anything mentioning updating an existing table with the OUTPUT clause, only inserting into a table.
I am still able to do what I need to do, so I'm not after alternatives. I just wondering if it is possible using the OUTPUT Clause
Thanks in advance

How can I insert rows of one table into multiple tables using a SQL Server stored procedure?

I am interested in inserting my rows of tempDataTable into two tables.
This is the table design of my tempdatatable:
The two tables I want to create via the stored procedure from my TempDataTable (one in the image).
Design for the two new table would be something like;
Table one (Product): ProductID (PK), ProductName, Product URL
Table two (ProductPricing): ProductPricingID(PK), ProductId (FK), price, priceperunit, Date
It's been a complete day I am searching for a solution, and will kept doing this but I am unable to exact solution. I am not experience with SQL but this is something I have to do.
Okay, I'm not sure exactly where you are struggling, so here's a script that sort of does what you asked for. None of this is too hard to follow, so maybe have a scan through it, and then let me know which bits are confusing?
Set up the table structure:
CREATE TABLE tempDataTable (
TempProductId INT,
TempProductUrl VARCHAR(512),
TempProductPrice VARCHAR(50),
TempProductPricePerUnit VARCHAR(50),
TempProductName VARCHAR(512));
INSERT INTO tempDataTable SELECT 2491, 'https://yadayada1', '£1.65/unit', '46p/100g', 'Yeo Valley Little Yeos, blah';
INSERT INTO tempDataTable SELECT 2492, 'https://yadayada2', '60p/unit', '1p/ea', 'Sainsbury''s Little Ones, etc';
CREATE TABLE Product (
ProductId INT PRIMARY KEY,
ProductName VARCHAR(512),
ProductUrl VARCHAR(512));
CREATE TABLE ProductPricing (
ProductPricingId INT IDENTITY(1,1) PRIMARY KEY,
ProductId INT,
ProductPrice VARCHAR(50),
ProductPricePerUnit VARCHAR(50),
ProductPricingDate DATETIME);
ALTER TABLE ProductPricing ADD CONSTRAINT foreignkey$ProductPricing$Product FOREIGN KEY (ProductId) REFERENCES Product (ProductId);
This gives me three tables to play with, one with some temporary data in it, and two that you want to push the data into, with a couple of primary keys, and a foreign key constraint to ensure integrity between the two tables.
Good so far?
Now to split the data between the two tables is as simple as:
INSERT INTO Product (ProductId, ProductName, ProductUrl) SELECT TempProductId, TempProductName, TempProductUrl FROM tempDataTable;
INSERT INTO ProductPricing (ProductId, ProductPrice, ProductPricePerUnit, ProductPricingDate) SELECT TempProductId, TempProductPrice, TempProductPricePerUnit, GETDATE() FROM tempDataTable;
If you run that then you should end up with data in your two tables, like this:
Product
ProductId ProductName ProductUrl
2491 Yeo Valley Little Yeos, blah https://yadayada1
2492 Sainsbury's Little Ones, etc https://yadayada2
ProductPricing
ProductPricingId ProductId ProductPrice ProductPricePerUnit ProductPricingDate
1 2491 £1.65/unit 46p/100g 2020-04-27 14:29:14.657
2 2492 60p/unit 1p/ea 2020-04-27 14:29:14.657
Now there's a whole load of questions that arise from this:
how are you going to cope with running this more than once, because the second time you run it there will be primary key violations?
do you want to clear down the temporary data somehow on successful completion?
do you want to use the system date as the pricing date, or are there more columns off the edge of your image?
do you want to check the data for duplicates and deal with them before running the script, or it will just fail?
if you do get a duplicate then do you skip it, or update the data (MERGE)?
why do you want this as a stored procedure? I mean it's easy enough to make into one, but I don't see why this would need to be repeatable... without seeing the other "moving parts" in this system anyway.
I'm guessing that you are loading bulk data into that temporary table somehow, from an Excel workbook, or XML, or similar. So all you want is a way to "tear the data up" into multiple tables. If this is indeed the case, then using a tool like SSIS might be more practical?
Okay, so that's 90% there, but you need two other things:
cope with situations where the product id already exists - don't try to insert it a second time as it will fail;
where the product id already exists then update the price data.
This should handle the first tweak:
INSERT INTO Product (ProductId, ProductName, ProductUrl) SELECT t.TempProductId, t.TempProductName, t.TempProductUrl FROM tempDataTable t
WHERE NOT EXISTS (SELECT * FROM Product p WHERE p.ProductId = t.TempProductId);
...and to UPDATE prices where the data already exists, or INSERT them if they don't exist, well you can use a MERGE statement:
MERGE
ProductPricing AS [target]
USING (SELECT TempProductId, TempProductPrice, TempProductPricePerUnit, GETDATE() AS ProductPricingDate FROM tempDataTable)
AS [source] (
ProductId,
ProductPrice,
ProductPricePerUnit,
ProductPricingDate)
ON ([target].ProductId = [source].ProductId)
WHEN MATCHED THEN
UPDATE SET
ProductPrice = [source].ProductPrice,
ProductPricePerUnit = [source].ProductPricePerUnit,
ProductPricingDate = [source].ProductPricingDate
WHEN NOT MATCHED THEN
INSERT (
ProductId,
ProductPrice,
ProductPricePerUnit,
ProductPricingDate)
VALUES (
[source].ProductId,
[source].ProductPrice,
[source].ProductPricePerUnit,
[source].ProductPricingDate);
Actually, re-reading your comment, I don't think you even need a MERGE (but I'm going to leave it there anyway, as it took me a little effort to write it).
I think your second case is as simple as just letting the second INSERT always run. There's two scenarios:
if there's already an entry for that product - then just add a new row to the ProductPricing table, so you will have one product, and two (or more) prices, each with a different date;
if it's a new product - then add the product and the price, so you will have one product and one price (until a new price arrives).
...and I can't resist adding, this is because you are using a natural key, i.e. a key from your data, so it doesn't change as you load it. If you were using a surrogate key (e.g. an IDENTITY that you got when you inserted the Product) then this wouldn't work, you would need to go and look up the surrogate key, then use this so your foreign key constraint worked properly. It's probably best to not think about this too hard?

Migrate tables from MS Access to MS SQL Server [duplicate]

This question already has answers here:
Migration Access to SQL server 2012
(4 answers)
Closed 8 years ago.
I'm moving a database from MS Access to MS SQL Server 2012. My question involves a data issue we need to resolve for the SQL Server database. We have a main MS Access table that generates an integer key then used in several other tables. In Access, the column autoincrements when a new record is created. The issue we're having is that there are gaps in the Access tables columny where records were (unwisely) deleted. It seems that the solution for the SQL Server version of he table is to migrate the table with that column set as PRIMARY and UNIQUE, without using the IDENTITY value. That means we would no longer get an autoincremented number. We'd have to write some code to create that numbering using a SELECT with MAX(). Is there a better way to resolve this issue?
You have TableA that currently maintains the Primary Key that is used is related tables. You want TableB to now control the creation of this Primary Key. I don't see why you can't continue using an auto-incrementing primary key on TableB to manage the creation of the Primary Key. It doesn't matter that previous values have gaps. SQL Server has a way around that.
You can't convert an exiting column in a table to IDENTITY, but you can create a table in SQL Server with an IDENTITY column, turn the IDENTITY function off, populate it with your own values (gaps and all), and then turn the IDENTITY function back on.
From the documentation on IDENTITY_INSERT:
-- Create products table.
CREATE TABLE products (id int IDENTITY PRIMARY KEY, product varchar(40))
GO
-- Inserting values into products table.
INSERT INTO products (product) VALUES ('screwdriver')
INSERT INTO products (product) VALUES ('hammer')
INSERT INTO products (product) VALUES ('saw')
INSERT INTO products (product) VALUES ('shovel')
GO
-- Create a gap in the identity values.
DELETE products
WHERE product = 'saw'
GO
SELECT *
FROM products
GO
-- Attempt to insert an explicit ID value of 3;
-- should return a warning.
INSERT INTO products (id, product) VALUES(3, 'garden shovel')
GO
-- SET IDENTITY_INSERT to ON.
SET IDENTITY_INSERT products ON
GO
-- Attempt to insert an explicit ID value of 3
INSERT INTO products (id, product) VALUES(3, 'garden shovel').
GO
-- SET IDENTITY_INSERT to OFF.
SET IDENTITY_INSERT products OFF
GO
SELECT *
FROM products
Using this technique, you can convert the Access version of TableB to temp_TableB in SQL Server, where there is no identity column. Then create a real TableB in SQL Server with an IDENTITY column.
Then you could
SET IDENTITY_INSERT TableB ON
INSERT INTO TableB ( {columnlist} )
SELECT * from temp_TableB
SET IDENTITY_INSERT TableB OFF
Now you have TableB populated correctly and ready to create the next auto-incremented IDENTITY value.

How to get both the new pk and old pk when inserting a subset of a table into itself?

I'm inserting a subset of a table into the same table and in order to create records in some mapping tables need to capture both the newly created identity PK, and the matching old PK..
If SQL would support it, something like:
Create table Test (pk identity, description varchar(10))
Declare #PKVALUES TABLE (NewPK int, OLdPk int)
INSERT INTO Test (description)
OUTPUT INSERTED.PK, Test.PK into #PKVALUES
Select description
From Test
Where ...
But, of course, SQL doesn't support Output of values from the FROM table during an INSERT operation..
The only set based alternative I've come across requires locking the whole table while creating the new PKs in a temporary table and then inserting them into the Test table using identity insert.
Is there some way I can accomplish this, (without having to resort to a one record at a time
approach or having to lock the whole table) ?
Thanks,
Ilmar
My preference would be to add a column to store the old pk in and then you can return it from the output clause. However, it is not always possible to change the table.
So, I have a sneaky trick but it involves doing twice as much work on your db. What you do is put the Old PK in the description field in the intial insert. Then you update the description to the value of teh old PK by joining on the description field to the PK.
Create table Test (pk identity, description varchar(10))
Declare #PKVALUES TABLE (NewPK int, OLdPk varchar(10)
INSERT INTO Test (description)
OUTPUT INSERTED.PK, INSERTED.Description into #PKVALUES
SELECT PK from Test where....
UPDATE tnew
SET description = told.description
FROM test told
JOIN test tnew ON CAST(told.PK AS varchar (10)) = t.description

SQL Server 2005 How can I set up an audit table that records the column name updated?

given this table definition
create table herb.app (appId int identity primary key
, application varchar(15) unique
, customerName varchar(35),LoanProtectionInsurance bit
, State varchar(3),Address varchar(50),LoanAmt money
,addedBy varchar(7) not null,AddedDt smalldatetime default getdate())
I believe changes will be minimal, usually only a single field, and very sparse.
So I created this table:
create table herb.appAudit(appAuditId int primary key
, field varchar(20), oldValue varchar(50),ChangedBy varchar(7) not null,AddedDt smalldatetime default getdate())
How in a trigger can I get the column name of the value of what was changed to store it? I know how to get the value by joining the deleted table.
Use the inserted and deleted tables. Nigel Rivett wrote a great generic audit trail trigger using these tables. It is fairly complex SQL code, but it highlights some pretty cool ways of pulling together the information and once you understand them you can create a custom solution using his ideas as inspiration, or you could just use his script.
Here are the important ideas about the tables:
On an insert, inserted holds the inserted values and deleted is empty.
On an update, inserted holds the new values and deleted holds the old values.
On a delete, deleted holds the deleted values and inserted is empty.
The structure of the inserted and deleted tables (if not empty) are identical to the target table.
You can determine the column names from system tables and iterate on them as illustrated in Nigel's code.
if exists (select * from inserted)
if exists (select * from deleted)
-- this is an update
...
else
-- this is an insert
...
else
-- this is a delete
...
-- For updates to a specific field
SELECT d.[MyField] AS OldValue, i.[MyField] AS NewValue, system_user AS User
FROM inserted i
INNER JOIN deleted d ON i.[MyPrimaryKeyField] = d.[MyPrimaryKeyField]
-- For your table
SELECT d.CustomerName AS OldValue, i.CustomerName AS NewValue, system_user AS User
FROM inserted i
INNER JOIN deleted d ON i.appId = d.appId
If you really need this kind of auditing in a way that's critical to your business look at SQL Server 2008's Change Data Capture feature. That feature alone could justify the cost of an upgrade.
something like this for each field you want to track
if UPDATE(Track_ID)
begin
insert into [log].DataChanges
(
dcColumnName,
dcID,
dcDataBefore,
dcDataAfter,
dcDateChanged,
dcUser,
dcTableName
)
select
'Track_ID',
d.Data_ID,
coalesce(d.Track_ID,-666),
coalesce(i.Track_ID,-666),
getdate(),
#user,
#table
from inserted i
join deleted d on i.Data_ID=d.Data_ID
and coalesce(d.Track_ID,-666)<>coalesce(i.Track_ID,-666)
end
'Track_ID' is the name of the field, and d.Data_ID is the primary key of the table your tracking. #user is the user making the changes, and #table would be the table your keeping track of changes in case you're tracking more than one table in the same log table
Here's my quick and dirty audit table solution. (from http://freachable.net/2010/09/29/QuickAndDirtySQLAuditTable.aspx)
CREATE TABLE audit(
[on] datetime not null default getutcdate(),
[by] varchar(255) not null default system_user+','+AppName(),
was xml null,
[is] xml null
)
CREATE TRIGGER mytable_audit ON mytable for insert, update, delete as
INSERT audit(was,[is]) values(
(select * from deleted as [mytable] for xml auto,type),
(select * from inserted as [mytable] for xml auto,type)
)

Resources