How to Update SQL Server Table With Data From Other Source (DataTable) - c#

I have a DataTable which is generated from .xls table.
I would like to store this DataTable into an existing table in SQL Server database.
I use SqlBulkCopy to store rows which have unique PK.
Problem is, I also have other rows which have same PK as SQL Server table but contain cells with different value compared to SQL Server table.
In short:
Let's say in my DataTable I have a row like this:
id(PK) | name | number
005 | abc | 123
006 | lge | 122
For my SQL server I have sth like this;
id(PK) | name | number
004 | cbs | 345
005 | lks | 122
Now you see the row 006 can be uploaded straight away into SQL Server using SqlBulkCopy. On the other hand the row 005 can't be inserted using it since SQL server table contains row with identical PK.
Now I tried to manually extract the row. Extract each single cell into an ArrayList then generate an UPDATE Table statement afterwards. However this method seems to be unfeasible as I have so many rows to process.
I am looking for a better method to achieve this goal.
Any help is appreciated.
Thank's

Use the below code:
C# Side code for reading data from DataTable and preparing the XML data:
DataTable dt = new DataTable();
StringBuilder sb = new StringBuilder();
sb.Append("<R>");
for (int i = 0; i < dt.Rows.Count; i++)
{
sb.Append("<C><ID>" + dt.Rows[0].ToString() + "</ID>");
sb.Append("<N>" + dt.Rows[1].ToString() + "</N>");
sb.Append("<I>" + dt.Rows[2].ToString() + "</I></C>");
}
sb.Append("</R>");
///pass XML string to DB side
///
//sb.ToString(); //here u get all data from data table as xml format
Database side Stored Procedure (you will need to update your table name):
CREATE PROCEDURE dbo.UpdateData
-- Add the parameters for the stored procedure here
#data XML
AS
BEGIN
SET NOCOUNT ON;
-- keep data into temp table
create table #tmp_data (id nchar(2),name varchar(20), number int)
DECLARE #XMLDocPointer INT
EXEC sp_xml_preparedocument #XMLDocPointer OUTPUT, #DATA
INSERT INTO #tmp_data(id,name,number)
SELECT ID,N,I
FROM OPENXML(#XMLDocPointer,'/R/C',2)
WITH(
ID nchar(30),
N VARCHAR(20),
I int
)
EXEC sp_xml_removedocument #XMLDocPointer
begin tran
-------------------INSERT not existing ones
INSERT INTO TABLE (id,name,number)
SELECT id,name,number
FROM #tmp_data
WHERE NOT EXISTS
(
SELECT 1
FROM TABLE
WHERE ID = #tmp_data.ID
)
--- update existing ones
UPDATE TABLE
SET name = #tmp_data.name, number = #tmp_data.number
FROM #tmp_data
WHERE #tmp_data.id = TABLE.id
commit tran
if(##error <> 0)
rollback tran
END

Related

Iterate between column names with user-defined table

I'm currently working with c# and sql server managment studio:
I have a table like:
+--------+-------------+-------------+------+
| TaskId | Item2(bool) | Item3(bool) | etc… |
+--------+-------------+-------------+------+
I don't know bool column names because there are created dinamically from code (user create them).
Well I insert data when form open, with TaskId and all bools with DEFAULT(0), si I have something like:
+--------------------------------------+-------------+-------------+------+
| TaskId | Item2(bool) | Item3(bool) | etc… |
+--------------------------------------+-------------+-------------+------+
| 9B093907-2072-4F59-A55C-0003CE89EF9E | 0 | 0 | 0 |
+--------------------------------------+-------------+-------------+------+
I want to update booleans, so in c# I create datatable to use User-defined tables:
DataTable checkboxList = new DataTable();
var checkboxNameColumn = checkboxList.Columns.Add("CheckBoxName", typeof(string));
var checkBoxValueColumns = checkboxList.Columns.Add("CheckBoxValue", typeof(bool));
foreach (var c in checkBoxes)
{
var row = checkboxList.NewRow();
row[checkboxNameColumn] = c.Name;
row[checkBoxValueColumns] = c.Checked;
checkboxList.Rows.Add(row);
}
Guid currentUser = new Guid(EBResources.EmpGuid);
Guid currentTaskId = new Guid(TaskId);
db.ExeSQLRedMarkItems($"usp_RedMarkItem_Insert", checkboxList, "#CheckBoxList", currentTaskId, currentUser);
CheckBoxName equals to my column Name and CheckBoxValue is value of that column. So I have my Datatable correct, now I send it to sql as:
User-defined table
CREATE TYPE [HELPER].CheckBoxValues AS TABLE(
CheckBoxName VARCHAR(255) NOT NULL,
CheckBoxValue BIT NOT NULL )
Stored Procedure:
CREATE OR ALTER PROCEDURE [dbo].[usp_RedMarkItem_Insert]
-- Add the parameters for the stored procedure here
#TaskId UNIQUEIDENTIFIER
, #CheckBoxList [Helper].[CheckBoxValues] READONLY
, #CurrentUser UNIQUEIDENTIFIER
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #CurrentCheckboxName VARCHAR(255) = (SELECT
[CheckBoxName]
FROM #CheckBoxList)
UPDATE [m]
SET
#CurrentCheckboxName = [c].[CheckBoxValue]
FROM [RedMarkItems] [m]
JOIN #CheckBoxList [c] ON [c].[CheckBoxName] = #CurrentCheckboxName
WHERE m.TaskId = #TaskId
END
So as you can see I declare #CurrentCheckboxName, but it throws me an error because that select return more than one value:
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
What I need to do to iterate between column names? Regards
you don't need the line below
DECLARE #CurrentCheckboxName VARCHAR(255) = (SELECT
[CheckBoxName]
FROM #CheckBoxList)
you already have this inside #CheckBoxList. You need something like below
UPDATE [m]
SET m.val = [c].Val
JOIN #CheckBoxList [c]
ON [c].[CheckBoxName] = m.CheckBoxName
WHERE m.TaskId = #TaskId
you can get column names from Information_schema
SELECT
TABLE_NAME,
COLUMN_NAME
FROM
INFORMATION_SCHEMA.COLUMNS
Your database design is going to give you a lot of problems. SQL Server works best with pre-defined tables with set numbers of named columns: while you can set up a system to have user-defined field labels, having actual user-defined fields is, in a sense, creating a database that runs on top of a database. You're going to need a lot of infrastructure, that you haven't created yet, to use and manage that.
My suggestion: if your need is for a single row with a collection of user-defined boolean values, store the boolean values as XML.
CREATE TABLE dbo.Task
(
TaskId int,
Checkboxes xml
)
Your XML would be defined something like this (though there are lots of ways you could define it!)
<Checkboxes>
<Checkbox name="User-defined name" value="True" />
<Checkbox name="Some other name" value="False" />
</Checkboxes>
You will still have some challenges -- for example, if you want to pull out all the rows that have a checkbox called "Apple" that had the value true -- but you'll have fewer than you would with an actual user-defined table.
And querying XML in a useful way is, in fact, possible.

C# Nested Loop for inserting multiples into DB

I have an application used for warehousing furniture. If the user enters a line with a qty > 1, I have a simple while loop where the line is broken out to be many unique lines inserted into a SQL db. I.e.
Item chair Qty 3
Becomes
Item |Qty|Unique Key
Chair |1 |1234
Chair |1 |1235
Chair |1 |1236
--
while (Qty >= 1)
{
//Go Get Next Num
// GetNextNum("LN");
int NextNum = GetNextNum("LN"); //Method to get next number from DB table
SqlConnection conn = new SqlConnection();
SqlCommand cmd = new SqlCommand();
string connStr = ConfigurationManager.ConnectionStrings["FurnitureDB"].ConnectionString;
conn.ConnectionString = connStr;
conn.Open();
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = conn;
//SQL Parameter List here
cmd.CommandText = "InsertSODtl";
SqlDataReader datareader = cmd.ExecuteReader();
Qty--;
}
My challenge is, there are some items that have a multiplier, where one item will be in 3 pieces. In which case I append a number behind the unique key:
Item Table - Qty 1 - Unique Key
------------------------------------
Table | 1 | 1234
Table | 1 | 12341
Table | 1 | 12342
So I need a nested while loop/for loop or some method to insert the appropriate number of pieces. So a table for example - there could be 2 tables, each with 3 pieces. So I need to
Item Table - Qty 1 - Unique Key
------------------------------------
Table | 1 | 1234
Table | 1 | 12341
Table | 1 | 12342
Table | 1 | 1235
Table | 1 | 12351
Table | 1 | 12352
I'm struggling to come up with the logic to do this correctly.
Thanks for your suggestions.
One option I would look at is doing this all from within the stored procedure to limit the queries you need to execute. This can be really easily achieved using any type of for loop in SQL. Now i have a basic table setup as below.
ItemTable
Item | nvarchar(50)
Qty | int
UniqueKey | int
ItemClassification
Item | nvarchar(50)
NumberOfPieces | int
Then a simple stored procedure such as:
CREATE PROCEDURE [dbo].[InsertSODtl]
#item nvarchar(50),
#uniqueKey int
AS
BEGIN
SET NOCOUNT ON
DECLARE #pieces int = (SELECT TOP 1 NumberOfPieces FROM ItemClassification WHERE Item = #item)
INSERT INTO ItemTable (Item, Quantity, UniqueKey) VALUES (#item, 1, #uniqueKey)
IF #pieces IS NOT NULL AND #pieces > 1
BEGIN
DECLARE #count int = 1;
WHILE #count < #pieces -- < assures we end -1 max piece count
BEGIN
INSERT INTO ItemTable VALUES (#item, 1, CAST(CAST(#uniqueKey as nvarchar(10)) + CAST(#count as nvarchar(5)) as int))
SET #count = #count + 1
END
END
END
Like I said it is pretty basic and probably not exactly how your tables look but the concept is there.
First query the amount of pieces in the ItemClassification table for the Item type (simple string).
Next insert the record into the ItemTable
Finally if #pieces is not null and #pieces > 1 then we run a loop from 1 to #pieces - 1 inserting back into the ItemTable appending the value to the unique key. Now we have to cast the #uniqueKey and #count as string (nvarchar(10)) as we need to concat the values and not add them together.
Again a pretty basic example but limits the required queries.
This also doesnt require a change to your code and can be done in one query.
Example Use:
EXEC InsertSODtl 'Table', 1234
EXEC InsertSODtl 'Table', 1235
EXEC InsertSODtl 'Chair', 1236
Results in:

mysql "upsert" query on table with two different unique columns

I'm trying to write a MySQL query to "upsert" into the following table:
ID (auto_int) | Code (unique) | Name (unique) | LastMod | LastBy | Other fields
========================================================================
1 | Code1 | Name1 |2015-2-2 |'system'|...
2 | Code2 | Name3 |2015-2-17|'system'|...
if code and name doesn't exist, insert
if code or (exclusive) name exist, update that name/code to match on duplicate
if code and name exist, in the same row, update "other" fields
if code and name exist, but in different rows, do nothing (detect as error in C#)
I need to select the row (with ID) that was inserted/updated (#4 can be handled here in C# by returning "0" rows and seeing that as an "error")
If any of the SQL in the transaction throws an error, roll back (I'm hoping it does this as is, but I want to confirm this is the case).
I come from an SqlServer back ground and I'm having a REALLY hard time not using If statements.
This needs to be a block of SQL (not a function/SP) and ideally transaction safe. (Has START TRANSACTION;/COMMIT;)
I've started at:
START TRANSACTION;
SET #lastModified = '2015-02-18 22:21:51';
SET #Code = 'code4';
SET #Name = 'name4';
SET #ModifiedBy = 'system';
UPDATE test1.catalog SET LastModified = #lastModified, ModifiedBy = #ModifiedBy WHERE Code = #Code AND Name = #Name;
INSERT IGNORE INTO test1.catalog (LastModified, ModifiedBy, Code, Name) VALUES (#lastModified, #modifiedBy, #code, #name);
#???
SELECT * FROM test1.catalog WHERE name = #name AND code = #code;
COMMIT;
Thanks!

Upsert on SQL Server table from XML

I'm attempting to create a small console app in C# to perform inserts on a table of Products (ITEMS) in SQL Server 2008 according to the contents of an XML file in the FASTEST way possible. I already have an .XSD file that contains the proper mappings to the SQL table (which may not be necessary with the approach outlined below).
Here's a high-level of my approach:
Read the XML, using it to create a table.
Perform a MERGE against the ITEMS table using the table created from the XML file.
2a. If the item exists, update it.
2b. If the item does not exist, insert it.
Create a log of only the records inserted in XML.
Consider the following ITEMS table and XML file:
ITEMS
Item_Id Name Price
1 Coke 5.00
2 Pepsi 3.00
3 Sprite 2.00
ITEMS.XML
<?xml version="1.0" encoding="ISO-8859-1"?>
<Item>
<Id>5</Id>
<Name>Mountain Dew</Name>
<Price>4.50</Price>
</Item>
<Item>
<Id>3</Id>
<Name>Sprite Zero</Name>
<Price>1.75</Price>
</Item>
After the import, the ITEMS table should look like:
ITEMS
Item_Id Name Price
1 Coke 5.00
2 Pepsi 3.00
3 Sprite Zero 1.75
5 Mountain Dew 4.50
Once that's done, I also need to generate an XML formatted log file that contains the "new" record that was inserted into the table (ITEMS_LOG.XML):
ITEMS_LOG.XML
<?xml version="1.0" encoding="ISO-8859-1"?>
<Item>
<Id>5</Id>
<Name>Mountain Dew</Name>
<Price>4.50</Price>
</Item>
I have tried implementing this using SQLXMLBulkLoad, but unfortunately it does not provide the logging that I need, nor does it permit me to access any of the messages returned from SQL Server (i.e. what's been inserted/updated). Although I have an intermediate level of SQL expertise, I am fairly new to working with XML, especially in this context. Any help/guidance would be greatly appreciated!
You can use merge with output to a table variable and then query the table variable to build the log XML.
Put it in a stored procedure where you have the item XML as an in parameter and the log XML as an out parameter.
create procedure AddItemXML
#ItemsXML xml,
#ItemsLogXML xml out
as
declare #Changes table
(
Item_Id int,
Name nvarchar(20),
Price money,
Action nvarchar(10)
);
merge Items as T
using
(
select T.N.value('Id[1]', 'int') as Item_Id,
T.N.value('Name[1]', 'varchar(20)') as Name,
T.N.value('Price[1]', 'money') as Price
from #ItemsXML.nodes('/Item') T(N)
) as S
on T.Item_Id = S.Item_Id
when matched then
update set Name = S.Name, Price = S.Price
when not matched then
insert (Item_Id, Name, Price) values (S.Item_Id, S.Name, S.Price)
output inserted.Item_Id,
inserted.Name,
inserted.Price,
$action
into #Changes;
set #ItemsLogXML =
(
select Item_Id as ID,
Name,
Price
from #Changes
where Action = 'INSERT'
for xml path('Item'), type
);
Working sample on SE-Data
Hope this helps you, What I did was to create a stored procedure as below. Basically the stored procedure takes xml values and checks the flags which are passed from code and determines if it is insert or update:
DECLARE #xml xml
SET #xml = #xmlCredentials
SELECT
item.value('#Id', 'int') As ID,
item.value('#AgentID', 'int') As AgentID,
item.value('#Username', 'varchar (50)') As Username,
item.value('#Password', 'varchar (50)') As [Password],
item.value('#IsDirty', 'bit') As IsDirty,
item.value('#IsDeleted', 'bit') As IsDeleted
INTO #tmp
FROM #xml.nodes('Credentials/Credential') x(item)
BEGIN TRY
BEGIN TRAN
INSERT INTO Credentials (AgentID, Username, [Password])
SELECT
AgentID, Username, [Password]
FROM
#tmp
WHERE
ID = 0 AND IsDirty = 1
UPDATE c
SET c.[AgentID] = t.AgentID,
c.[Username] = t.Username,
c.[Password] = t.[Password]
FROM
[dbo].[Credentials] c
JOIN
#tmp t ON t.Id = c.ID
WHERE
t.IsDirty = 1 AND t.IsDeleted = 0
DELETE FROM [dbo].[Credentials]
FROM [dbo].[Credentials] c
JOIN #tmp t ON t.Id = c.ID
WHERE
t.IsDirty = 1 AND t.IsDeleted = 1
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN
DECLARE #errorMSG varchar(4000)
DECLARE #errorSeverity int
DECLARE #errorState int
SET #errorMSG = ERROR_MESSAGE()
SET #errorSeverity = ERROR_SEVERITY()
SET #errorState = ERROR_STATE()
RAISERROR (#errorMSG,
#errorSeverity, #errorState);
END CATCH
SELECT [ID], [AgentID], [Username], [Password]
FROM [dbo].[Credentials]
In code behind I have my xml and pass the xml as parameter to the stored procedure:
// read xml and assign it to string variable
string xml = readxml();
try
{
string command = "EXEC SaveCredentails '" + xml + "'";
}
catch(Exception e)
{
}
I would use a staging table to import the xml into a SQL Server table. Add an extra column to indicate the action (insert or update). Then use regular sql to perform upserts as usual. You can then use the staging table to generate the XML logging you need (reading the action column to determine if it was an insert or update).

How do I insert values from two columns into an array for later use with autocomplete?

I'm doing a program in C# (windows forms) and SQL-Server where I need to take the strings from two columns and put them into an array. I will use that array later in order to implement autocomplete to some textBoxes. The rules are:
The array should be able to hold whatever the amount of strings I
have in the columns, this amount may exceed 500 strings and is variable.
I will need the distinct values from the columns, no duplicates.
I think I'm supposed to use "UNION" for putting together all the strings from the two columns and SqlDataReader with a "while" cycle when putting the strings into the array.
Here is an example table, use it to explain it to me:
----------------------------
| name | surname |
----------------------------
| John | Jackson |
----------------------------
| Michael | Jones |
----------------------------
| Amanda | Lopez |
----------------------------
| Christina | Lopez |
----------------------------
So how would the query look like and how would can I put the results into an array?
If you want a concatenate use this:
SELECT DISTINCT name + surname FROM MyTable
If you dont want a concatenate you can do this:
SELECT Col1 FROM MyTable
UNION
SELECT Col2 FROM MyTable
A union will work as long as Col1 and Col2 have the same datatype. Once you have the data you can bring it back to the client side (via a sproc) into a dataset, datatable, or sqldatareader.
The rest should be simple, take the data and store it in an array of some sort.
Either through some sort of loop
reader = GetData();
while(reader.read())
{
//store into an array...
}
Copy paste this in sql server management studio
CREATE TABLE #Test
(
col1 varchar(10),
col2 varchar(10)
)
INSERT INTO #Test(col1, col2) VALUES('jon', 'jane')
INSERT INTO #Test(col1, col2) VALUES('jane', 'jane')
INSERT INTO #Test(col1, col2) VALUES('bob', 'phil')
INSERT INTO #Test(col1, col2) VALUES('marc', 'phil')
INSERT INTO #Test(col1, col2) VALUES('jon', 'jon')
INSERT INTO #Test(col1, col2) VALUES('jon', 'Jon')
INSERT INTO #Test(col1, col2) VALUES('jane1', 'jane')
INSERT INTO #Test(col1, col2) VALUES('bob2', 'phil')
INSERT INTO #Test(col1, col2) VALUES('marc2', 'phil')
INSERT INTO #Test(col1, col2) VALUES('ste', 'jane')
SELECT col1 FROM #test
UNION
SELECT col2 FROM #test
DROP TABLE #Test
The result is:
bob
bob2
jane
jane1
jon
marc
marc2
phil
ste

Categories

Resources