XML Shredding into SQL Server - sql

I have an XML doc in the following format.
<?xml version="1.0"?>
<Survey xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>US CDB GI MMPS </Name>
<Vendor Name="TWDS" />
<Versions>
<Version Name="All Functions Incumbent Weighted Accouting Value" Year="2014">
<JobMasters>
<JobMaster JobCode="AAS000-ALL-M1" JobTitle="AAS000 Administrative Services Generalist/Multidiscipline">
<Levels>
<Level LevelCode="M1">
<JobDetails>
<JobDetail Scope="Global Revenue">
<PayComponents>
<PayComponent Id="1" Name="Base" CompanyCount="16" IncumbentCount="281" IsPercent="false">
<Type>Base</Type>
<Pays>
<Pay Id="100" Stat="Average" Value="65.6">
<StatValue>Average</StatValue>
</Pay>
<Pay Id="10" Stat="P10" Value="41">
<StatValue>P10</StatValue>
</Pay>
<Pay Id="25" Stat="P25" Value="51.6">
<StatValue>P25</StatValue>
</Pay>
<Pay Id="101" Stat="Median" Value="66.3">
<StatValue>Median</StatValue>
</Pay>
<Pay Id="75" Stat="P75" Value="74.2">
<StatValue>P75</StatValue>
</Pay>
<Pay Id="90" Stat="P90" Value="89.5">
<StatValue>P90</StatValue>
</Pay>
</Pays>
</PayComponent>
</PayComponents>
</JobDetail>
</JobDetails>
</Level>
</Levels>
</JobMaster>
</JobMasters>
<Reports>
<Report Id="0" Name="standard" Type="Published" DatePublished="2015-04-06T14:29:52.7826312-07:00">
<JobDetails />
</Report>
</Reports>
</Version>
</Versions>
</Survey>
It is much larger, but this is the format. I want to dump these records into a denormalized table, containing everything from the Survey Name through Pay StatValue. I have tried many variations of the following script, but I can only correctly retrieve the Name of the Survey. Sometimes I can get the Survey Name and all StatValues concatenated as a string, all in one field like this..
US CDB GI MMPS BaseAverageP10P25MedianP75P90, and it is not useful or expected. Anyway, here is the script I'm using
--------WORKS----------------------------
DECLARE #myXML xml = (SELECT XMLData from XMLTest)
SELECT
T.C.value('Name[1]', 'varchar(255)') as Surveys--,
--T.C.value('JobCode[1]','varchar(255)') as JobCode
FROM #myXML.nodes('(/Survey)') as T(C)
-------------DOESN'T WORK-------------------------
DECLARE #myXML xml = (SELECT XMLData from XMLTest)
SELECT
T.C.value('Name[1]','varchar(255)') as VendorName
FROM #myXML.nodes('(/Survey/Vendor)') as T(C)

Here's an idea for you. I've tried to shred data for some and you can do it for others.
DECLARE #myXML xml = '<?xml version="1.0"?>
<Survey xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>US CDB GI MMPS </Name>
<Vendor Name="TWDS" />
<Versions>
<Version Name="All Functions Incumbent Weighted Accouting Value" Year="2014">
<JobMasters>
<JobMaster JobCode="AAS000-ALL-M1" JobTitle="AAS000 Administrative Services Generalist/Multidiscipline">
<Levels>
<Level LevelCode="M1">
<JobDetails>
<JobDetail Scope="Global Revenue">
<PayComponents>
<PayComponent Id="1" Name="Base" CompanyCount="16" IncumbentCount="281" IsPercent="false">
<Type>Base</Type>
<Pays>
<Pay Id="100" Stat="Average" Value="65.6">
<StatValue>Average</StatValue>
</Pay>
<Pay Id="10" Stat="P10" Value="41">
<StatValue>P10</StatValue>
</Pay>
<Pay Id="25" Stat="P25" Value="51.6">
<StatValue>P25</StatValue>
</Pay>
<Pay Id="101" Stat="Median" Value="66.3">
<StatValue>Median</StatValue>
</Pay>
<Pay Id="75" Stat="P75" Value="74.2">
<StatValue>P75</StatValue>
</Pay>
<Pay Id="90" Stat="P90" Value="89.5">
<StatValue>P90</StatValue>
</Pay>
</Pays>
</PayComponent>
</PayComponents>
</JobDetail>
</JobDetails>
</Level>
</Levels>
</JobMaster>
</JobMasters>
<Reports>
<Report Id="0" Name="standard" Type="Published" DatePublished="2015-04-06T14:29:52.7826312-07:00">
<JobDetails />
</Report>
</Reports>
</Version>
</Versions>
</Survey>'
DECLARE #docH INT;
EXEC [sys].[sp_xml_preparedocument] #docH OUTPUT, #myXML;
SELECT *
FROM OPENXML(#docH, '/Survey/Versions/Version/JobMasters/JobMaster/Levels/Level/JobDetails/JobDetail/PayComponents/PayComponent/Pays/Pay', 3)
WITH (Stat nvarchar(50) '#Stat',
Value DECIMAL '#Value',
Id INT '#Id',
PayComponentName NVARCHAR(50) '../../#Name',
PayComponentId int '../../#Id',
JobDetailScope NVARCHAR(20) '../../../../#Scope'
)
EXEC [sys].[sp_xml_removedocument] #docH

Related

PLSQL - REGEXP_SUBSTR Remove XML Tags

I currently have a payload that was generated by oracle xml gateway that I need to pull some exact information out. The payload information is store within a db table meaning that I am attempting to us regexp_substr to accomplish this task.
This is the tag that is in the middle of the XML document
<IDCODE>S2200</IDCODE>
"<?xml version="1.0" encoding="UTF-8" standalone='no'?>
<!DOCTYPE PROCESS_INVOICE_002 SYSTEM "asfasdf.dtd">
<!-- Oracle eXtensible Markup Language Gateway Server -->
<PROCESS_INVOICE_002>
<CNTROLAREA>
<BSR>
<VERB value="PROCESS"/>
<NOUN value="INVOICE"/>
<REVISION value="002"/>
</BSR>
<SENDER>
<LOGICALID/>
<COMPONENT/>
<TASK/>
<REFERENCEID/>
<CONFIRMATION/>
<LANGUAGE/>
<CODEPAGE/>
<AUTHID/>
</SENDER>
<DATETIME qualifier="CREATION">
<YEAR/>
<MONTH/>
<DAY/>
<HOUR/>
<MINUTE/>
<SECOND/>
<SUBSECOND/>
<TIMEZONE/>
</DATETIME>
</CNTROLAREA>
<DATAAREA>
<PROCESS_INVOICE>
<INVHEADER>
<AMOUNT qualifier="DOCUMENT" type="T" index="1">
<VALUE>78538</VALUE>
<NUMOFDEC>8</NUMOFDEC>
<SIGN>+</SIGN>
<CURRENCY>USD</CURRENCY>
<DRCR>D</DRCR>
</AMOUNT>
<DATETIME qualifier="DOCUMENT" index="1">
<YEAR>2020</YEAR>
<MONTH>11</MONTH>
<DAY>28</DAY>
<HOUR>00</HOUR>
<MINUTE>00</MINUTE>
<SECOND>00</SECOND>
<SUBSECOND>0000</SUBSECOND>
<TIMEZONE>+0000</TIMEZONE>
</DATETIME>
<DOCUMENTID>81989184</DOCUMENTID>
<DESCRIPTN/>
<DOCTYPE>INV</DOCTYPE>
<PAYMETHOD/>
<REASONCODE/>
<USERAREA>
<NOTEREFCODE/>
<NOTESREF/>
<VENDNUMQUAL>IA</VENDNUMQUAL>
<VENDNUM>98181</VENDNUM>
<DEPTNUMQUAL>DP</DEPTNUMQUAL>
<DEPTNUM>85</DEPTNUM>
<ORDNUMQUAL/>
<ORDNUM>0</ORDNUM>
<CUSTCODEQUAL/>
<CUSTCODE/>
<NETDAYS/>
<DATETIMEQUAL/>
<FOBCODE/>
<UOM/>
<TOTALQUANTITY/>
</USERAREA>
<PARTNER>
<NAME index="1">COMPANY NAME</NAME>
<ONETIME/>
<PARTNRID/>
<PARTNRTYPE>Supplier</PARTNRTYPE>
<SYNCIND/>
<ACTIVE/>
<CURRENCY/>
<DESCRIPTN/>
<DUNSNUMBER/>
<GLENTITYS/>
<PARENTID/>
<PARTNRIDX/>
<PARTNRRATG/>
<PARTNRROLE/>
<PAYMETHOD/>
<TAXEXEMPT/>
<TAXID/>
<TERMID/>
<USERAREA>
<IDQUAL/>
<IDCODE/>
</USERAREA>
<CONTACT>
<NAME index="1">PROFILE</NAME>
<CONTCTTYPE/>
<DESCRIPTN/>
<EMAIL/>
<FAX index="1"/>
<TELEPHONE index="1"/>
<USERAREA/>
</CONTACT>
</PARTNER>
<PARTNER>
<NAME index="1">CUSTOMER NAME</NAME>
<ONETIME/>
<PARTNRID>981698198</PARTNRID>
<PARTNRTYPE>ShipTo</PARTNRTYPE>
<SYNCIND/>
<ACTIVE/>
<CURRENCY/>
<DESCRIPTN/>
<DUNSNUMBER/>
<GLENTITYS/>
<PARENTID/>
<PARTNRIDX/>
<PARTNRRATG/>
<PARTNRROLE/>
<PAYMETHOD/>
<TAXEXEMPT/>
<TAXID/>
<TERMID/>
<USERAREA>
<IDQUAL>ZZ</IDQUAL>
<IDCODE>S2200</IDCODE>
</USERAREA>
<ADDRESS>
<ADDRLINE index="1">123 MAIN STREET</ADDRLINE>
<ADDRTYPE/>
<CITY>HAM CITY</CITY>
<COUNTRY>United States</COUNTRY>
<COUNTY>NEW YORK</COUNTY>
<DESCRIPTN/>
<FAX index="1"/>
<POSTALCODE>18080</POSTALCODE>
<REGION/>
<STATEPROVN>NY</STATEPROVN>
<TAXJRSDCTN/>
<TELEPHONE index="1"/>
<URL/>
<USERAREA/>
</ADDRESS>
REGEX that I am using in the query
TRIM(regexp_substr(ed.payload, '?.+(</IDCODE>)')) Store_NUM,
TRIM(regexp_substr(ed.payload, '(^IDCODE)?.+(</IDCODE>)')) Store_Number
The Outcome that I am receiving from the above SQL regexp_substr. The issue is that I have made it to the correct tab but I can't figure out how to strip the \<IDCODE> and the \</IDCODE> for the output
-Field can have 4 or 5 chars
-letters or numbers
<IDCODE>S2200</IDCODE> Store_NUM
<IDCODE>S2200</IDCODE> Store_Number
I believe you are looking for this if I am understanding you correctly. Return everything in the group between the tags.
SELECT REGEXP_SUBSTR('<IDCODE>S2200</IDCODE>', '<IDCODE>(.*)</IDCODE>', 1, 1, NULL, 1) Store_Number
from dual;
STORE_NUMBER
------------
S2200
1 row selected.

Find element or attribute value anywhere in XML

I am trying to find the value of an element / attribute regardless of where it exists in the XML.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<cXML payloadID="12345677-12345567" timestamp="2017-07-26T09:11:05">
<Header>
<From>
<Credential domain="1212">
<Identity>01235 </Identity>
<SharedSecret/>
</Credential>
</From>
<To>
<Credential domain="1212">
<Identity>01234</Identity>
</Credential>
</To>
<Sender>
<UserAgent/>
<Credential domain="8989">
<Identity>10678</Identity>
<SharedSecret>Testing123</SharedSecret>
</Credential>
</Sender>
</Header>
<Request deploymentMode="Prod">
<ConfirmationRequest>
<ConfirmationHeader noticeDate="2017-07-26T09:11:05" operation="update" type="detail">
<Total>
<Money>0.00</Money>
</Total>
<Shipping>
<Description>Delivery</Description>
</Shipping>
<Comments>WO# generated</Comments>
</ConfirmationHeader>
<OrderReference orderDate="2017-07-25T15:22:11" orderID="123456780000">
<DocumentReference payloadID="5678-4567"/>
</OrderReference>
<ConfirmationItem quantity="1" lineNumber="1">
<ConfirmationStatus quantity="1" type="detail">
<ItemIn quantity="1">
<ItemID>
<SupplierPartID>R954-89</SupplierPartID>
</ItemID>
<ItemDetail>
<UnitPrice>
<Money currency="USD">0.00</Money>
</UnitPrice>
<Description>Test Descritpion 1</Description>
<UnitOfMeasure>QT</UnitOfMeasure>
</ItemDetail>
</ItemIn>
</ConfirmationStatus>
</ConfirmationItem>
<ConfirmationItem quantity="1" lineNumber="2">
<ConfirmationStatus quantity="1" type="detail">
<ItemIn quantity="1">
<ItemID>
<SupplierPartID>Y954-89</SupplierPartID>
</ItemID>
<ItemDetail>
<UnitPrice>
<Money currency="USD">0.00</Money>
</UnitPrice>
<Description>Test Descritpion 2</Description>
<UnitOfMeasure>QT</UnitOfMeasure>
</ItemDetail>
</ItemIn>
</ConfirmationStatus>
</ConfirmationItem>
</ConfirmationRequest>
</Request>
</cXML>
I want to get the value of the payloadID on the DocumentReference element. This is what I have tried so far:
BEGIN
Declare #Xml xml
Set #Xml = ('..The XML From Above..' as xml)
END
--no value comes back
Select c.value('(/*/DocumentReference/#payloadID)[0]','nvarchar(max)') from #Xml.nodes('//cXML') x(c)
--no value comes back
Select c.value('#payloadID','nvarchar(max)') from #Xml.nodes('/cXML/*/DocumentReference') x(c)
--check if element exists and it does
Select #Xml.exist('//DocumentReference');
I tried this in an xPath editor: //DocumentReference/#payloadID
This does work, but I am not sure what the equivalent syntax is in SQL
Calling .nodes() (like suggested in comment) is an unecessary overhead...
Better try it like this:
SELECT #XML.value('(//DocumentReference/#payloadID)[1]','nvarchar(max)')
And be aware, that XPath starts counting at 1. Your example with [0] cannot work...
--no value comes back
Select c.value('(/*/DocumentReference/#payloadID)[0]','nvarchar(max)') from...

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>

Get comma separated values from an xml in SQL

I am calling Scalar UDF from a stored procedure to get a column value. Inside the scalar UDF I have an xml and I have to get the comma separated values of a particular node. I used Cross apply but it caused huge performance bottleneck because stored procedure is actually used to fetch reports.
There is a table [Traveler] which has a field ID, BookingID(can be duplicate) and FareDetails. Inside the FareDetails we are storing the xml.
The logic inside UDF is as follows :
1st Solution , Using Cross Apply :
ALTER FUNCTION [dbo].[GetBookingInfo] (#BookingID bigint, #InfoID smallint) RETURNS VARCHAR(1024) AS
BEGIN
DECLARE #InfoCSV VARCHAR(1024)
--
-- Fare Basis: InfoID = 1
--
IF #InfoID = 1
BEGIN
SELECT #InfoCSV = (SELECT
(PTSD.PSTDNode.value('(FBC)[1]', 'VARCHAR(1024)') + ',') [text()]
FROM
[Traveler]
CROSS APPLY [FareDetails].nodes('/AirFareInfo/PTSDPFS/PTSD') PTSD(PSTDNode)
WHERE
[BookingID] = #BookingID
ORDER BY
ID ASC
FOR XML PATH (''))
IF #InfoCSV IS NOT NULL AND LEN(#InfoCSV) > 0
SET #InfoCSV = LEFT(#InfoCSV, LEN(#InfoCSV) - 1)
END
RETURN #InfoCSV
2nd solution , without cross apply :
ALTER FUNCTION [dbo].[GetBookingInfo] (#BookingID bigint, #InfoID smallint) RETURNS VARCHAR(1024) AS
BEGIN
DECLARE #InfoCSV VARCHAR(1024)
--
-- Fare Basis: InfoID = 1
--
IF #InfoID = 1
BEGIN
SELECT #InfoCSV = (SELECT TOP 1 REPLACE(FareDetails.query(N'data(/AirFareInfo/PTSDPFS/PTSD/FBC)').value('(text())[1]','nvarchar(100)'),' ',',')
FROM [Traveler]
WHERE
[BookingID] = #BookingID)
IF #InfoCSV IS NOT NULL AND LEN(#InfoCSV) > 0
SET #InfoCSV = LEFT(#InfoCSV, LEN(#InfoCSV) - 1)
END
RETURN #InfoCSV
The 2nd solution is saving lot of time , but when we have duplicate booking ID's then it is not concatenating all the values of FBC .
e.g. :
1)If BookingID is unique and we have FareDetail xml as follows then output should be be AP,AP
2)If BookingID is not unique(coming twice) and we have FareDetail xml as follows then output should be be AP,AP,AP,AP corresponding to both BookingID.
The xml is as follows :
<AirFareInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" IPFA="false">
<PT>Flight</PT>
<FPMID>0</FPMID>
<PTID>1</PTID>
<FS>
<CID>2</CID>
<Value>0</Value>
</FS>
<TF>
<CID xsi:nil="true" />
<Value>0</Value>
</TF>
<VF>
<CID>2</CID>
<Value>0</Value>
</VF>
<VD>
<CID>2</CID>
<Value>0</Value>
</VD>
<VCR xsi:nil="true" />
<VC>
<CID>2</CID>
<Value>0</Value>
</VC>
<VFC>
<CID>2</CID>
<Value>0</Value>
</VFC>
<VST />
<VIT />
<AAPFVDR xsi:nil="true" />
<CC>
<CID>2</CID>
<Value>0</Value>
</CC>
<D>
<CID>2</CID>
<Value>514.15</Value>
</D>
<PD>
<CID>2</CID>
<Value>0</Value>
</PD>
<EBF>
<CID>2</CID>
<Value>0</Value>
</EBF>
<CST>
<DL>
<ATRID>13</ATRID>
<OB>
<CID>2</CID>
<Value>74.04</Value>
</OB>
<OC>
<CID>2</CID>
<Value>0.00</Value>
</OC>
<OS>
<CID>2</CID>
<Value>0.00</Value>
</OS>
<OF>
<CID>2</CID>
<Value>50.83</Value>
</OF>
<OP>
<CID>2</CID>
<Value>0.00</Value>
</OP>
<C>
<CID>2</CID>
<Value>0</Value>
</C>
<IBF>false</IBF>
<D>2014-06-09T14:57:53.521Z</D>
</DL>
</CST>
<CIT />
<CRMR xsi:nil="true" />
<CRM>
<CID>2</CID>
<Value>0</Value>
</CRM>
<TL ATC="Tax" PC="" DEN="User Development Fee - Arrival (UDF)">
<TID xsi:nil="true" />
<Amount>
<CID>2</CID>
<Value>75.00</Value>
</Amount>
</TL>
<TL ATC="Tax" PC="" DEN="Passenger Service Fee">
<TID xsi:nil="true" />
<Amount>
<CID>2</CID>
<Value>146.00</Value>
</Amount>
</TL>
<TL ATC="Tax" PC="" DEN="User Development Fee - Departure (UDF)">
<TID xsi:nil="true" />
<Amount>
<CID>2</CID>
<Value>1681.00</Value>
</Amount>
</TL>
<TL ATC="Tax" PC="" DEN="Cute Fee">
<TID xsi:nil="true" />
<Amount>
<CID>2</CID>
<Value>50.00</Value>
</Amount>
</TL>
<TL ATC="Tax" PC="" DEN="Government Service Tax">
<TID xsi:nil="true" />
<Amount>
<CID>2</CID>
<Value>151.00</Value>
</Amount>
</TL>
<TL ATC="Tax" PC="" DEN="User Development Fee - Arrival (UDF)">
<TID xsi:nil="true" />
<Amount>
<CID>2</CID>
<Value>833.00</Value>
</Amount>
</TL>
<TL ATC="Tax" PC="" DEN="Passenger Service Fee">
<TID xsi:nil="true" />
<Amount>
<CID>2</CID>
<Value>1132.00</Value>
</Amount>
</TL>
<TL ATC="Tax" PC="" DEN="User Development Fee - Departure (UDF)">
<TID xsi:nil="true" />
<Amount>
<CID>2</CID>
<Value>76.00</Value>
</Amount>
</TL>
<TL ATC="Tax" PC="" DEN="Government Service Tax">
<TID xsi:nil="true" />
<Amount>
<CID>2</CID>
<Value>148.00</Value>
</Amount>
</TL>
<PTSDPFS>
<PTSD IO="false">
<FBC>AP</FBC>
<ACD RBD="" ACCID="1" MCT="Super Sale Fare(AP)" INC="false" />
<ATSID xsi:nil="true" />
</PTSD>
</PTSDPFS>
<PTSDPFS>
<PTSD IO="false">
<FBC>AP</FBC>
<ACD RBD="" ACCID="1" MCT="Super Sale Fare(AP)" INC="false" />
<ATSID xsi:nil="true" />
</PTSD>
</PTSDPFS>
<RuleDetails>
<TRS xsi:nil="true" />
<PP xsi:nil="true" />
<II xsi:nil="true" />
<LTD xsi:nil="true" />
</RuleDetails>
</AirFareInfo>
Please suggest how it can be done keeping performance in mind.
This is a fully working example.
You told us, that performance matters, so do not use scalar UDF!
Try it like this (next time it's your job to create a (reduced!!!) MCVE:
CREATE DATABASE testDB;
GO
USE testDB;
GO
CREATE TABLE Booking(BookingID INT CONSTRAINT PK_Booking PRIMARY KEY
,SomeBookingData VARCHAR(100));
INSERT INTO Booking VALUES(1,'Booking 1'),(2,'Booking 2');
CREATE TABLE BookingInfo(BookingID INT CONSTRAINT FK_BookingInfo_BookingID FOREIGN KEY REFERENCES Booking(BookingID)
,SomeOtherInfo VARCHAR(100)
,FareDetails XML);
INSERT INTO BookingInfo VALUES
(1,'First row for ID=1, returns AP,AP'
,N'<AirFareInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" IPFA="false">
<PTSDPFS>
<PTSD IO="false">
<FBC>AP</FBC>
</PTSD>
</PTSDPFS>
<PTSDPFS>
<PTSD IO="false">
<FBC>AP</FBC>
</PTSD>
</PTSDPFS>
</AirFareInfo>')
,(1,'Second row for ID=1, returns XY,MN'
,N'<AirFareInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" IPFA="false">
<PTSDPFS>
<PTSD IO="false">
<FBC>XY</FBC>
</PTSD>
</PTSDPFS>
<PTSDPFS>
<PTSD IO="false">
<FBC>MN</FBC>
</PTSD>
</PTSDPFS>
</AirFareInfo>')
,(2,'row with ID=2, returns AA,BB'
,N'<AirFareInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" IPFA="false">
<PTSDPFS>
<PTSD IO="false">
<FBC>AA</FBC>
</PTSD>
</PTSDPFS>
<PTSDPFS>
<PTSD IO="false">
<FBC>BB</FBC>
</PTSD>
</PTSDPFS>
</AirFareInfo>');
GO
--This is the function. It returns as table and is fully inlined (no BEGIN...END!)
CREATE FUNCTION dbo.CreateBookingInfoCSV(#BookingID INT)
RETURNS TABLE
AS
RETURN
SELECT STUFF(
(
SELECT ','+REPLACE(FareDetails.query(N'data(/AirFareInfo/PTSDPFS/PTSD/FBC)').value(N'.',N'nvarchar(max)'),' ',',')
FROM BookingInfo AS bi
WHERE bi.BookingID=#BookingID
FOR XML PATH('')
),1,1,'') AS BookingInfoCSV;
GO
--Hint the trick with XQuery data() function will break, if your values contain blanks!
--The following SELECT calls all rows from Booking-table and gets the fitting details
SELECT b.BookingID
,b.SomeBookingData
,A.BookingInfoCSV
FROM Booking AS b
OUTER APPLY dbo.CreateBookingInfoCSV(b.BookingID) AS A;
GO
--Clean up (carefull with real data!)
USE master;
GO
DROP DATABASE testDB;
--The result
BookingID SomeBookingData BookingInfoCSV
1 Booking 1 AP,AP,XY,MN
2 Booking 2 AA,BB

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]