Cloning A Table Along With Its Foreign Keys - sql-server

How can I clone a table with guid's as well as another table that references those guid's as foreign keys.
The two tables have a one to many relationship. I would need to do this in an automated way.
My problem is being able to reference the relationship between the two tables when newly created guid's are created during the clone.
EDIT: I need to clone the entries to the tables themselves, not new tables.

Basically, we want to duplicate the Tables with new Ids but keep the relationship the same as the originals.
If required, you could keep the same Ids, but in practice this shouldn't be a requirement; for testing, the Ids should not matter - only the relationship.
I'll demonstrate this with two Tables:
The first is AnimalType with Id (uniqueidentifier - RowGuid, Primary Key) and AnimalType (nvarchar) Columns
The second is Animal with AnimalName (nvarchar) and AnimalType (uniqueidentifier, Foreign Key) Columns
For the parent/lookup Table:
Create a new Table (newTable) to populate with the data of the existing Table (oldTable).
Create newTable with its own Primary Key Id column (ROWGUID, IDENTITY etc.) with its Default
Create an extra Column in newTable to hold a copy of oldTable's Id Column values
The Id Column in newTable will generate unique Ids on creation of records
The second (child) Table:
Create a new Table (newChildTable) to populate with the data of the existing Table (oldChildTable).
Create newChildTable with its own Foreign Key Column to point to newTable's Primary Key Column
Create an extra Column in newChildTable to hold a copy of oldChildTable's Foreign Key Column values
Once created, we populate the new parent/lookup Table with the data from the original Table, placing the Id values in the extra Column added for this data. The Table's own Ids will generate uniquely as usual.
Next, we populate the child Table with the data from its original Table, placing the original Foreign Key Column values into the added Column for this data.
Next, we join the two new tables on the Columns that hold the original Id values and update the Foreign Key Column values to the new Ids in the parent/lookup Table.
Finally, we can remove the Columns holding the original Id values, and we are left with two Tables linked to the same data but by the new Ids we generated when created when the records were copied.
You will not have any reference to the original Ids - just in case of selecting the wrong Table at any time in your testing (although this should be done in a different server...). If you needed the original Ids too, you can perform the above, not move the Ids around, rename the Columns etc. - as you wish really.
/*
Create copy of parent/lookup Table with its own Id column
Add a column to hold the original Ids
*/
CREATE TABLE [dbo].[AnimalTypeBak](
[Id] [uniqueidentifier] ROWGUIDCOL NOT NULL CONSTRAINT [DF_AnimalTypeBak_Id] DEFAULT (newid()),
[OriginalId] [uniqueidentifier] NOT NULL,
[AnimalType] [nvarchar](32) NOT NULL,
CONSTRAINT [PK_AnimalTypeBak] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
) ON [PRIMARY]
GO
/*
Create copy of child Table
Add a column to hold the original Foreign Key values
*/
CREATE TABLE [dbo].[AnimalBak](
[AnimalName] [nvarchar](20) NOT NULL,
[OriginalAnimalType] [uniqueidentifier] NOT NULL,
[AnimalType] [uniqueidentifier] NOT NULL
) ON [PRIMARY]
GO
/*
Import data from the parent/lookup Table placing the origional Ids into the added Column
*/
INSERT INTO [dbo].[AnimalTypeBak]
([OriginalId]
,[AnimalType])
SELECT [Id], [AnimalType]
FROM [dbo].[AnimalType]
GO
/*
Import data from the child Table placing the origional Foreign Key values into the added Column
*/
INSERT INTO [dbo].[AnimalBak]
([OriginalAnimalType]
,[AnimalName])
SELECT [AnimalType], [AnimalName]
FROM [dbo].[Animal]
GO
/*
Update the child Table placing the new parent/lookup Ids into the Foreign Key Column
*/
UPDATE [dbo].[AnimalBak]
SET [dbo].[AnimalBak].[AnimalType] = [dbo].[AnimalTypeBak].[Id]
FROM [dbo].[AnimalBak]
INNER JOIN [dbo].[AnimalTypeBak]
ON [dbo].[AnimalBak].[OriginalAnimalType] = [dbo].[AnimalTypeBak].[OriginalId]
GO
/*
Drop the redundant Columns
*/
ALTER TABLE [dbo].[AnimalBak]
DROP COLUMN [OriginalAnimalType]
GO
ALTER TABLE [dbo].[AnimalTypeBak]
DROP COLUMN [OriginalId]
/*
Add the Foreign Key Contraint between the two Tables
*/
ALTER TABLE [dbo].[AnimalBak] WITH CHECK ADD CONSTRAINT [FK_AnimalBak_AnimalTypeBak] FOREIGN KEY([AnimalType])
REFERENCES [dbo].[AnimalTypeBak] ([Id])
GO
/*
And select the data to ensure the data is related as it was in the original Tables
*/
SELECT a.AnimalName, a.AnimalType, b.AnimalType FROM [dbo].[AnimalBak] as a INNER JOIN [dbo].[AnimalTypeBak] as b ON b.Id = a.AnimalType

declare #Parents table (Id uniqueidentifier, Name varchar(50));
declare #Children table (Id uniqueidentifier, ParentId uniqueidentifier, Name varchar(50));
declare #NewId uniqueidentifier = newid();
insert into #Parents values (#NewId, 'Original parent');
insert into #Children values (newid(), #NewId, 'First original child');
insert into #Children values (newid(), #NewId, 'Second original child');
declare #Ids table (CloneId uniqueidentifier, OriginalId uniqueidentifier);
merge #Parents as target
using (
select
CloneId = newid(),
OriginalId = Id,
Name = Name + ' (Cloned)'
from
#Parents
where
Id = #NewId
)
as source on source.CloneId = target.Id
when not matched by target then
insert (Id, Name)
values (source.CloneId, source.Name)
output
source.CloneId, source.OriginalId
into #Ids (CloneId, OriginalId);
merge #Children as target
using (
select
Id = newid(),
ParentId = ids.CloneId,
Name = Name + ' (Cloned)'
from
#Children c
inner join #Ids ids on ids.OriginalId = c.ParentId
)
as source on source.Id = target.Id
when not matched by target then
insert (Id, ParentId, Name)
values (source.Id, source.ParentId, source.Name);
select * from #Parents
select * from #Children

Related

Adding AUTO_INCREMENT

I have a table where I've merged 2 tables into one.
One of the tables had an ID (primary key).
Now I got a merged table where some of the ID is 0.
I now try to restore and fill out the 0 with AUTO_INCREMENT so I
get a table with unique numbers (and not lose the one already there )
Someone got a god solution here ?
Firstly, the fact that you have a bunch of 0's in the table implies 2 additional problems:
The "ID" column is not a Primary Key or dose not have a Unique Index on it; meaning that duplicates were inserted
The column is (likely) no longer an IDENTITY.
Firstly, You'll need to get the new values in there. This can be done with an updatable CTE, with ROW_NUMBER and a windowed MAX
First some sample data:
CREATE TABLE dbo.TestTable (ID int NOT NULL);
INSERT INTO dbo.TestTable (ID)
VALUES(1),(2),(3),(0),(0),(0);
And now to UPDATE the rows with 0:
WITH RNs AS(
SELECT ID,
ROW_NUMBER() OVER (PARTITION BY CASE ID WHEN 0 THEN 0 ELSE 1 END ORDER BY (SELECT NULL)) + --If you have a way of determining the order, change the ORDER BY
MAX(ID) OVER () AS [NewID]
FROM dbo.TestTable)
UPDATE RNs
SET ID = [NewID]
WHERE ID = 0;
Now we (probably) need to fix the table and get the IDENTITY column in there. You can't change a column to an IDENTITY, so we'll need to create a new one and ensure it follows the value of the existing ID.
First, therefore, we need to add a CLUSTERED index to the table, so that the new IDENTITY will use that to generate its value:
ALTER TABLE dbo.TestTable ADD CONSTRAINT PK_TestTable PRIMARY KEY CLUSTERED (ID);
Now we can add the new IDENTITY column:
ALTER TABLE dbo.TestTable ADD IdentityID int IDENTITY NOT NULL;
Then we need to DROP the Primary Key we just created, and then the old column:
ALTER TABLE dbo.TestTable DROP CONSTRAINT PK_TestTable ;
ALTER TABLE dbo.TestTable DROP COLUMN ID;
And then, finally, we can rename the new column, and then recreate the Primary Key:
EXEC sys.sp_rename N'dbo.TestTable.IdentityID','ID','COLUMN';
GO
ALTER TABLE dbo.TestTable ADD CONSTRAINT PK_TestTable PRIMARY KEY CLUSTERED (ID);

How to move data from one table to another and update foreign keys (T-SQL 2008)

I have two tables Blobs and FileContents which store file contents.
Both tables have identical structure: Id int IDENTITY(1,1), Content varbinary(max) NULL,
Both table have data in it.
I have several other tables which have foreign keys to these tables.
For example table MxdFiles has field BlobId which is foreign key to Id field of Blobs table.
Now I need to move all records from Blobs to FileContents and update MxdFiles table with new Ids
Please advise how this can be achieved or provide any links to read.
Try something like this:
CREATE DATABASE TestDB
GO
USE TestDB;
GO
CREATE TABLE t1(ID INT IDENTITY PRIMARY KEY,SomeContent VARCHAR(100));
CREATE TABLE t2(ID INT IDENTITY PRIMARY KEY,SomeContent VARCHAR(100));
INSERT INTO t1 VALUES('Test1a'),('Test1b');
INSERT INTO t2 VALUES('Test2a'),('Test2b');
CREATE TABLE Referenced(ID INT IDENTITY, t1_ID INT CONSTRAINT fkTest FOREIGN KEY REFERENCES t1(ID));
INSERT INTO Referenced VALUES(1),(2); --Now both entries in t1 are referenced
--Want to shift all entries from t1 into t2:
ALTER TABLE t2 ADD OldID INT;
INSERT INTO t2
SELECT t1.SomeContent,t1.ID FROM t1;
SELECT * FROM t2;
--The result: imported rows have an OldID
ID SomeContent OldID
1 Test2a NULL
2 Test2b NULL
3 Test1a 1
4 Test1b 2
--Switch the values:
--Drop the constraint
ALTER TABLE Referenced DROP CONSTRAINT fkTest;
--Switch the references
WITH updateableCTE aS
(
SELECT r.t1_ID AS OldValue
,t2.ID AS NewValue
FROM Referenced AS r
INNER JOIN t2 ON r.t1_ID=t2.OldId
)
UPDATE updateableCTE SET OldValue=NewValue;
--Re-create the FK
ALTER TABLE Referenced ADD CONSTRAINT fkTest FOREIGN KEY (t1_ID) REFERENCES t2(ID);
SELECT * FROM Referenced;
GO
USE master;
GO
DROP DATABASE TestDB;

How to set "auto insert" foreign key value by using SQL Server?

I have created two tables and also created a relationship between them.
Table students:
create table students
(
[StudentId] NVARCHAR(50) NOT NULL PRIMARY KEY,
[Name] NVARCHAR(50) NOT NULL
);
Table studentprofile:
create table studentprofile
(
[Id] INT NOT NULL PRIMARY KEY IDENTITY(1, 1),
[StudentId] NVARCHAR(50) NOT NULL,
[Address] NVARCHAR(50) NOT NULL,
);
and relationship:
alter table studentprofile
add constraint students_studentprofile_FK
foreign key (StudentId)
references students(StudentId)
on delete cascade on update cascade
But, when I wrote this line:
insert into students values('110111', 'Marik')
the value of StudentId (in table studentprofile) wasn't updated automatically. Why?
Can you tell me how to set the value of StudentId (in table studentprofile) can be inserted automatically whenever I insert into table students?
There is no such thing as insert cascade.
You can implement such a thing by using a trigger for insert on your students table, inserting default values (or nulls) into the studentprofile table:
CREATE TRIGGER students_insert ON students AFTER INSERT
AS
INSERT INTO studentprofile(StudentId, Address)
SELECT StudentId, 'NO ADDRESS'
FROM inserted
Note that your Address column is defined as not null and has no default value, this is why I've used the hard coded 'NO ADDRESS' for it.
However, I agree with the comments on your question: you would be better off inserting the data to the student profile using a different insert statement (perhaps inside a transaction with the insert to students).

Associate Document with all Candidates in Candidate_Document table

CREATE TABLE [CandidateDocsAssociation](
[Row_ID] [bigint] IDENTITY(1,1) NOT NULL,
[Doc_ID] [bigint] NOT NULL,
[Candidate_ID] [bigint] NOT NULL,
) ON [PRIMARY]
GO
I have the above table structure to store the association between documents and candidates. Row_ID is an auto generated primary key. Doc_ID is a foreign key referencing the documents table. Candidate_ID is also a foreign key referencing the Candidates table.
A candidate can be associated with more than one document and one document can be associated with multiple candidates.
What i want to achieve is insert a default common document (Doc_ID) for all candidates(DISTINCT) if a Candidate_ID row with a DOC_ID of 2 does not already exist.
I'm not getting the below code to work
WHILE EXISTS (SELECT DISTINCT Candidate_ID from CandidateDocsAssociation
WHERE Doc_ID <> (SELECT Doc_ID FROM Doc_Table WHERE Doc_Name = N'Default'))
BEGIN
INSERT CandidateDocsAssociation (Doc_ID, Candidate_ID) VALUES ((SELECT Doc_ID FROM Doc_Table WHERE Doc_Name = N'Default'),Candidate_ID)
END
GO
Forget about while-loop, SQL is about set manipulation so you must adjust your thinking to it. What you need is set of candidates that do not have document with id=2 associated. For each such candidate you need to create an association with the default document. Let's try to write that down:
INSERT INTO [CandidateDocsAssociation] VALUES([Candidate_ID], [Doc_ID])
SELECT [Candidate_ID], 2
FROM [Candidate] c -- I assume you have table of all candidates
WHERE NOT EXISTS (
SELECT * FROM [CandidateDocsAssociation] a
WHERE a.[Candidate_ID] = c.[Candidate_ID] AND a.[Doc_ID] = 2
)

Reordering Identity primary key in sql server

Yes i am very well aware the consequences. But i just want to reorder them. Start from 1 to end.
How do I go about reordering the keys using a single query ?
It is clustered primary key index
Reordering like
First record Id 1
second record Id 2
The primary key is Int
Drop PK constraint
Drop Identity column
Re-create Identity Column
Re-Create PK
USE Test
go
if(object_id('IdentityTest') Is not null)
drop table IdentityTest
create table IdentityTest
(
Id int identity not null,
Name varchar(5),
constraint pk primary key (Id)
)
set identity_insert dbo.IdentityTest ON
insert into dbo.IdentityTest (Id,Name) Values(23,'A'),(26,'B'),(34,'C'),(35,'D'),(40,'E')
set identity_insert dbo.IdentityTest OFF
select * from IdentityTest
------------------1. Drop PK constraint ------------------------------------
ALTER TABLE [dbo].[IdentityTest] DROP CONSTRAINT [pk]
GO
------------------2. Drop Identity column -----------------------------------
ALTER table dbo.IdentityTest
drop column Id
------------------3. Re-create Identity Column -----------------------------------
ALTER table dbo.IdentityTest
add Id int identity(1,1)
-------------------4. Re-Create PK-----------------------
ALTER TABLE [dbo].[IdentityTest] ADD CONSTRAINT [pk] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
--------------------------------------------------------------
insert into dbo.IdentityTest (Name) Values('F')
select * from IdentityTest
IDENTITY columns are not updatable irrespective of SET IDENTITY_INSERT options.
You could create a shadow table with the same definition as the original except for the IDENTITY property. Switch into that (this is a metadata only change with no movement of rows that just affects the table's definition) then update the rows and switch back though.
A full worked example going from a situation with gaps to no gaps is shown below (error handling and transactions are omitted below for brevity).
Demo Scenario
/*Your original table*/
CREATE TABLE YourTable
(
Id INT IDENTITY PRIMARY KEY,
OtherColumns CHAR(100) NULL
)
/*Some dummy data*/
INSERT INTO YourTable (OtherColumns) VALUES ('A'),('B'),('C')
/*Delete a row leaving a gap*/
DELETE FROM YourTable WHERE Id =2
/*Verify there is a gap*/
SELECT *
FROM YourTable
Remove Gaps
/*Create table with same definition as original but no `IDENTITY`*/
CREATE TABLE ShadowTable
(
Id INT PRIMARY KEY,
OtherColumns CHAR(100)
)
/*1st metadata switch*/
ALTER TABLE YourTable SWITCH TO ShadowTable;
/*Do the update*/
WITH CTE AS
(
SELECT *,
ROW_NUMBER() OVER (ORDER BY Id) AS RN
FROM ShadowTable
)
UPDATE CTE SET Id = RN
/*Metadata switch back to restore IDENTITY property*/
ALTER TABLE ShadowTable SWITCH TO YourTable;
/*Remove unneeded table*/
DROP TABLE ShadowTable;
/*No Gaps*/
SELECT *
FROM YourTable
I don't think there is any way to do this in a single query. Your best bet is to copy the data to a new table, drop and recreate the original table (or delete the data and reseed the identity) and reinsert the data in the original order using the previous identity as the ordering (but not re-inserting it).
CREATE TABLE Table1_Stg (bla bla bla)
INSERT INTO Table1_Stg (Column2, Column3,...) SELECT Column2, Column3,... FROM Table1 ORDER BY Id
Here the Id column is excluded from the SELECT column list.
Or, you can do:
SELECT * INTO Table1_Stg FROM Table1 ORDER BY Id
DROP Table1
sp_rename Table1_stg Table1
Please lookup the usage for sp_rename as I am doing this from memory.
Hope this helps.
EDIT: Please save a script with all your indexes and constraints if any on Table1.
EDIT2: Added second method of creating table and inserting into table.
UPDATE tbl SET id = (SELECT COUNT(*) FROM tbl t WHERE t.id <= tbl.id);
This last statement is genius. Just had to remove the primary key from the table design first and make sure under the design option Identity Specifications is set to no. Once you run the query set these options back.

Resources