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).
Related
I have a SQL Server 2008 R2 datatable dbo.Forum_Posts with columns Subject (nvarchar(255)) andBody (nvarchar(max)).
I would like to get all words with length >= 3 from columns Subject and Body and insert them into datatable dbo.Search_Word (column Word, nvarchar(100)) and datatable dbo.SearchItem (column Title (nvarchar(200)).
I also want to get new generated SearchWordsID (primary key, autoincrement, int) from dbo.Search_Word, and SearchItemID (primary key, autoincrement,int) from dbo.SearchItem, and insert them into datatable dbo.SearchItemWord (columns SearchWordsID (foreign key,int, not null) and SearchItemID (foreign key,int,not null).
What is a fastest way to do this in T-SQL? Or I have to use C#? Thank you in advance for any help.
As requested, this will keep the ID's. So you will get a DISTINCT list of works BY id.
Slightly different approach than the first answer, but easily achieved via the Outer Apply
**
You must edit the initial query Select KeyID=[YourKeyID],Words=[YourField1]+' '+[YourField2] from [YourTable]
**
Declare #String varchar(max) = ''
Declare #Delimeter varchar(25) = ' '
-- Generate and Strip special characters
Declare #StripChar table (Chr varchar(10));Insert Into #StripChar values ('.'),(','),('/'),('('),(')'),(':') -- Add/Remove as needed
-- Generate Base Data and Expand via Outer Apply
Declare #XML xml
Set #XML = (
Select A.KeyID
,B.Word
From ( Select KeyID=[YourKeyID],Words=[YourField1]+' '+[YourField2] from [YourTable]) A
Outer Apply (
Select Word=split.a.value('.', 'varchar(150)')
From (Select Cast ('<x>' + Replace(A.Words, #Delimeter, '</x><x>')+ '</x>' AS XML) AS Data) AS A
Cross Apply data.nodes ('/x') AS Split(a)
) B
For XML RAW)
-- Convert XML to varchar(max) for Global Search & Replace (could be promoted to Outer Appy)
Select #String = Replace(Replace(cast(#XML as varchar(max)),Chr,' '),' ',' ') From #StripChar
Select #XML = cast(#String as XML)
Select Distinct
KeyID = t.col.value('#KeyID', 'int')
,Word = t.col.value('#Word', 'varchar(150)')
From #XML.nodes('/row') AS t (col)
Where Len(t.col.value('#Word', 'varchar(150)'))>3
Order By 1
Returns
KetID Word
0 UNDEF
0 Undefined
1 HIER
1 System
2 Control
2 UNDEF
3 JOBCONTROL
3 Market
3 Performance
...
87 Analyitics
87 Market
87 UNDEF
88 Branches
88 FDIC
88 UNDEF
...
You're going to need T-SQL to do the inserting into your tables. Your biggest challenge is going to be splitting the posts into words.
My suggestion would be to read the posts into C#, split each post into words (you can use the Split method to split on spaces or punctuation), filter the collection of words, and then execute your inserts from C#.
You can avoid using T-SQL directly if you use Entity Framework or a similar ORM.
Don't try to use T-SQL to split your posts into words unless you really want a totally SQL solution and are willing to take time to perfect it. And, yes, it will be slow: T-SQL is not fast at string operations.
You can also investigate full text indexing, which I believe has support for search keywords.
Perhaps this will help
Declare #String varchar(max) = ''
Declare #Delimeter varchar(25) = ' '
Select #String = #String + ' '+Words
From (
Select Words=[YourField1]+' '+[YourField2] from [YourTable]
) A
-- Generate and Strip special characters
Declare #StripChar table (Chr varchar(10));Insert Into #StripChar values ('.'),(','),('/'),('('),(')'),(':') -- Add/Remove as needed
Select #String = Replace(Replace(#String,Chr,' '),' ',' ') From #StripChar
-- Convert String into XML and Split Delimited String
Declare #Table Table (RowNr int Identity(1,1), String varchar(100))
Declare #XML xml = Cast('<x>' + Replace(#String,#Delimeter,'</x><x>')+'</x>' as XML)
Insert Into #Table Select String.value('.', 'varchar(max)') From #XML.nodes('x') as T(String)
-- Generate Final Resuls
Select Distinct String
From #Table
Where Len(String)>3
Order By 1
Returns (sample)
String
------------------
Access
Active
Adminstrators
Alternate
Analyitics
Applications
Branches
Cappelletti
City
Class
Code
Comments
Contact
Control
Daily
Data
Date
Definition
Deleted
Down
Email
FDIC
Variables
Weekly
Hi I am trying to insert data into an SQL Server Database using an XML file which has some data as follows.I was able to do the attribute mapping in OPENXML.If i try to pass the XML as elements instead of attributes i get an error regarding the null insertion.
Following is my XML FILE (containg attributes)
<NewDataSet>
<SampleDataTable id="20" Name="as" Address="aaa" Email="aa" Mobile="123" />
</NewDataSet>
I am successful using the above format.If i use the below format i face errors
<Customer>
<Id>20</Id>
<Name>Cn</Name>
<Address>Pa</Address>
<Email>bnso#gmail.com</Email>
<Mobile>12345513213</Mobile>
</Customer>
This is my openXML in SQL
insert into #tempTable
select * from openxml (#xmlHandle,'ROOT/Customer/',1)
with (Cust_id int '#id',
Customer_Name varchar(30) '#Name',
Address varchar(30) '#Address',
Email_id varchar(30) '#Email',
Mobile_no bigint '#Mobile'
)
Insert into Test.dbo.tblCustomers (Cust_id,Customer_Name,Address,Email,Mobile_No) (select * from #tempTable)
please help
It's because you're trying to fetch data as attributes, but int your xml data is inside elements. Try this:
insert into #tempTable
select *
from openxml (#xmlHandle,'ROOT/Customer/',1)
with (Cust_id int '#id',
Customer_Name varchar(30) 'Name[1]',
Address varchar(30) 'Address[1]',
Email_id varchar(30) 'Email[1]',
Mobile_no bigint 'Mobile[1]'
)
Or you can do this without openxml:
select
t.c.value('Name[1]', 'varchar(30)') as Name,
t.c.value('Address[1]', 'varchar(30)') as Address,
t.c.value('Email[1]', 'varchar(30)') as Email,
t.c.value('Mobile[1]', 'bigint') as Mobile
from #Data.nodes('Customer') as t(c)
sql fiddle demo
The problem is "element value" vs "element attribute".
This page has good examples of both:
http://technet.microsoft.com/en-us/library/ms187897%28v=sql.90%29.aspx
<Customer>
<CustomerID>LILAS</CustomerID>
<ContactName>Carlos Gonzlez</ContactName>
<Order OrderID="10283" CustomerID="LILAS" EmployeeID="3" OrderDate="1996-08-16T00:00:00">
<OrderDetail ProductID="72" Quantity="3"/>
</Order>
</Customer>
</ROOT>'
-- Create an internal representation of the XML document.
EXEC sp_xml_preparedocument #XmlDocumentHandle OUTPUT, #XmlDocument
-- Execute a SELECT statement using OPENXML rowset provider.
SELECT *
FROM OPENXML (#XmlDocumentHandle, '/ROOT/Customer',2)
WITH (CustomerID varchar(10),
ContactName varchar(20))
EXEC sp_xml_removedocument #XmlDocumentHandle
This is the first time I am using XML to insert data into a table.I am saving the data from the front end(all the Datagridview rows) into an xml file and sending it to database to insert into table SD_ShippingDetails.Below is the Query for reading the XML data and saving data.As you can see from the Query I am deleting the related ShippingID details and inserting again.(DELETE FROM SD_ShippingDetails WHERE ShippingID=#ShippingID).Can we update already existing rows in the SD_ShippingDetails by getting the data from XML.If Yes,Please help me with the query.
CREATE PROCEDURE SD_Insert_ShippingDetails
#PBMXML as varchar(Max),
#ShippingID as INT
AS
BEGIn
declare #i int
exec sp_xml_preparedocument #i output,#PBMXML
DELETE FROM SD_ShippingDetails WHERE ShippingID=#ShippingID
INSERT INTO SD_ShippingDetails(ShippingID,Weight,Height,TotalBoxes,Price)
SELECT ShippingID,Weight,Height,TotalBoxes,Price FROM OPENXML(#i,'Root/ShippingBox',2)
WITH (
ShippingID int,Weight varchar(20),Height varchar(20),TotalBoxes varchar(20),Price numeric(18,2))
exec sp_xml_removedocument #i
END
Thanks.
You are on SQL Server 2005 so you can use the XML datatype instead of openxml so this answer uses that instead. Using the XML datatype is not necessary for the solution. You can rewrite using openxml if you want to.
You specified in a comments that there is an ID identity field in SD_ShippingDetails (I assume that is the primary key) but you also said that the combination of ShippingID and Weight is unique. That leaves us with a table structure that looks like this.
create table dbo.SD_ShippingDetails
(
ID int identity primary key,
ShippingID int not null,
Weight varchar(20) not null,
Height varchar(20),
TotalBoxes varchar(20),
Price numeric(18,2),
unique (ShippingID, Weight)
);
The stored procedure first needs to update all rows that already exist in SD_ShippingDetails and after that it needs to insert the rows that are missing.
create procedure dbo.SD_Insert_ShippingDetails
#PBMXML as xml
as
update dbo.SD_ShippingDetails
set Height = T.N.value('(Height/text())[1]', 'varchar(20)'),
TotalBoxes = T.N.value('(TotalBoxes/text())[1]', 'varchar(20)'),
Price = T.N.value('(Price/text())[1]', 'numeric(18,2)')
from #PBMXML.nodes('Root/ShippingBox') as T(N)
where ShippingID = T.N.value('(ShippingID/text())[1]', 'int') and
Weight = T.N.value('(Weight/text())[1]', 'varchar(20)');
insert into dbo.SD_ShippingDetails(ShippingID, Weight, Height, TotalBoxes, Price)
select T.N.value('(ShippingID/text())[1]', 'int'),
T.N.value('(Weight/text())[1]', 'varchar(20)'),
T.N.value('(Height/text())[1]', 'varchar(20)'),
T.N.value('(TotalBoxes/text())[1]', 'varchar(20)'),
T.N.value('(Price/text())[1]', 'numeric(18,2)')
from #PBMXML.nodes('Root/ShippingBox') as T(N)
where not exists (
select *
from dbo.SD_ShippingDetails
where ShippingID = T.N.value('(ShippingID/text())[1]', 'int') and
Weight = T.N.value('(Weight/text())[1]', 'varchar(20)')
);
SQL Fiddle
If you have Sql Server 2005, then placing the values in #temp or #variables tables is best.
With 2008 and up, you could piggy back on the MERGE functionality.
http://msdn.microsoft.com/en-us/library/bb522522(v=sql.105).aspx
Here is a good link for xml shredding. Note, you are using the older version of OPENXML. That was a more Sql Server 2000 command. Check Plamen's blog below for 2005 and above syntax.
http://pratchev.blogspot.com/2007/06/shredding-xml-in-sql-server-2005.html
I would populate your XML into a variable table and then use an Update Statement and an Insert with a Not Exists.
If you had SQL 2008 you could replace your delete and insert statements with this...
MERGE SD_ShippingDetails AS Target
USING (SELECT ShippingID,
Weight,
Height,
TotalBoxes,
Price
FROM OPENXML(#i,'Root/ShippingBox',2)
WITH (ShippingID int,
Weight varchar(20),
Height varchar(20),
TotalBoxes varchar(20),
Price numeric(18,2)) ) AS source (ShippingID,Weight,Height,TotalBoxes,Price)
ON (target.ShippingID = source.ShippingID)
WHEN MATCHED THEN
UPDATE SET Weight = source.Weight,
Height = source.Height,
TotalBoxes = source.TotalBoxes,
Price = source.Price
WHEN NOT MATCHED THEN
INSERT (ShippingID,Weight,Height,TotalBoxes,Price)
VALUES (source.ShippingID,source.Weight,source.Height,source.TotalBoxes,source.Price);
I have a Gridview in front end where Grid have two columns : ID and Order like this:
ID Order
1 1
2 2
3 3
4 4
Now user can update the order like in front end Gridview:
ID Order
1 2
2 4
3 1
4 3
Now if the user click the save button the ID and order data is being sent to Stored Procedure as #sID = (1,2,3,4) and #sOrder = (2,4,1,3)
Now if I want to update the order and make save I want to store it into database. Through Stored procedure how can update into the table so that the table is updated and while select it gives me the results like:
ID Order
1 2
2 4
3 1
4 3
There is no built in function to parse these comma separated string. However, yo can use the XML function in SQL Server to do this. Something like:
DECLARE #sID VARCHAR(100) = '1,2,3,4';
DECLARE #sOrder VARCHAR(10) = '2,4,1,3';
DECLARE #sIDASXml xml = CONVERT(xml,
'<root><s>' +
REPLACE(#sID, ',', '</s><s>') +
'</s></root>');
DECLARE #sOrderASXml xml = CONVERT(xml,
'<root><s>' +
REPLACE(#sOrder, ',', '</s><s>') +
'</s></root>');
;WITH ParsedIDs
AS
(
SELECT ID = T.c.value('.','varchar(20)'),
ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS RowNumber
FROM #sIDASXml.nodes('/root/s') T(c)
), ParsedOrders
AS
(
SELECT "Order" = T.c.value('.','varchar(20)'),
ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS RowNumber
FROM #sOrderASXml.nodes('/root/s') T(c)
)
UPDATE t
SET t."Order" = p."Order"
FROM #tableName AS t
INNER JOIN
(
SELECT i.ID, p."Order"
FROM ParsedOrders p
INNER JOIN ParsedIDs i ON p.RowNumber = i.RowNumber
) AS p ON t.ID = p.ID;
Live Demo
Then you can put this inside a stored procedure or whatever.
Note that: You didn't need to do all of this manually, it should be some way to make this gridview update the underlying data table automatically through data binding. You should search for something like this instead of all this pain.
You could use a table valued parameter to avoid sending delimiter-separated values or even XML to the database. To do this you need to:
Declare a parameter type in the database, like this:
CREATE TYPE UpdateOrderType TABLE (ID int, Order int)
After that you can define the procedure to use the parameter as
CREATE PROCEDURE UpdateOrder (#UpdateOrderValues UpdateOrderType readonly)
AS
BEGIN
UPDATE t
SET OrderID = tvp.Order
FROM <YourTable> t
INNER JOIN #UpdateOrderValues tvp ON t.ID=tvp.ID
END
As you can see, the SQL is trivial compared to parsing XML or delimited strings.
Use the parameter from C#:
using (SqlCommand command = connection.CreateCommand()) {
command.CommandText = "dbo.UpdateOrder";
command.CommandType = CommandType.StoredProcedure;
//create a table from your gridview data
DataTable paramValue = CreateDataTable(orderedData)
SqlParameter parameter = command.Parameters
.AddWithValue("#UpdateOrderValues", paramValue );
parameter.SqlDbType = SqlDbType.Structured;
parameter.TypeName = "dbo.UpdateOrderType";
command.ExecuteNonQuery();
}
where CreateDataTable is something like:
//assuming the source data has ID and Order properties
private static DataTable CreateDataTable(IEnumerable<OrderData> source) {
DataTable table = new DataTable();
table.Columns.Add("ID", typeof(int));
table.Columns.Add("Order", typeof(int));
foreach (OrderData data in source) {
table.Rows.Add(data.ID, data.Order);
}
return table;
}
(code lifted from this question)
As you can see this approach (specific to SQL-Server 2008 and up) makes it easier and more formal to pass in structured data as a parameter to a procedure. What's more, you're working with type safety all the way, so much of the parsing errors that tend to crop up in string/xml manipulation are not an issue.
You can use charindex like
DECLARE #id VARCHAR(MAX)
DECLARE #order VARCHAR(MAX)
SET #id='1,2,3,4,'
SET #order='2,4,1,3,'
WHILE CHARINDEX(',',#id) > 0
BEGIN
DECLARE #tmpid VARCHAR(50)
SET #tmpid=SUBSTRING(#id,1,(charindex(',',#id)-1))
DECLARE #tmporder VARCHAR(50)
SET #tmporder=SUBSTRING(#order,1,(charindex(',',#order)-1))
UPDATE dbo.Test SET
[Order]=#tmporder
WHERE ID=convert(int,#tmpid)
SET #id = SUBSTRING(#id,charindex(',',#id)+1,len(#id))
SET #order=SUBSTRING(#order,charindex(',',#order)+1,len(#order))
END
I having XMl file I am Reading All the Xml using this code . But mean while I want to insert XML records to sql table,Whatever I have Read. I did not Find any Solution for that .XMl consists No of rows and how can i insert in Proper way .
if (File.Exists(xmlpath))
{
try
{
XDocument xmlDoc = XDocument.Load(xmlpath);
var vrresult = from a in xmlDoc.XPathSelectElements("/Parts/Part")
select new
{
S = a.Element("Section").Value,
M = a.Element("Mark").Value,
Q = a.Element("Qty").Value,
W = a.Element("Weight").Value,
RD = a.Element("databaseUpdateMark").Value
};
GridView1.DataSource = vrresult;
GridView1.DataBind();
}
catch (Exception ex)
{
Lbmsg.Text = ex.Message;
}
finally
{
}
}
here is one example how to use OPENXML function
Bulk Insertion of Data Using C# DataTable and SQL server OpenXML function
Make use of function : OPENXML (Transact-SQL) which is avialble in sql server allows you to insert data in server....
also read : Use OPENXML to insert values from a string of XML
Example :
--Use OPENXML to pull records from an XML string and insert them into a database
DECLARE #idoc int
DECLARE #doc varchar (1000)
--XML document
SET #doc ='
<ROOT>
<Employee>
<FirstName>Tom</FirstName>
<LastName>Jones</LastName>
</Employee>
<Employee>
<FirstName>Gus</FirstName>
<LastName>Johnson</LastName>
</Employee>
</ROOT>'
--Create an internal representation of the XML document
--the idoc variable is set as the document handler, so we can refer to it later
EXEC sp_xml_preparedocument #idoc OUTPUT, #doc
-- Use the OPENXML rowset provider with an Insert
-- #idoc lets us know which internal representation of an xml doc to use
-- '/ROOT/Employee' shows us which node in the xml tree to work on
-- the 2 denotes we want to use elemenet centric mapping for the values, as oppsed to attributes or a combination of both
-- in the 'With' we specify how the output rowset will look
INSERT INTO Employees (FirstName, LastName)
SELECT *
FROM OPENXML (#idoc, '/ROOT/Employee',2)
WITH (FirstName varchar(10),
LastName varchar(20)
)
-- Clear the XML document from memory
EXEC sp_xml_removedocument #idoc