Looping thru xml then updating xml variable in SQL - sql

My xml looks like the ff:
<root>
<TemplateQuestion>
<Row rfqID="1" rftID="1" questionDesc="Question 1" responseType="1" rfqDisplayOrder="1" deletedBit="0" />
<Row rfqID="2" rftID="1" questionDesc="Question 2" responseType="2" rfqDisplayOrder="2" deletedBit="0" />
<Row rfqID="3" rftID="1" questionDesc="Question 3" responseType="3" rfqDisplayOrder="3" deletedBit="0" />
</TemplateQuestion>
</root>
Now my goal is to make the rfqID to have the letter "q" before it. So the results should be like the ff:
<root>
<TemplateQuestion>
<Row rfqID="q1" rftID="1" questionDesc="Question 1" responseType="1" rfqDisplayOrder="1" deletedBit="0" />
<Row rfqID="q2" rftID="1" questionDesc="Question 2" responseType="2" rfqDisplayOrder="2" deletedBit="0" />
<Row rfqID="q3" rftID="1" questionDesc="Question 3" responseType="3" rfqDisplayOrder="3" deletedBit="0" />
</TemplateQuestion>
</root>
I am achieving that by doing this:
declare #xml XML
set #xml = (select dbo.udfGetXMLVal(1))
declare #nodeCount int
declare #i int
declare #qid nvarchar(20)
set #i = 1
select #nodeCount = #xml.value('count(/root/TemplateQuestion/Row/#rfqID)','int')
while(#i <= #nodeCount)
begin
select #qid = x.value('#rfqID[1]', 'VARCHAR(20)')
from #xml.nodes('/root/TemplateQuestion/Row[position()=sql:variable("#i")]') e(x)
set #qid = 'q' + #qid
select #qid
Set #xml.modify('replace value of (/root/TemplateQuestion/Row/#rfqID)[1] with sql:variable("#qid")')
set #i = #i + 1
end
Im having problems with this line:
Set #xml.modify('replace value of (/root/TemplateQuestion/Row/#rfqID)[1] with sql:variable("#qid")')
How can I replace the [1] to the variable #i? I get some error with string literals when I try to use sql:variable.
Any help you could provide would be greatly appreciated. Thank you

"How can I replace the [1] to the variable #i? I get some error with string literals when I try to use sql:variable"
You can do it like this (tested and works in SQL Server 2008R2) :
Set #xml.modify('
replace value of ((/root/TemplateQuestion/Row/#rfqID)[sql:variable("#i")] )[1]
with sql:variable("#qid")
')

Quick and dirty :-)
SELECT CAST(REPLACE(CAST(#x AS VARCHAR(MAX)),' rftID="',' rftID="q') AS XML);
And here's a clean approach:
DECLARE #x XML='<root>
<TemplateQuestion>
<Row rfqID="1" rftID="1" questionDesc="Question 1" responseType="1" rfqDisplayOrder="1" deletedBit="0" />
<Row rfqID="2" rftID="1" questionDesc="Question 2" responseType="2" rfqDisplayOrder="2" deletedBit="0" />
<Row rfqID="3" rftID="1" questionDesc="Question 3" responseType="3" rfqDisplayOrder="3" deletedBit="0" />
</TemplateQuestion>
</root>';
SELECT
(
SELECT 'q' + R.value('#rfqID','varchar(max)') AS [#rfqID]
,R.value('#rftID','int') AS [#rftID]
,R.value('#questionDesc','varchar(max)') AS [#questionDesc]
--other attributes similar
FROM #x.nodes('/root/TemplateQuestion/Row') AS A(R)
FOR XML PATH('Row'),ROOT('TemplateQuestion'),TYPE
)
FOR XML PATH('root');

Related

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

Extracting attributes from XML column in SQL Server 2012

I have a column called MetaXML, which has XML stored inside. I only want to extract information relating to the URLpanel tag. How do I access the node/element down to the URLPanel tag. I also want to separate out the Cap, CaptionText, Height, HeightType, ID and URL (all contained in URLPanel). Is that possible?
Note: I've removed the schema info from the first line...
<DASHBOARD>
<COLUMNS>
<COLUMN Width="19" WidthType="Percent">
<PANELS>
<ACTIONLISTPANEL Cap="8fdd05da-c6b9-41c3-9e4d-5b661c3e134a"
CaptionText="My Action List" Height="14" HeightType="Percent"
ID="CL_Front9.2_c0_r0" />
<PROCESSPANEL Cap="11e934f8-1aed-4fd3-ad95-42049343a390" CaptionText="My
Processes" Height="19" HeightType="Percent" ID="CL_Front9.2_c0_r1">
<APPOBJECTS>
<APPOBJECT AppObjectID="CL_WfMyMattersActList" ID="8a690a0f-268c-
4eb0-8536-6ac09605b7f8" Type="Process" />
<APPOBJECT AppObjectID="CL_ModifyTimeNarrative" ID="d2f320e9-2797-
4631-b63a-5bdcb241d633" Type="Process" />
<APPOBJECT AppObjectID="Invoice Inquiry" ID="7749f30c-acdf-4c09-
b841-60026e274284" Type="Dashboard" />
</APPOBJECTS>
</PROCESSPANEL>
<DASHBOARDENTRY Cap="13d4cd47-a3cf-4222-b03b-932bd8ff6850"
CaptionText="Inquiry" DBID="CliMattInquiry2" Height="50"
HeightType="Percent" ID="CL_Front9.2_c0_r2" />
<REPORTPANEL Cap="9596ced2-76d2-4ebb-a7a3-4f2bd2450fde" CaptionText="My
Details" Height="17" HeightType="Percent" ID="CL_Front9.2_c0_r3">
<VIEWS CurrentViewID="CL_TkprStaticUser">
<VIEW Cap="5f99bd4b-1509-4a65-a54c-07bbc444aceb" CaptionText="My
Details" ReportLayoutID="CL_TkprStaticUser" Type="Report" />
</VIEWS>
</REPORTPANEL>
</PANELS>
</COLUMN>
<COLUMN Width="77" WidthType="Percent">
<PANELS>
<URLPANEL Cap="de940bd4-80f8-47f4-b721-76eed63c650f" CaptionText="Fee
Earner Dashboard" Height="100" HeightType="Percent" ID="CL_Front9.2_c1_r0"
Url="http://ccdc1sql119/ReportServer_SQLR_FINDW_PRD1/Pages/ReportViewer.aspx?%2fDWR%2fMIP+Phase+3%2fMIP_Landing_Indiv&rs:Command=Render&rc:Parameters=Collapsed" />
</PANELS>
</COLUMN>
</COLUMNS>
<SHORTLIST />
</DASHBOARD>
The XML above is quite small, but I cannot seem to extract the URLPanel tag. I've created the following SQL to try to extract the URLPanel tag:
DECLARE #DataID UNIQUEIDENTIFIER
SET #DataID = 'AEFFF874-5980-4340-92C1-ED6B292AA667'
BEGIN
SET NOCOUNT ON;
IF OBJECT_ID('tempdb..#TempXML') IS NOT NULL
DROP TABLE #TempXML
DECLARE #X XML
SET #X = (SELECT D.MetaXML AS 'MetaXML'
FROM NxFWKAppObjectData AS D
WHERE NxFWKAppObjectDataID = #DataID)
DECLARE #iX INT
EXEC sp_xml_preparedocument #ix output, #x
CREATE TABLE #TempXML (URLPanel NVARCHAR(MAX))
INSERT INTO #TempXML(URLPanel)
SELECT
URLPanel
FROM
OPENXML(#iX, '//DASHBOARD/COLUMNS/COLUMN/PANELS/URLPANEL', 2)
WITH
(URLPanel NVARCHAR(MAX))
SELECT *
FROM #TempXML AS DT
EXEC sp_xml_removedocument #iX
DROP TABLE #TempXML
END
I would recommend to use the built-in native XQuery support (instead of the legacy OPENXML approach which has its problems with memory leaks etc.).
Given your #X variable - try this:
SELECT
Cap = XC.value('#Cap', 'varchar(50)'),
CaptionText = XC.value('#CaptionText', 'varchar(250)'),
Height = XC.value('#Height', 'int'),
HeightType = XC.value('#HeightType', 'varchar(50)'),
ID = XC.value('#ID', 'varchar(50)'),
Url = XC.value('#Url', 'varchar(500)')
FROM
#X.nodes('/DASHBOARD/COLUMNS/COLUMN/PANELS/URLPANEL') AS XT(XC)
This hopefully should return the bits of data you're looking for.

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>

XQuery retrieval of a value

I have the following T-SQL that determines if a row exists using two criteria:
Declare #x xml = '
<row ParentID="45" ObjectID="0" Node="root.local.navigation[7]" itemKey="page" itemValue="Confirmation" itemType="string" />
<row ParentID="45" ObjectID="0" Node="root.local.navigation[7]" itemKey="visited" itemValue="false" itemType="bool" />'
SELECT #x.exist('/row[#Node eq "root.local.navigation[7]"] and /row[#itemValue eq "Confirmation"]')
Question: Given the above SELECT, how can I SELECT the second row's itemValue?
i.e. Since there's a row with Node="root.local.navigation[7]" and itemValue="Confirmation", return the itemType value in the row where node is the same and itemKey="visited"?
How about this:
declare #x xml = '
<row ParentID="45" ObjectID="0" Node="root.local.navigation[7]" itemKey="page" itemValue="Confirmation" itemType="string" />
<row ParentID="45" ObjectID="0" Node="root.local.navigation[7]" itemKey="visited" itemValue="false" itemType="bool" />'
select case when #x.exist('/row[#Node eq "root.local.navigation[7]"] and /row[#itemValue eq "Confirmation"]') = 1
then #x.value('/row[#Node eq "root.local.navigation[7]" and #itemKey eq "visited"][1]/#itemType', 'varchar(50)')
end as item_type

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]