Get comma separated values from an xml in SQL

浪子不回头ぞ 提交于 2019-12-02 16:00:51

问题


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.


回答1:


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


来源:https://stackoverflow.com/questions/42777070/get-comma-separated-values-from-an-xml-in-sql

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!