tsql code to sync two xml documents - sql

xml 1
<Team>
<Players>
<Player id="1" timestamp="11/03/2012 08:10:12">30</Player>
<Player id="2" timestamp="11/02/2012 09:11:12">40</Player>
<Players>
</Team>
xml 2
<Team>
<Players>
<Player id="1" timestamp="11/02/2012 09:10:12">10</Player>
<Player id="2" timestamp="11/03/2012 10:11:12">20</Player>
<Player id="3" timestamp="11/03/2012 13:00:00">50</Player>
<Players>
</Team>
OUTPUT when we merge the above two XMLs based on timestamp element:
<Team>
<Players>
<Player id="1" timestamp="11/02/2012 09:10:12">30</Player>
<Player id="2" timestamp="11/03/2012 10:11:12">20</Player>
<Player id="3" timestamp="11/03/2012 13:00:00">50</Player>
<Players>
</Team>
Could anyone please help me with T-SQL code to do this in SQL Server 2005/2008

Using this approach with two nested CTE's, you can get close - but not all the way:
DECLARE #XML1 XML = '<Team>
<Players>
<Player id="1" timestamp="11/03/2012 08:10:12">30</Player>
<Player id="2" timestamp="11/02/2012 09:11:12">40</Player>
</Players>
</Team>'
DECLARE #XML2 XML = '<Team>
<Players>
<Player id="1" timestamp="11/02/2012 09:10:12">10</Player>
<Player id="2" timestamp="11/03/2012 10:11:12">20</Player>
<Player id="3" timestamp="11/03/2012 13:00:00">50</Player>
</Players>
</Team>'
-- extract the ID, Timestamp and node values from both XML variables
;WITH CTE AS
(
SELECT
ID = P1.value('#id', 'int'),
TS = P1.value('#timestamp', 'datetime2'),
NodeValue = P1.value('(.)[1]', 'int')
FROM #XML1.nodes('/Team/Players/Player') AS XTbl1(P1)
UNION
SELECT
ID = P2.value('#id', 'int'),
TS = P2.value('#timestamp', 'datetime2'),
NodeValue = P2.value('(.)[1]', 'int')
FROM #XML2.nodes('/Team/Players/Player') AS XTbl2(P2)
),
-- partition and sequentially number the result, so that the newest
-- (most recent) item can be extracted
CTE2 AS
(
SELECT ID, TS, NodeValue,
RowNum = ROW_NUMBER() OVER(PARTITION BY ID ORDER BY TS DESC)
FROM CTE
)
SELECT
ID AS '#id',
TS AS '#timestamp',
NodeValue AS 'text()'
FROM CTE2
WHERE RowNum = 1
FOR XML PATH('Player'), ROOT('Players')
Results in output like this:
<Players>
<Player id="1" timestamp="2012-11-03T08:10:12">30</Player>
<Player id="2" timestamp="2012-11-03T10:11:12">20</Player>
<Player id="3" timestamp="2012-11-03T13:00:00">50</Player>
</Players>

Related

Split XML report in SQL

I have below SQL stored procedure which generates a report and sends to me via email.
However it sends all the various rows as one big chunk report.
Rather I would want it to split each error
Any idea how I can split the below query to have individual XML reports generated.
DECLARE #Report XML
BEGIN
UPDATE [Orders].dbo.PurOrd
SET [Status] = 'Failed', Reason = '<e id="0" message="failed test order" />'
WHERE [Status] = 'InProcess'
SET #Report = (
SELECT
p.Name as "#Name",
p.Customer "#Customer",
CASE p.Name
WHEN 'Default' THEN convert(xml,p.RejectedReason)
ELSE convert(xml,f.RejectedReason)
END AS "RejectedReason",
(
SELECT u.first_name as "#FirstName",
u.last_name as "#LastName",
FROM [Users].dbo.Users u
WHERE u.user_id = u.user_id
for xml PATH('Users'), type
),
(
SELECT
li.Product as "#PId",
li.Quantity as "#Quantity",
li.SalePrice as "#Price",
FROM [Cart].dbo.LineItems li
WHERE li.OrderFormId = f.OrderFormId
ORDER BY li.ItemNumber
for xml PATH('LineItem'), type
)
FROM [Orders].dbo.OrForms f
JOIN [Orders].dbo.PurOrd p on f.GroupId = p.GroupId
WHERE
(p.Status = 'OrderRejected' AND p.ReportStatus IS NULL)
FOR XML PATH('test'), TYPE
)
SELECT #Report FOR XML PATH('Report')
Current Output:
<Report>
<PurchaseOrder OrderId="Order 1" Name="name1" Seller="abc">
<Reason>
<errors>
<e id="0" message="failed test ord" />
</errors>
</Reason>
<Users FirstName="abc" LastName="xyz"/>
<LineItem ProductId="Clothes1" Quantity="1.0000" SalePrice="100rs"/>
</PurchaseOrder>
<PurchaseOrder OrderId="Order 2" Name="name 2" Seller="abc">
<Reason>
<errors>
<e id="0" message="failed test ord" />
</errors>
</Reason>
<Users FirstName="abc" LastName="xyz"/>
<LineItem ProductId="Clothes1" Quantity="1.0000" SalePrice="100rs"/>
</PurchaseOrder>
</Report>
EXPECTED OUTPUT:
<Report>
<PurchaseOrder OrderId="Order 1" Name="name1" Seller="abc">
<Reason>
<errors>
<e id="0" message="failed test ord" />
</errors>
</Reason>
<Users FirstName="abc" LastName="xyz"/>
<LineItem ProductId="Clothes1" Quantity="1.0000" SalePrice="100rs"/>
</PurchaseOrder>
</Report>
<Report>
<PurchaseOrder OrderId="Order 2" Name="name 2" Seller="abc">
<Reason>
<errors>
<e id="0" message="failed test ord" />
</errors>
</Reason>
<Users FirstName="abc" LastName="xyz"/>
<LineItem ProductId="Clothes1" Quantity="1.0000" SalePrice="100rs"/>
</PurchaseOrder>
</Report>
One way to get
<Report>
<PurchaseOrder ... >
...
</PurchaseOrder>
</Report>
<Report>
<PurchaseOrder ...>
...
</PurchaseOrder>
</Report>
structure is to wrap your select list into CROSS APLLY. Kind of
SELECT t.PurchaseOrder
FROM [Orders].dbo.OrForms f
JOIN [Orders].dbo.PurOrd p on f.GroupId = p.GroupId
CROSS APPLY (
SELECT
-- original select list copy
p.Name as "#Name",
p.Customer "#Customer",
CASE p.Name
WHEN 'Default' THEN convert(xml,p.RejectedReason)
ELSE convert(xml,f.RejectedReason)
END AS "RejectedReason",
(
SELECT u.first_name as "#FirstName",
u.last_name as "#LastName",
FROM [Users].dbo.Users u
WHERE u.user_id = u.user_id
for xml PATH('Users'), type
),
(
SELECT
li.Product as "#PId",
li.Quantity as "#Quantity",
li.SalePrice as "#Price",
FROM [Cart].dbo.LineItems li
WHERE li.OrderFormId = f.OrderFormId
ORDER BY li.ItemNumber
for xml PATH('LineItem'), type
)
-- original select list copy END
FOR XML PATH(''), TYPE
) t(PurchaseOrder)
WHERE
(p.Status = 'OrderRejected' AND p.ReportStatus IS NULL)
FOR XML PATH('Report'), TYPE;

Can yo help me with getting some data from a XML?

Getting SQL data from multiple XML
I already tried to put the code in an XML variable and select OrderNumber, ProductionLine and ItemId's but having some troubles with the query.
DECLARE #DXML XML = '<ComDecom OrderNumber="101983026"
ProductionLine="14" BatchNumber="02-00" ItemObjectTypeId="1"
ItemFlag="20" EventGuid="989bfdb4-9dd8-40be-9872-1e0bae7cc4d6"
LastMessage="false" HostName="PMIPTLISWCT0014+1">
<Item ItemId="LESTCNNGxDDCPq1bSF1S119052306" TimeStamp="2019-05-23
07:56:07.475 +01:00" SeqNumber="175660" />
<Item ItemId="LESTCNNGxDDCPq1bSF1S119052306" TimeStamp="2019-05-23
07:56:07.519 +01:00" SeqNumber="175661" />
<Item ItemId="LESTCNoTmCiiVu1bSF1S119052306" TimeStamp="2019-05-23
07:56:08.487 +01:00" SeqNumber="175662" />
</ComDecom>'
SELECT ComDeCom.value('#OrderNumber', 'int') AS OrderNumber
,ComDecom.value('#ProductionLine', 'int') AS ProductionLine
,ItemTbl.value('#ItemId', 'varchar') AS Item
FROM #dxml.nodes('/ComDecom/') AS ComDecomTbl(ComDecom)
CROSS APPLY ComDecom.Item.nodes('Site') AS ItemTbl(Item)
I think you are looking for this-
DECLARE #DXML XML=
'<ComDecom OrderNumber="101983026" ProductionLine="14" BatchNumber="02-00" ItemObjectTypeId="1" ItemFlag="20" EventGuid="989bfdb4-9dd8-40be-9872-1e0bae7cc4d6" LastMessage="false" HostName="PMIPTLISWCT0014+1">
<Item ItemId="LESTCNNGxDDCPq1bSF1S119052306" TimeStamp="2019-05-23 07:56:07.475 +01:00" SeqNumber="175660" />
<Item ItemId="LESTCNNGxDDCPq1bSF1S119052306" TimeStamp="2019-05-23 07:56:07.519 +01:00" SeqNumber="175661" />
<Item ItemId="LESTCNoTmCiiVu1bSF1S119052306" TimeStamp="2019-05-23 07:56:08.487 +01:00" SeqNumber="175662" />
</ComDecom>';
SELECT
T.N.value('#OrderNumber', 'int') AS OrderNumber,
T.N.value('#ProductionLine', 'int') AS ProductionLine,
T2.N2.value('#ItemId', 'varchar(MAX)') AS Item
FROM #dxml.nodes('/ComDecom') AS T(N)
CROSS APPLY #dxml.nodes('/ComDecom/Item') AS T2(N2)
Output is-
OrderNumber ProductionLine Item
101983026 14 LESTCNNGxDDCPq1bSF1S119052306
101983026 14 LESTCNNGxDDCPq1bSF1S119052306
101983026 14 LESTCNoTmCiiVu1bSF1S119052306

FORXML SQL Group By Element

I am trying to group some elements together under one node. This is my current SQL;
declare #xml xml
set #xml = (
select (
select
'DERIVED' '#type',
m.NuixDerivedFieldName '#name', (
SELECT
NuixFieldType as 'metadata/#type',
NuixFieldName as 'metadata/#name'
from eddsdbo.MetadataMapping m1
where m1.NuixDerivedFieldName = m.NuixDerivedFieldName
for xml path ('first-non-blank'), type
)
from (select distinct NuixDerivedFieldName from eddsdbo.MetadataMapping) m
for xml path ('metadata'))
)
;WITH XMLNAMESPACES(DEFAULT 'http://nuix.com/fbi/metadata-profile')
select #xml for XML PATH ('metadata-list'), ROOT ('metadata-profile')
Which gives me the following output;
<metadata-profile xmlns="http://nuix.com/fbi/metadata-profile">
<metadata-list>
<metadata type="DERIVED" name="Barcode" xmlns="">
<first-non-blank>
<metadata type="CUSTOM" name="Barcode" />
</first-non-blank>
<first-non-blank>
<metadata type="EVIDENCE" name="Barcode" />
</first-non-blank>
</metadata>
I want to group together elements together which have the same 'name' attribute of the metadata element under the <first-non-blank> element.
The desired output should be;
<metadata-profile xmlns="http://nuix.com/fbi/metadata-profile">
<metadata-list>
<metadata type="DERIVED" name="Barcode" xmlns="">
<first-non-blank>
<metadata type="CUSTOM" name="Barcode" />
<metadata type="EVIDENCE" name="Barcode" />
</first-non-blank>
</metadata>
...
My database looks something like this;
NuixFieldName NuixFieldType NuixDerivedFieldName
------------------------------ ------------------------------ ------------------------------
_EmailEntryID PROPERTY EmailEntryID
Audited Audited Audited
Author PROPERTY Author
Barcode CUSTOM Barcode
Barcode EVIDENCE Barcode
I would also like to remove the xlmns namespace identifier from the metadata elements.
Thanks in advance!
You could try this
DECLARE #SampleData AS TABLE
(
NuixFieldName varchar(20),
NuixFieldType varchar(20),
NuixDerivedFieldName varchar(20)
)
INSERT INTO #SampleData
VALUES
('_EmailEntryID','PROPERTY','EmailEntryID'),
('Audited', 'Audited ','Audited'),
('Author ', 'PROPERTY','Author '),
('Barcode', 'CUSTOM ','Barcode'),
('Barcode', 'EVIDENCE','Barcode')
DECLARE #xml XML
SET #xml = (
SELECT
-- sd.NuixDerivedFieldName AS [#name],
'DERIVED' AS [#type],
sd.NuixDerivedFieldName AS [#name],
(
SELECT
sd2.NuixFieldType as '#type',
sd2.NuixFieldName as '#name'
FROM #SampleData sd2 WHERE sd2.NuixDerivedFieldName = sd.NuixDerivedFieldName
FOR XML PATH ('metadata'),ROOT('first-non-blank'), TYPE
)
FROM (select DISTINCT sd.NuixDerivedFieldName from #SampleData sd ) sd
FOR XML PATH('metadata'), ROOT('metadata-list'),TYPE
)
;WITH XMLNAMESPACES(DEFAULT 'http://nuix.com/fbi/metadata-profile')
SELECT #xml FOR XML PATH (''),ROOT('metadata-profile')
return:
<metadata-profile xmlns="http://nuix.com/fbi/metadata-profile">
<metadata-list>
<metadata type="DERIVED" name="Audited">
<first-non-blank>
<metadata type="Audited " name="Audited" />
</first-non-blank>
</metadata>
<metadata type="DERIVED" name="Author ">
<first-non-blank>
<metadata type="PROPERTY" name="Author " />
</first-non-blank>
</metadata>
<metadata type="DERIVED" name="Barcode">
<first-non-blank>
<metadata type="CUSTOM " name="Barcode" />
<metadata type="EVIDENCE" name="Barcode" />
</first-non-blank>
</metadata>
<metadata type="DERIVED" name="EmailEntryID">
<first-non-blank>
<metadata type="PROPERTY" name="_EmailEntryID" />
</first-non-blank>
</metadata>
</metadata-list>
</metadata-profile>

The target of 'replace' must be at most one node

I'm trying to modify an XML value but keep getting the message
The target of the replace must be at most one node, found attribute(prefType, xdt:untypedAtomic)
First here is my XML
<preferences>
<categories>
<category id="1" prefType="2">
<subcat id="1" prefType="2" />
<subcat id="2" prefType="2" />
<subcat id="3" prefType="2" />
<subcat id="77" prefType="2" />
</category>
<category id="2" prefType="2">
<subcat id="9" prefType="2" />
<subcat id="10" prefType="2" />
<subcat id="11" prefType="2" />
<subcat id="12" prefType="2" />
<subcat id="13" prefType="2" />
<subcat id="14" prefType="2" />
<subcat id="17" prefType="2" />
<subcat id="78" prefType="2" />
<subcat id="101" prefType="2" />
</category>
<category id="3" prefType="2">
<subcat id="18" prefType="2" />
<subcat id="19" prefType="2" />
<subcat id="20" prefType="2" />
</category>
</categories>
</preferences>
And my code
declare #XMLinput as XML;
declare #custXML as XML;
declare #subcatid as nvarchar(3);
declare #interest as nvarchar(8);
declare #newValue as varchar(1);
declare #cnt as int;
set #XMLinput = '<preferences><categoryId>73</categoryId><interestLevel>POSITIVE</interestLevel></preferences>';
-- get the subcatid and interest level
SET #subcatid = #XMLinput.value('(//preferences/categoryId)[1]','nvarchar(3)');
SET #interest = #XMLinput.value('(//preferences/interestLevel)[1]','nvarchar(20)');
SET #newValue =
CASE #interest
WHEN 'POSITIVE' THEN '1'
WHEN 'NEGATIVE' THEN '3'
ELSE '2'
END;
set #custXML = (select Preferences from Customer_Preferences where custID=11584);
select #custXML.exist('//preferences/categories/category/subcat[#id=sql:variable("#subcatid")]');
if (##ROWCOUNT > 0)
BEGIN TRY
BEGIN TRAN;
set #cnt = CAST(CAST(#custXML.query('count(//preferences/categories/category/subcat[#id=(sql:variable("#subcatid"))])') AS VARCHAR) AS INT);
-- replace the value
UPDATE Customer_Preferences
SET preferences.modify('
replace value of
(//*/subcat[#id=sql:variable("#subcatid")]/#prefType[1])
with sql:variable("#newValue")
')
where CustID = 11584;
COMMIT TRAN;
END TRY
BEGIN CATCH
select XACT_STATE() as 'XACT_STATE', ##TRANCOUNT as '##TRANCOUNT';
if ##TRANCOUNT > 0 ROLLBACK TRANSACTION;
END CATCH
select preferences from Customer_Preferences where custid=11584
SELECT XACT_STATE() as 'XACT_STATE', ##TRANCOUNT AS '##TRANCOUNT'
I've tried removing the sql variables and replacing them with fixed values but still get the same issue. I've also tried removing all the XML subcats except for one and the same error occurs.
After 3 hours of working through this and getting no where, I'd really appreciate your help.
Just as further reference (although you solved your problem already on your own) for others with similar problems: Here is what actually went wrong: The error message already indicates that the replace target can be at most one value (meaning you can replace only one value at a time).
However, (//*/subcat[#id=sql:variable("#subcatid")]/#prefType[1]) yields a sequence of results What is means literally is to take each subcat element and select the first attribute with the name prefType. This actually does not make much sense as an XML element can not have multiple attributes with the same name, so the query would be the same without the [1] predicate.
What you probably wanted to write is: Give me each prefType attribute of each subcat element and return only the first one of the whole result set. That is exactly what your working query is doing: (//*/subcat[#id=sql:variable("#subcatid")]/#prefType)[1]

How to parse data from a CLOB in SQL Server?

I would like to use OPENROWSET with the BULK command to load data into SQL Server as a CLOB, and as a second step to parse the CLOB and load read the data as a table.
E.g.:
SELECT BulkColumn
FROM OPENROWSET (BULK 'c:\somedir\somefile.txt', SINGLE_CLOB) TheFile
yields:
BulkColumn
Col1,Col2,Col3,1,2,3,1,2,3,1,2,3,1,2,3
I want to select this as:
Col1 Col2 Col3
1 2 3
1 2 3
1 2 3
Create a format file and use OPENROWSET or BULK INSERT to import data from text file.
Example format.xml:
<?xml version="1.0" ?>
<BCPFORMAT xmlns="http://schemas.microsoft.com/sqlserver/2004/bulkload/format" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<RECORD>
<FIELD ID="1" xsi:type="CharTerm" TERMINATOR="," MAX_LENGTH="1" />
<FIELD ID="2" xsi:type="CharTerm" TERMINATOR="," MAX_LENGTH="1" />
<FIELD ID="3" xsi:type="CharTerm" TERMINATOR="," MAX_LENGTH="1" />
</RECORD>
<ROW>
<COLUMN SOURCE="1" NAME="Col1" xsi:type="SQLNVARCHAR" />
<COLUMN SOURCE="2" NAME="Col2" xsi:type="SQLNVARCHAR" />
<COLUMN SOURCE="3" NAME="Col3" xsi:type="SQLNVARCHAR" />
</ROW>
</BCPFORMAT>
This is the key line:
<FIELD ID="3" xsi:type="CharTerm" TERMINATOR="," MAX_LENGTH="1" />
where the terminator char is "," and not "\r\n", if the data is in a single line.
Example OPENROWSET:
INSERT INTO [your_table]
SELECT [text_file].[Col1],
[text_file].[Col2],
[text_file].[Col3]
FROM OPENROWSET(
BULK N'c:\somedir\somefile.txt',
FORMATFILE = N'c:\somedir\format.xml',
FIRSTROW = 1) AS [text_file]
Example BULK INSERT:
BULK INSERT [your_table]
FROM N'c:\somedir\somefile.txt'
WITH (FORMATFILE = N'c:\somedir\format.xml')
Don't import it as OPENROWSET..BULK
You can use BULK INSERT to import it as columns or OPENROWSET by itself