1

When a user deletes a row in the database, I want to archive it into a separate table in the database rather than delete it or flag it in the current table. I figure I would need to do something like in this link:

How to copy a row from one SQL Server table to another

The thing is, the archive table has 1 extra column in it that does not match the original table (ArchiveTimeStamp). This ArchiveTimeStamp does not exist in the original table, instead I would use something like

archiveComm.Parameters.AddWithValue("ArchiveTimeStamp", Date.Time.Now);

This is what I have so far:

 SqlCommand archiveComm = new SqlCommand("INSERT INTO Archive_Table SELECT * FROM Table WHERE RowID = @rowID", conn);

Is there a way for me to modify the SqlCommand to add another param that doesn't exist in the original Table?

Community
  • 1
  • 1
Kevin
  • 3,209
  • 9
  • 39
  • 53

6 Answers6

2

Why not just handle this on the back end? You can create a trigger on the original table to insert into another table after every delete? Your trigger will look like this:

CREATE TRIGGER onOriginalTableDelete
ON originalTable
FOR DELETE
AS 
INSERT INTO anotherTable
SELECT * FROM deleted;

When a record is deleted on the original table, it will insert the deleted record into the other table. You might want to read on using the deleted table here.

Check this SQL Fiddle. Since you're inserting the timestamp in another column, you can just add this on the INSERT INTO SELECT statement:

INSERT INTO OtherTable
SELECT *, CURRENT_TIMESTAMP FROM MainTable;

This could be the query for your trigger:

CREATE TRIGGER onOriginalTableDelete
ON originalTable
FOR DELETE
AS 

INSERT INTO anotherTable
SELECT *, CURRENT_TIMESTAMP FROM deleted;
whastupduck
  • 1,156
  • 11
  • 25
  • This would be a stored procedure right? That makes a lot of sense actually to call the stored procedure. But again with a additional column in the other table (the time stamp), the SELECT * FROM wouldn't work right? – Kevin Jul 12 '13 at 03:10
  • 1
    I'm not a fan of triggers, but this seems like a really good use for one. – Matthew Jul 12 '13 at 03:11
  • No this is not a stored procedure, it is a trigger. You don't have to call this, it will be automatic in the database when you delete a record from the table. – whastupduck Jul 12 '13 at 03:12
  • Just to make sure I understand, in the JSFiddle, when you do SELECT *, CURRENT_TIMESTAMP FROM Main, where is the timestamp coming from? Is it just the time stamp of when the query is ran? The FROM Main part is throwing me off. Is it the timestamp of when the deletion occured? – Kevin Jul 12 '13 at 03:31
  • the CURRENT_TIMESTAMP "returns the current database system timestamp" as per MSDN documentation. So yes that would return the timestamp when the record was deleted. – whastupduck Jul 12 '13 at 03:34
  • It selects all the columns of the deleted row and adds the addition column of the CURRENT_TIMESTAMP to be inserted to the other table. – whastupduck Jul 12 '13 at 03:37
  • Awesome thanks! One last question - if I am using DELETED.Col1, will I ever potentially run into a problem is somehow 2 rows are simultaneously deleted at the same time? – Kevin Jul 16 '13 at 01:32
  • Oh sorry was that as "yes" as in yes, you can run into potential problems if two rows are deleted simultaneously or a "yes" as in yes the DELETED.Col1 will not mess up if two rows are deleted simultaneously? Like what if 2 rows are deleted simultaneously could DELETED.col1 come from 1 row and DELETED.col2 come from another? – Kevin Jul 19 '13 at 01:25
2

Good question. I'd suggest (as Gian has also suggested) moving the logic you require to backup the deleted row into a trigger that gets fired on delete.

Triggers are events in a database associated to a table which get fired upon an action occurring i.e. insert / update / delete.

So in your scenario, if you create an ON DELETE trigger in the source table, it will get fired when a delete occurs. The SQL contained within the trigger can specify what to do with the deleted data, which in your scenario will be: insert the deleted info into the archive table with a timestamp.

So if you have:

Source_Table:
Col_1
Col_2
Col_3

Archive_Table:
Col_1
Col_2
Col_3
Time_Stamp

You'll need to create a FOR DELETE trigger against Source_Table (something like this):

CREATE TRIGGER SourceDeletedTrigger
ON database.dbo.Source_Table
FOR DELETE
AS

INSERT INTO Archive_Table(Col_1, Col_2, Col_3, Time_Stamp)
  SELECT
    DELETED.Col_1,
    DELETED.Col_2,
    DELETED.Col_3,
    GETDATE()
  FROM DELETED

GO

The above is some rough SQL which may contain a couple of syntax errors but the guts of the idea is conveyed.

Michael Petrotta
  • 59,888
  • 27
  • 145
  • 179
  • No worries. It's always good to put this kind of logic into the database layer as it should be app independent where possible. – Taz Hussein Jul 16 '13 at 05:07
1

You will have to use to explicit column list and values form of the INSERT statement:

INSERT INTO Archive_Table (
     Col1
    ,Col2
    ,Col3 )
SELECT
     Col1
    ,Col2
    ,Col3
FROM
   Table
WHERE
   Row_ID = @Row_ID

See Insert into ... values ( SELECT ... FROM ... )

Community
  • 1
  • 1
Keith Payne
  • 3,002
  • 16
  • 30
  • I get this part but I'm not sure where I would put the ArchiveTimeStamp, which is a column that is only in the archive_Table and not in the original table. – Kevin Jul 12 '13 at 03:20
1

I think you have to specify the columns with something like this

INSERT INTO tab1
(col1, col2)
SELECT col1, col2
FROM tab2
WHERE  RowID = @rowID"
COLD TOLD
  • 13,513
  • 3
  • 35
  • 52
1

You need to specify the columns name in that case:

 archiveComm.Parameters.AddWithValue("ArchiveTimeStamp", Date.Time.Now);
 string SQL = "INSERT INTO Archive_Table (Col1,Col2,ArchiveTimeStamp) " & _
              "SELECT Col1,Col2,@ArchiveTimeStamp FROM Table WHERE RowID = @rowID"
 SqlCommand archiveComm = new SqlCommand(SQL, conn);
NeverHopeless
  • 11,077
  • 4
  • 35
  • 56
  • Does this work if the ArchiveTimeStamp does not exist in the original Table? I editted my question to make it clearer. I want to use something like Date.Time.Now for the ArchiveTimeStamp (Basically a time stamp of when the archive row was created) – Kevin Jul 12 '13 at 02:26
  • In that case you can pass value to the query in the same way as you did for @rowID. Currently solution records the time when the record inserted in DB. – NeverHopeless Jul 12 '13 at 02:28
  • This syntax will be wrong, but is there some way to do it like so: SqlCommand archiveComm = new SqlCommand("INSERT INTO Archive_Table(Col1, Col2, ArchiveTimeStamp) VALUES (SELECT * FROM Table WHERE RowID = @rowID, @timeStamp)", conn); – Kevin Jul 12 '13 at 02:31
  • And then archiveComm.Parameters.AddWithValue("timeStamp", Date.Time.Now); – Kevin Jul 12 '13 at 02:31
  • You can check the updates, the modification is I have changed the value of third column to the parameter and add the parameter in SqlCommand. – NeverHopeless Jul 12 '13 at 02:42
0

Here is my suggestion, you are forced to supply the column names or it won't let you run the query, however I understand you would prefer a generic solution that worked for any table so I suggest building the insert SQL dynamically, cache it on your application, and then just execute it with your extra archive column. Here is a c# example:

public class ArchiveTableRow
{
    private static readonly IDictionary<string, string> _cachedInsertStatements = new Dictionary<string, string>();

    public void Archive(string tableName, string rowId)
    {   
        if (_cachedInsertStatements.ContainsKey(tableName) == false)
        {
            BuildInsertStatement(tableName);
        }

        var insertQuery = _cachedInsertStatements[tableName];

        // ...      
        SqlCommand archiveComm = new SqlCommand(insertQuery, conn);
        // ...      
        archiveComm.Parameters.AddWithValue("ArchiveTimeStamp", Date.Time.Now);     
        // ...      
    }

    private void BuildInsertStatement(string tableName)
    {
        // Get the columns names:
        var getColumnNamesQuery = @"
            SELECT Table_Schema, Column_Name
            FROM Information_Schema.Columns
            WHERE Table_Name = '" + tableName + "'
            Order By Ordinal_Position";

        // Execute the query
        SqlCommand archiveComm = new SqlCommand(getColumnNamesQuery, conn);

        // Loop and build query and add your archive in the end

        // Add to dictionary    
    }   
}

You would use it with something like:

var archiveRow = new ArchiveTableRow();

archiveRow.Archive("TableName", rowId);