Copying row from 1 table to another in SQL - c#

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?

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;

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.

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 ... )

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"

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);

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);

Related

Working with composite primary key in c#

I have a sql table temp.it has 4 columns, 3 of which are primary keys(composite). While doing a insert to a table I need to check if the composite PK exists already in table. If yes I need to update the row else I need to insert a new row to temp table. Can I proceed this way. I don't know hoe to check for PK in table. Kindly guide me. Below is the Insert
string constr = ConfigurationManager.ConnectionStrings["constr"].ToString();
using (OdbcConnection con = new OdbcConnection(constr))
{
try
{
string query = "Insert into temp_table(Name,DeptName,Alias,City) values(name,dept,alias,city)";
con.Open();
OdbcCommand cmd = new OdbcCommand(query, con);
cmd.ExecuteNonQuery();
}
here name, dept and city are composite primary key.
Try to update first, if the record does not exists than the update will fail and then you can do an insert.
This is more efficient because each time the update succeeds then only one statement will be called.
update temp_table
set Alias = #alias
where Name = #name
and DeptName = #dept
if ##rowcount = 0 then
begin
insert into temp_table (Name, DeptName, Alias, City)
values (#name, #dept, #alias, #city)
end
your solution
if not exists (Select * from temp_table where Name=#name and DeptName=#dept and City=#city)
begin
Insert into temp_table
(Name,DeptName,Alias,City)
values(#name,#dept,#alias,#city)
end
else
begin
update temp_table set Alias=#alias where Name=#name and DeptName=#dept and City=#city
end
The Merge Command combine check, insert and update into one command.
Syntax is here:
https://msdn.microsoft.com/en-us/library/bb510625.aspx

SqlBulkCopy Ignore Duplicate Records of Datatable From DataBase

Can i ignore the duplicate records of a data already present in sql database from a datatable which i am passing to SqlBulkCopy. If Yes then How and also explain me if No and other option.
No, that's not built-in. You need to clean the data on the client or insert into a staging table first.
As previous poster said, this is not built in. I achieve similar using the following:
SQL Stored Procedure that accepts a TableValuedParameter with the data you require.
In the stored proc, I then INSERT all records into a temp table. Once you have it there, you can use SQLs MERGE statement in your stored proc to insert data where it doesn't already exist.
So, let us assume that our data is simply people's names stored in a table people. We hold only an ID and a name. I also assume this table is called 'people'.
Here's how I create my Table Valued Parameter type (created in SQL Server)
CREATE TYPE udt_person AS TABLE(
[id] [INT] NOT NULL,
[name] [nvarchar(50)] NULL
)
GO
I now create the stored procedure:
CREATE PROCEDURE SaveNewPeople #pPeople udt_Person
AS
BEGIN
-- Create Temp table
CREATE TABLE #tmpPeople (id INT, name VARCHAR 50)
-- We will stage all data passed in into temp table
INSERT INTO #tmpPeople
SELECT id, name FROM #pPeople
-- NB: you will need to think about locking strategy a bit here
MERGE people AS p
USING #tmpPeople AS t
ON p.id = t.id
WHEN NOT MATCHED BY TARGET THEN
-- We want to insert new person
INSERT (id, name) VALUES (t.id, t.name)
WHEN MATCHED THEN
-- you may not need this, assume updating name for example
UPDATE SET p.name = t.name
END
Now we have the SQL in place.
Let us create the bulk of data in C#:
DataTable ppl = new DataTable();
ppl.Columns.Add("id", typeof(int));
ppl.Columns.Add("name", typeof(string));
// table is created, let's add some people
var bob = ppl.NewRow();
bob["id"] = 1;
bob["name"] = "Bob";
ppl.Rows.Add(bob);
var jim = ppl.NewRow();
jim["id"] = 2;
jim["name"] = "Jim";
ppl.Rows.Add(jim);
// that's enough people for now, let's call the stored procedure
using(var conn = new SqlConnection("YouConnStringHere"))
{
using(var cmd = new SqlCommand("SaveNewPeople", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
var tvp = new SqlParameter
{
ParameterName = "#pPeople",
SqlDbType = SqlDbType.Structured,
Value = ppl,
TypeName = "udt_person"
}
cmd.Parameters.Add(tvp);
conn.Open();
cmd.ExecuteNonQuery();
}
}
Hopefully this gives you the idea. If you then modified the C# datatable, you should see rows inserted, updated or ignored.
Good luck.
Another way to do it is to create a database trigger to replace the inserts initiated by SqlBulkCopy. The performance will be impeded, depending on, among other things, the size of the batch, but it works nonetheless.
CREATE TABLE [dbo].[TempTable] (
[Id] INT IDENTITY PRIMARY KEY,
[Val] NVARCHAR(20)
)
GO
CREATE OR ALTER TRIGGER [IgnoreDuplicates] ON [dbo].[TempTable]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[TempTable]([Val])
SELECT [Val] FROM [INSERTED] WHERE [Val] NOT IN (
SELECT [Val] FROM [dbo].[TempTable]
)
END
GO

Copy a database table to another database table with a different structure

I am aware this was asked here. However it doesn't answer my question. I have 10 tables in a database called "merged". I am taking a bunch of other databases with an identical structure as "merged" except that "merged" has an extra column that is a combination of two columns in the table. I am trying to transfer all this data into merged but I believe the extra column I believe is preventing the transfer.
SqlCommand MergeDB = connDestination.CreateCommand();
string sqlMergeDB = "";
int a= 0;
for (a=0; a < tablenames.Length; a++){
string sqlMergeDB = "INSERT INTO sql_merged.dbo." + tablenames[a] + " SELECT * FROM sourceForMerged.dbo." + tablenames[a];
using (SqlDataReader reader = MergeDB.ExecuteReader()) {
while(reader.Read())
{
MessageBox.Show("Transfered a table");
}
}
}
The error occurs at the SqlDataReader row of the code above, which I believe means there is something wrong with the sql command. Please help. Thanks
If you name all the columns in both parts of the INSERT . . . SELECT statement you can map which source column gets inserted into which destination column.
If you imagine TargetTable (Name, ProductType, Date) and SourceTable (Date, Type, Name) then using:
INSERT INTO TargetTable (Name, ProductType, Date)
SELECT Name, Type, Date FROM SourceTable
would move the three columns into the appropriate columns even though the order doesn't match.
If you have "extra" columns in one table or the other you can either leave them out or provide expressions to fill them in:
INSERT INTO TargetTable (Name, ProductType, Date, CombinedValues)
SELECT Name, Type, Date, (ValCol1 + ' ' + ValCol2) FROM SourceTable
has four columns receiving data from four expressions, one of which concatenates two columns worth of data. (In real life, you may find that the concatenation expression is more complicated, but this is the basic idea).
You cannot use a:
Insert Into [Table] Select * From [Table2]
unless the tables schemas are identical. You would have to list out the columns for the Insert Statement.
If possible you could drop the column on the destination table and then add it back after the insert.
You could do something like this to build up you insert code if the table is very wide:
SELECT
'cmd.Parameter.Add("#' + column_name + '", SqlDbType.' + data_type + ');',
column_name 'Column Name',
data_type 'Data Type'
FROM information_schema.columns
WHERE table_name = 'TableName'

SelectedIndex not inserting correct value

I have a method to insert user information into the SQL Server database. I have my combobox populate on pageload event. The user selects the input they want and hit update if they are updating a old record or insert if creating a new record. When they do so my database is not storing the right value if they select 4 it stores 3. Here is my insert method and populate method.
Insert method: I have to join the StockID because it is a primary key.
using (dbConn2 = new SqlConnection(Properties.Settings.Default["tville"].ToString()))
{
SqlCommand addNewFormat = new SqlCommand(#"INSERT INTO PackLabelFormat ( PackFormatID, Description, PrintWeight, PrintPlantCode, PrintPrice, StockID) VALUES (#PackFormatID, #Description, #PrintWeight, #PrintPlantCode, #PrintPrice, (SELECT #StockID from LabelStockReference LSR WHERE LSR.StockID = #StockID))", dbConn2);
addNewFormat.Parameters.AddWithValue("#PackFormatID", Convert.ToInt32(IDselect));
addNewFormat.Parameters.AddWithValue("#Description", rTxtBoxDescription.Text);
addNewFormat.Parameters.AddWithValue("#PrintPrice", rChkBoxPrintPrice.Checked);
addNewFormat.Parameters.AddWithValue("#PrintWeight", rChkBoxWeight.Checked);
addNewFormat.Parameters.AddWithValue("#PrintPlantCode", rChkBoxPlantCode.Checked);
addNewFormat.Parameters.AddWithValue("#StockID", Convert.ToInt32(cmboBoxStock.SelectedIndex));
dbConn2.Open();
addNewFormat.ExecuteNonQuery();
Populate method:
if (labelList != null)
{
foreach (LabelData l in labelList)
{
cmboBoxStock.Items.Add(string.Format("{0} - {1}", l.PackFormatID, l.Description));
}
}
If there is anything else I'm leaving out just let me know. Thanks.
There are two options for your INSERT statement:
(1) you can use INSERT ... VALUES .... and in this case, you must supply as many values as you have columns to insert data into, and you have to supply literal values or SQL Server variables only - you cannot use a SELECT to provide a value:
DECLARE #ASqlServerVariable VARCHAR(100)
SET #ASqlServerVariable = 'any possible value that's legal for this datatype'
INSERT INTO dbo.YourTable(ID, Col1, Col2, Col3)
VALUES (42, 'Some fixed value', #ASqlServerVariable, 'Another literal value')
What you could do is use a SELECT to store the value you're interested in into a SQL Server variable:
DECLARe #StockID INT
SELECT #StockID = ID
FROM dbo.LabelStockReference LSR
WHERE LSR.StockID = 4711
(2) if you can't provide all literal values or variables, then you must use the INSERT .... SELECT ... option, which requires you to provide as many columns in your SELECT as the INSERT expects to insert a row into the target table:
DECLARE #ASqlServerVariable VARCHAR(100)
SET #ASqlServerVariable = 'any possible value that's legal for this datatype'
INSERT INTO dbo.YourTable(ID, Col1, Col2, Col3)
SELECT
42, 'Some fixed value', #ASqlServerVariable, aTableColumn
FROM
dbo.SomeOtherTable
See the official TechNet documentation for INSERT for all the details and exact syntax of all possible options etc.
The first SelectedIndex is 0, and your first ID is 1 ,so just make this change:
addNewFormat.Parameters.AddWithValue("#StockID", Convert.ToInt32(cmboBoxStock.SelectedIndex)+1);

Delete from table, and return the records which weren't deleted

I have an table at my SQLSERVER(2008R2) called "Clients" , it has ClientID and Address.
I'd like to make a query/SP which does the following:
Recieves a list of addresses.
Deletes the records with these address.
Returns a list of the addresses that can't be deleted(row not found, etc).
Returns the number of deleted rows.
My application is C#-ASP.NET based application.
I've thought of the following solutions:
Make an SP that recieves a list of addresses, and then tries to delete them, and if it can't delete it , to add it to some sort of array/list. - my problem with this solution is that I'm not familiar with how lists works at SQLSERVER(2008R2).
Work with DataTable , select the data I need from the DB, and delete it with SqlDataAdapter, and check whats the rows which have changed via RowState.
Example of my DataBase:
Client ID Address
111111111 'foo st. 2'
222222222 'foo bld 1'
333333333 'foo rd 22'
444444444 'foo st. 1'
Example of the input:
input list{'foo st. 2','foo bld 1','foo st 22','foo st 1'} - the last two items aren't exist at the table above, So i would like my SP (or any other method of solution) to return:
'Deleted rows: 2' //thats not a problem
'list of the records which weren't removed: {'foo st 22','foo st 1'} //thats a problem..
Hope I made myself as clear as possible.
I don't think you can find which items aren't deleted by a delete statement in sql. Firstly you need a way to split your string by comma's. I'd use a table function like what's suggested in this question
once you have that then you need to select before you delete into a temp table, then delete the records that are there, then return the missing records:
DECLARE #SplitString TABLE
(
value varchar(MAX)
)
DECLARE #MissingRecords TABLE
(
value varchar(MAX)
)
INSERT INTO #SplitString
SELECT part
FROM dbo.fSplitString('foo st 22, foo st 1', ',')
SELECT value
FROM #SplitString
WHERE value NOT IN ( SELECT Address
FROM TableName
)
DELETE FROM TableName
WHERE Address IN (SELECT Value FROM #SplitString)
SELECT *
FROM #MissingRecords
Firstly, execute the DELETE statement:
sql="DELETE FROM TABLENAME where Col1='Something'";
And then after execute SELECT statement to list of rows which are not deleted?
sql="SELECT * FROM TABLENAME where Col1='Something'";
EDIT:
List<string> addresses=new List<string>()
{
"foo st. 2","foo bld 1","foo st 22","foo st 1"
};
List<string> deleted=new List<string>();
List<string> notdeleted=new List<string>();
using(SqlConnection cn=new SqlConnection("connStr"))
{
using(SqlCommand cmd=new SqlCommand())
{
cmd.CommandText="DELETE from TableName Where Address=#address";
cmd.Connection=cn;
cmd.Parameters.Add("#address",SqlDbType.VarChar,50);
cn.Open();
for(String address in addresses)
{
cmd.Parameters["#address"].Value=address;
int result=cmd.ExecuteNonQuery();
if(result!=-1)
{
//deleted
deleted.Add(address);
}
else
{
//not deleted
notdeleted.Add(address);
}
}
cn.Close();
}
}

Categories