SQL MOVE Records to another table - sql

I have a number of functions which MOVE records from one table to another (generally for a form of archiving the data) and wondered if there was a "best practice" for doing this, or a more efficient method than I am currently using.
At the moment, I am running something like:
INSERT INTO archive_table
SELECT [ROWID], [COL1], [COL2]
FROM live_table
WHERE <criteria>
DELETE FROM live_table
WHERE [ROWID] IN
(
SELECT [ROWID] FROM archive_table
)
This is also throwing up a warning on the SQL performance software that the query may cause index suppression and performance degradation; due to a SCAN being performed, rather than a SEEK.
Worth adding that the archive_table is an exact copy of the live_table, with the exception that we have removed the identity and primary key off of the [ROWID] column and that this table is not used within the 'live' environment, other than having the old data inserted, as described.
[edit]
Would seem that the answer from Alex provides a really simple resolution to this; the comment about using a trigger doesn't resolve the issue in this instance as the event happens a number of days later and the criteria is dependant on events during that period.
DELETE
FROM live_table
OUTPUT DELETED.* INTO archive_table
WHERE <criteria>

If you have to move large number of records from one table to another, i suggest you check the possibility to partition your "active table". Each time, you copy data from one (or more) partitions to the "achieve table" and drop those partitions. It will be much faster than delete records from an "online" table.

Worth adding that the archive_table is an exact copy of the live_table, with the exception that we have removed the identity and primary key off of the [ROWID] column and that this table is not used within the 'live' environment, other than having the old data inserted, as described.
I can't tell if the reason you are removing the primary key from the archive_table is because you expect the ROWID's to be re-used in the live_table or not.
If I'm understanding the context of your data correctly and that you want to archive days after the data is completed, you can improve the performance of the query by reducing/eliminating the comparison of rows that will not exist in the live_table. Basically, once a ROWID has migrated from live_table to archive_table, there is no reason to look for it again.
Note: This assumes that ROWID's are not re-used in the live_table and are always increasing numbers.
INSERT INTO archive_table
SELECT [ROWID], [COL1], [COL2]
FROM live_table
WHERE <criteria>
DELETE FROM live_table
WHERE [ROWID] IN
(
SELECT [ROWID] FROM archive_table WHERE [ROWID] >= (SELECT MIN(ROWID) FROM live_table)
)
If ROWID's are re-used. If you have a datetime field in your data set that is close to when the record was live or archived it can be used as an alternative to the ROWID. This would mean you are only looking for recently archived rows to delete from the live_table, instead of the entire set. Also, making [somedate] the clustered index on the archive_table could improve performance as the data would be physically ordered to where you are only looking at the tail of the table.
INSERT INTO archive_table
SELECT [ROWID], [COL1], [COL2]
FROM live_table
WHERE <criteria>
DELETE FROM live_table
WHERE [ROWID] IN
(
SELECT [ROWID] FROM archive_table WHERE [somedate] >= DATEADD(dy,-30,GETDATE())
)

Your code snippet does not include a named transaction which MUST be the first consideration. Second design a table variable, temp table or hard table to use as for staging. The designed table should include a column identical in datatype to the identity column from your source table and that column should be indexed. Third design your TSQL to populate the staging table, copy rows from source table to destination table based on a join between the source and staging then remove rows from the source table based on the same join that moved data to the destination table. Below is a working sample
--test setup below
DECLARE #live_table table (rowid int identity (1,1) primary key clustered, col1 varchar(1), col2 varchar(2))
DECLARE #archive_table table (rowid int, col1 varchar(1), col2 varchar(2))
Insert #live_table (col1, col2)
Values
('a','a'),
('a','a'),
('a','a'),
('a','a'),
('b','b')
--test setup above
BEGIN Transaction MoveData
DECLARE #Staging table (ROWID int primary Key)
Insert #Staging
SELECT lt.rowid
FROM #live_table as lt
WHERE lt.col1 = 'a'
INSERT INTO #archive_table
select lt.rowid, lt.col1, lt.col2
FROM #live_table as lt
inner join #Staging as s on lt.rowid = s.ROWID
DELETE #live_table
FROM #live_table as lt
inner join #Staging as s on lt.rowid = s.ROWID
COMMIT Transaction MoveData
select * from #live_table
select * from #archive_table
select * from #Staging

You can use triggers for replace any CRUD Command
Add Trigger to your table for after delete operation
CREATE TRIGGER TRG_MoveToDeletedStaff ON Staff
after delete
as
DECLARE
#staffID int,
#staffTC varchar(11),
#staffName varchar(50),
#staffSurname varchar(50),
#staffbirthDate date,
#staffGSM varchar(11),
#staffstartDate date,
#staffEndDate date,
#staffCategory int,
#staffUsername varchar(20),
#staffPassword varchar(20),
#staffState int,
#staffEmail varchar(50)
set #staffEndDate=GETDATE()
SET #staffState=0
SELECT #staffID=staffID,
#staffTC=staffTC,
#staffName=staffName,
#staffSurname=staffSurname,
#staffbirthDate=staffbirthDate,
#staffGSM=staffGSM,
#staffstartDate=staffstartDate,
#staffCategory=staffCategory,
#staffUsername=staffuserName,
#staffPassword=staffPassword,
#staffEmail=staffeMail
from deleted
INSERT INTO DeletedStaff values
(
#staffID,
#staffTC,
#staffName,
#staffSurname,
#staffbirthDate,
#staffGSM,
#staffstartDate,
#staffEndDate,
#staffCategory,
#staffUsername,
#staffPassword,
#staffState,
#staffEmail
)

Related

How we can add column in sql server with auto generated record insertion number. if Index are defined on that table

My database(SQL server 2008) already having some records and having two sorting index on two columns. I tried to add identity column using following query. But it providing me wrong identity numbers associated with records.
alter table Table_name add RECORD_NUMBER int identity(1,1)
record which inserted first having higher number and record which is inserted last having lower number. and some records are having just opposite numbers. Is it possible to remove such kind of problem and insert right insertion order for records.
I did this because i was not having any primary key or unique or compost key in my table. So to identify which record is first entered, i introduce identity column. I was thinking that it will allocate record number according to insertion order.
If it's possible for you to move data in a new table then this is an approach which is basically recommended by Microsoft:
/*************
Preparing for the example, create tables, insert sample data,...
**************/
-- Your existing table
CREATE TABLE dbo.oldtable
(
colA int,
colB nvarchar(10)
)
-- The new table, same structure + new identity column:
CREATE TABLE dbo.newtable
(
colID int IDENTITY(1,1),
colA int,
colB nvarchar(10)
)
--create unique clustered index on new identity column
create unique clustered index ucx_newtable
on dbo.newtable (colID ASC)
--create other indexes as you need them
create index idx_newtable
on dbo.newtable (colA ASC)
--sample data
insert into dbo.oldtable(colA,colB)
values (16,'this'),(17,'is'),(225,'an'),(300,'example')
/*************
Data Movement
**************/
BEGIN TRANSACTION
BEGIN TRY
--move data to new table including new id column which is generated by
--colA, which gives us the order from old to new in this example
SET IDENTITY_INSERT dbo.newtable ON --So we can insert into the colID identity column
INSERT INTO dbo.newtable(colID, colA,colB)
SELECT ROW_NUMBER() OVER(ORDER BY colA ASC) AS colID --this will generate numbers beginning from 1
,colA, colB
FROM dbo.oldtable
SET IDENTITY_INSERT dbo.newtable OFF
--rename old table / or drop it if you want
EXEC sp_rename #objname = 'dbo.oldtable', #newname = 'dbo.oldtable_xyz'
EXEC sp_rename #objname = 'dbo.newtable', #newname = 'dbo.oldtable'
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
ROLLBACK TRANSACTION
END CATCH

Duplicating parent, child and grandchild records

I have a parent table that represents a document of-sorts, with each record in the table having n children records in a child table. Each child record can have n grandchild records. These records are in a published state. When the user wants to modify a published document, we need to clone the parent and all of its children and grandchildren.
The table structure looks like this:
Parent
CREATE TABLE [ql].[Quantlist] (
[QuantlistId] INT IDENTITY (1, 1) NOT NULL,
[StateId] INT NOT NULL,
[Title] VARCHAR (500) NOT NULL,
CONSTRAINT [PK_Quantlist] PRIMARY KEY CLUSTERED ([QuantlistId] ASC),
CONSTRAINT [FK_Quantlist_State] FOREIGN KEY ([StateId]) REFERENCES [ql].[State] ([StateId])
);
Child
CREATE TABLE [ql].[QuantlistAttribute]
(
[QuantlistAttributeId] INT IDENTITY (1, 1),
[QuantlistId] INT NOT NULL,
[Narrative] VARCHAR (500) NOT NULL,
CONSTRAINT [PK_QuantlistAttribute] PRIMARY KEY ([QuantlistAttributeId]),
CONSTRAINT [FK_QuantlistAttribute_QuantlistId] FOREIGN KEY ([QuantlistId]) REFERENCES [ql].[Quantlist]([QuantlistId]),
)
Grandchild
CREATE TABLE [ql].[AttributeReference]
(
[AttributeReferenceId] INT IDENTITY (1, 1),
[QuantlistAttributeId] INT NOT NULL,
[Reference] VARCHAR (250) NOT NULL,
CONSTRAINT [PK_QuantlistReference] PRIMARY KEY ([AttributeReferenceId]),
CONSTRAINT [FK_QuantlistReference_QuantlistAttribute] FOREIGN KEY ([QuantlistAttributeId]) REFERENCES [ql].[QuantlistAttribute]([QuantlistAttributeId]),
)
In my stored procedure, i pass in the QuantlistId I want to clone as #QuantlistId. Since the QuantlistAttribute table has a ForeignKey I can easily clone that as well.
INSERT INTO [ql].[Quantlist] (
[StateId],
[Title],
) SELECT
1,
Title,
FROM [ql].[Quantlist]
WHERE QuantlistId = #QuantlistId
SET #ClonedId = SCOPE_IDENTITY()
INSERT INTO ql.QuantlistAttribute(
QuantlistId
,Narrative)
SELECT
#ClonedId,
Narrative,
FROM ql.QuantlistAttribute
WHERE QuantlistId = #QuantlistId
The trouble comes down to the AttributeReference. If I cloned 30 QuantlistAttribute records, how do I clone the records in the reference table and match them up with the new records I just inserted in to the QuantlistAttribute table?
INSERT INTO ql.AttributeReference(
QuantlistAttributeId,
Reference,)
SELECT
QuantlistAttributeId,
Reference,
FROM ql.QuantlistReference
WHERE ??? I don't have a key to go off of for this.
I thought I could do this with some temporary linking tables that holds the old attribute id's along with the new attribute id's. I don't know how to go about inserting the old Attribute Id's in to a temp table along with their new ones. Inserting the existing Attributes, by QuantlistId, is easy enough, but I can't figure out how to make sure I link the correct new and old Id's together in some way, so that the AttributeReference table can be cloned right. If I could get the QuantlistAttribute new and old Id's linked, I could join on that temp table and figure out how to restore the relationship of the newly cloned references, to the newly cloned attributes.
Any help on this would be awesome. I've spent the last day and a half trying to figure this out with no luck :/
Please excuse some of the SQL inconsistencies. I re-wrote up the sql real quick, trimming out a lot of additional columns, related-tables and constraints that weren't needed for this question.
Edit
After doing a little digging around, I found that OUTPUT might be useful for this. Is there a way to use OUTPUT to map the QuantlistAttributeId records I just inserted, to the QuantlistAttributeId they originated from?
You can use OUTPUT to get the inserted rows.
You can insert the data into QuantlistAttribute based on the order of ORDER BY c.QuantlistAttributeId ASC
Have a temp table/table variable which 3 columns
an id identity column
new QuantlistAttributeId
old QuantlistAttributeId.
Use OUTPUT to insert new identity values of QuantlistAttribute into a temp table/table variable.
The new IDs are generated in the same order as c.QuantlistAttributeId
Use a row_number() ordered by QuantlistAttributeId to match the old QuantlistAttributeId and new QuantlistAttributeIds based on row_number() and id of the table variable and update the values or old QuantlistAttributeId in the table variable
Use the temp table and join with AttributeReference and insert records in one go.
Note:
ORDER BY during INSERT INTO SELECT and ROW_NUMBER() to get matching old QuantlistAttributeId is required because looking at your question, there seems to be no other logical key to map old and new records together.
Query for above Steps
DECLARE #ClonedId INT,#QuantlistId INT = 0
INSERT INTO [ql].[Quantlist] (
[StateId],
[Title]
) SELECT
1,
Title
FROM [ql].[Quantlist]
WHERE QuantlistId = #QuantlistId
SET #ClonedId = SCOPE_IDENTITY()
--Define a table variable to store the new QuantlistAttributeID and use it to map with the Old QuantlistAttributeID
DECLARE #temp TABLE(id int identity(1,1), newAttrID INT,oldAttrID INT)
INSERT INTO ql.QuantlistAttribute(
QuantlistId
,Narrative)
--New QuantlistAttributeId are created in the same order as old QuantlistAttributeId because of ORDER BY
OUTPUT inserted.QuantlistAttributeId,NULL INTO #temp
SELECT
#ClonedId,
Narrative
FROM ql.QuantlistAttribute c
WHERE QuantlistId = #QuantlistId
--This is required to keep new ids generated in the same order as old
ORDER BY c.QuantlistAttributeId ASC
;WITH CTE AS
(
SELECT c.QuantlistAttributeId,
--Use ROW_NUMBER to get matching id which is same as the one generated in #temp
ROW_NUMBER()OVER(ORDER BY c.QuantlistAttributeId ASC) id
FROM ql.QuantlistAttribute c
WHERE QuantlistId = #QuantlistId
)
--Update the old value in #temp
UPDATE T
SET oldAttrID = CTE.QuantlistAttributeId
FROM #temp T
INNER JOIN CTE ON T.id = CTE.id
INSERT INTO ql.AttributeReference(
QuantlistAttributeId,
Reference)
SELECT
T.NewAttrID,
Reference
FROM ql.AttributeReference R
--Use OldAttrID to join with ql.AttributeReference and insert NewAttrID
INNER JOIN #temp T
ON T.oldAttrID = R.QuantlistAttributeId
Hope this helps.

add an identity column to existing table as the primary key and change order

I have a table that has over 7 million records in it. The table does not have a primary key. I would like to add a new identity column and set this as the primary key. I tried adding the column using SSMS, then I set it as the primary key. I called this new column Id.
This almost worked, however I wanted to change the default order of the table to be based another column, for instance a date time column in descending order. Is this possible? Perhaps I need to use a temp table and the ROW_NUMBER() function.
However, I am not very good at SQL. Can someone help?
I also need to have a rollback script so I can get back to the original table.
Here is one more idea:
Step1 - Create temporary clustered index on "date time column in descending order"
CREATE CLUSTERED INDEX ix_YourTableTEMP ON YourTable (DateTimeColumn DESC)
Step2 - Add identity column. Now the IDs should be in order of previously created index - although I don't think there is 100% guarantee on this.
ALTER TABLE YourTable ADD IdColumn INT IDENTITY(1,1)
Step3 - Drop temporary index
DROP INDEX ix_YourTableTEMP ON YourTable
Step4 - Create new clustered PK on new column
ALTER TABLE YourTable
ADD CONSTRAINT PK_YourTable PRIMARY KEY CLUSTERED (IdColumn)
The Link Martin Smith has given is probably the best answer, but here is an alternative:
-- CREATE TABLE WITH SOME DATA IN
CREATE TABLE T (X INT);
INSERT T VALUES (1), (2), (3);
-- CREATE A CLONE OF THIS TABLE, ADDING AN IDENTITY COLUMN
-- USING ORDER BY TO AFFECT THE ORDER OF THE INSERT
SELECT ID = IDENTITY(INT, 1, 1),
T.*
INTO T_Clone
FROM T
ORDER BY X DESC;
-- DROP ORIGINAL TABLE
DROP TABLE T;
-- RENAME CLONE TABLE TO ORIGINAL TABLE NAME
EXECUTE SP_RENAME 'dbo.T_Clone', 'T', 'OBJECT';
-- SELECT FROM TABLE TO CHECK RESULTS
SELECT *
FROM T;
To rollback:
ALTER TABLE T DROP COLUMN ID;
EDIT
It has been pointed out that SELECT ID = IDENTITY(INT, 1, 1).. INTO.. FROM .. ORDER BY ... does not guarantee the order of the insert. So it would appear that the fail safe option is to create your clone table using CREATE TABLE syntax and adding the IDENTITY column:
CREATE TABLE T_Clone
( ID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
<your columns>
);
INSERT T_Clone (<your columns>)
SELECT <your columns>
FROM T
ORDER BY ...;
Then carry on with the Drop and rename as above. I can find no documentation to say this method is not reliable for ordering the insert, if it proves to still not be reliable you could use:
SET IDENTITY_INSERT T_Clone ON;
INSERT T_Clone (ID, <your columns>)
SELECT ROW_NUMBER() OVER(ORDER BY ...),
<your columns>
FROM T;
SET IDENTITY_INSERT T_Clone OFF;
Then reseed T_CLone after the insert.

Trigger for insert on identity column

I have a table A with an Identity Column which is the primary key.
The primary key is at the same time a foreign key that points towards another table B.
I am trying to build an insert trigger that inserts into Table B the identity column that is about to be created in table A and another custom value for example '1'.
I tried using ##Identity but I keep getting a foreign key conflict. Thanks for your help.
create TRIGGER dbo.tr ON dbo.TableA FOR INSERT
AS
SET NOCOUNT ON
begin
insert into TableB
select ##identity, 1;
end
alexolb answered the question himself in the comments above. Another alternative is to use the IDENT_CURRENT function instead of selecting from the table. The drawback of this approach is that it always starts your number one higher than the seed, but that is easily remedied by setting the seed one unit lower. I think it feels better to use a function than a subquery.
For example:
CREATE TABLE [tbl_TiggeredTable](
[id] [int] identity(0,1) NOT NULL,
[other] [varchar](max)
)
CREATE TRIGGER [trgMyTrigger]
ON [tbl_TriggeredTable]
INSTEAD OF INSERT,UPDATE,DELETE
SET identity_insert tbl_TriggeredTable ON
INSERT INTO tbl_TriggeredTable (
[id],
[other]
)
SELECT
-- The identity column will have a zero in the insert table when
-- it has not been populated yet, so we need to figure it out manually
case i.[id]
when 0 then IDENT_CURRENT('tbl_TriggeredTable') + IDENT_INCR('tbl_TriggeredTable')
ELSE i.[id]
END,
i.[other],
FROM inserted i
SET identity_insert tbl_TriggeredTable OFF
END

How to add data to two tables linked via a foreign key?

If I were to have 2 tables, call them TableA and TableB. TableB contains a foreign key which refers to TableA. I now need to add data to both TableA and TableB for a given scenario. To do this I first have to insert data in TableA then find and retrieve TableA's last inserted primary key and use it as the foreign key value in TableB. I then insert values in TableB. This seems lika a bit to much of work just to insert 1 set of data. How else can I achieve this? If possible please provide me with SQL statements for SQL Server 2005.
That sounds about right. Note that you can use SCOPE_IDENTITY() on a per-row basis, or you can do set-based operations if you use the INSERT/OUTPUT syntax, and then join the the set of output from the first insert - for example, here we only have 1 INSERT (each) into the "real" tables:
/*DROP TABLE STAGE_A
DROP TABLE STAGE_B
DROP TABLE B
DROP TABLE A*/
SET NOCOUNT ON
CREATE TABLE STAGE_A (
CustomerKey varchar(10),
Name varchar(100))
CREATE TABLE STAGE_B (
CustomerKey varchar(10),
OrderNumber varchar(100))
CREATE TABLE A (
Id int NOT NULL IDENTITY(51,1) PRIMARY KEY,
CustomerKey varchar(10),
Name varchar(100))
CREATE TABLE B (
Id int NOT NULL IDENTITY(1123,1) PRIMARY KEY,
CustomerId int,
OrderNumber varchar(100))
ALTER TABLE B ADD FOREIGN KEY (CustomerId) REFERENCES A(Id);
INSERT STAGE_A VALUES ('foo', 'Foo Corp')
INSERT STAGE_A VALUES ('bar', 'Bar Industries')
INSERT STAGE_B VALUES ('foo', '12345')
INSERT STAGE_B VALUES ('foo', '23456')
INSERT STAGE_B VALUES ('bar', '34567')
DECLARE #CustMap TABLE (CustomerKey varchar(10), Id int NOT NULL)
INSERT A (CustomerKey, Name)
OUTPUT INSERTED.CustomerKey,INSERTED.Id INTO #CustMap
SELECT CustomerKey, Name
FROM STAGE_A
INSERT B (CustomerId, OrderNumber)
SELECT map.Id, b.OrderNumber
FROM STAGE_B b
INNER JOIN #CustMap map ON map.CustomerKey = b.CustomerKey
SELECT * FROM A
SELECT * FROM B
If you work directly with SQL you have the right solution.
In case you're performing the insert from code, you may have higher level structures that help you achieve this (LINQ, Django Models, etc).
If you are going to do this in direct SQL, I suggest creating a stored procedure that takes all of the data as parameters, then performs the insert/select identity/insert steps inside a transaction. Even though the process is still the same as your manual inserts, using the stored procedure will allow you to more easily use it from your code. As #Rax mentions, you may also be able to use an ORM to get similar functionality.