Select value from XML node where conditions are met - sql-server

I have the following xml which I want to query to turn into a flat table structure.
<RiskBoundReportProcess>
<RiskBoundReport>
<Policy key="Pol126446">
<LineOfBusinessCode>ISR</LineOfBusinessCode>
<AssignedIdentifier>
<RoleCode>Insured</RoleCode>
<Id>BP-1438</Id>
</AssignedIdentifier>
<RiskParticipationPercentIndicator>true</RiskParticipationPercentIndicator>
<PolicySection>
<PolicyProducer>
<RoleCode>Coverholder</RoleCode>
<Producer>
<OrganizationReferences organizationReference="Coverholder"/>
</Producer>
</PolicyProducer>
<PolicyProducer>
<RoleCode>Underwriter</RoleCode>
<Producer>
<Contact>
<PersonReferences>
<PersonName>
<FullName>Name</FullName>
</PersonName>
</PersonReferences>
</Contact>
</Producer>
</PolicyProducer>
<PolicyProducer>
<RoleCode>Broker</RoleCode>
<AssignedIdentifier>
<RoleCode>Broker</RoleCode>
<Id>112</Id>
</AssignedIdentifier>
<Producer>
<OrganizationReferences organizationReference="BrokerOrg112"/>
</Producer>
</PolicyProducer>
<PolicyProducer>
<RoleCode>LondonBroker</RoleCode>
<ProducerReferences producerReference="Binder1">
<ExternalIdentifier>
<TypeCode>UniqueMarketReference</TypeCode>
<Id>UMRGoesHere</Id>
</ExternalIdentifier>
<OrganizationReferences>
<OrganizationName>
<FullName>Partners Ltd</FullName>
</OrganizationName>
</OrganizationReferences>
<BindingAuthorityPeriod>
<StartDate>2014-05-01</StartDate>
<EndDate>2015-04-30</EndDate>
</BindingAuthorityPeriod>
</ProducerReferences>
<RiskParticipationPercent>1.0000</RiskParticipationPercent>
</PolicyProducer>
</PolicySection>
</Policy>
</RiskBoundReport>
</RiskBoundReportProcess>
So far I have two columns. I now want to create a column for UniqueMarketReference which is found in the following node
RiskBoundReportProcess/RiskBoundReport/Policy/PolicySection/PolicyProducer[RoleCode="LondonBroker"]/ProducerReferences/ExternalIdentifier/[TypeCode="UniqueMarketReference"]/Id
How do I modifiy the SQL below to include this?
SELECT
Policy.value('#key', 'VARCHAR(50)') AS PolicyId,
Policy.value('LineOfBusinessCode[1]', 'VARCHAR(50)') AS LineOfBusinessCode
FROM #ACORDXML.nodes('/RiskBoundReportProcess/RiskBoundReport') AS RiskBoundReport(Nodes)
CROSS APPLY RiskBoundReport.Nodes.nodes('Policy') AS Policies(Policy)
I will eventually return The Insured, Coverholer and Underwriter as columns too.

You were very close... The XPath you provided has just one / before [TypeCode ...], which is to much.
Try it like this:
SELECT
Policy.value(N'#key', N'VARCHAR(50)') AS PolicyId
,Policy.value(N'LineOfBusinessCode[1]', N'VARCHAR(50)') AS LineOfBusinessCode
,Policy.value(N'(/RiskBoundReportProcess/RiskBoundReport/Policy/PolicySection
/PolicyProducer[RoleCode="LondonBroker"]
/ProducerReferences/ExternalIdentifier[TypeCode="UniqueMarketReference"]/Id/text())[1]','VARCHAR(50)') AS LondonBroker
,Policy.value(N'(/RiskBoundReportProcess/RiskBoundReport/Policy/PolicySection
/PolicyProducer[RoleCode="Underwriter"]
/Producer/Contact/PersonReferences/PersonName/FullName/text())[1]','VARCHAR(50)') AS Underwriter
FROM #ACORDXML.nodes('/RiskBoundReportProcess/RiskBoundReport') AS RiskBoundReport(Nodes)
CROSS APPLY RiskBoundReport.Nodes.nodes('Policy') AS Policies(Policy);
Some of your nodes look like 1:n-related (<PersonReferences><PersonName>...). This might need some extra effort (.nodes())...

Related

Retrieve associated value from next node for each tag

I have the following XML:
<Envelope format="ProceedoOrderTransaction2.1">
<Sender>SENDER</Sender>
<Receiver>RECEIVER</Receiver>
<EnvelopeID>xxxxx</EnvelopeID>
<Date>2021-05-06</Date>
<Time>11:59:46</Time>
<NumberOfOrder>1</NumberOfOrder>
<Order>
<Header>
<OrderNumber>POXXXXX</OrderNumber>
</Header>
<Lines>
<Line>
<LineNumber>1</LineNumber>
<ItemName>Ipsum Lorum</ItemName>
<SupplierArticleNumber>999999</SupplierArticleNumber>
<UnitPrice vatRate="25.0">50</UnitPrice>
<UnitPriceBasis>1</UnitPriceBasis>
<OrderedQuantity unit="Styck">200</OrderedQuantity>
<AdditionalItemProperty Key="ARTIKELNUMMER" Description="Unik ordermärkning (artikelnummer):" />
<Value>999999</Value>
<AdditionalItemProperty Key="BESKRIVNING" Description="Kort beskrivning:" />
<Value>Ipsum Lorum</Value>
<AdditionalItemProperty Key="BSKRIVNING" Description="Beskrivning:" />
<Value>Ipsum Lorum</Value>
<AdditionalItemProperty Key="ENHET" Description="Enhet:" />
<Value>Styck</Value>
<AdditionalItemProperty Key="KVANTITET" Description="Kvantitet:" />
<Value>200</Value>
<AdditionalItemProperty Key="PRIS" Description="Pris/Enhet (ex. moms):" />
<Value>50</Value>
<AdditionalItemProperty Key="VALUTA" Description="Valuta:" />
<Value>SEK</Value>
<Accounting>
<AccountingLine amount="10000">
<AccountingValue dimensionPosition="001" dimensionExternalID="ACCOUNT">xxx</AccountingValue>
<AccountingValue dimensionPosition="002" dimensionExternalID="F1">Ipsum Lorum</AccountingValue>
<AccountingValue dimensionPosition="005" dimensionExternalID="F3">1</AccountingValue>
<AccountingValue dimensionPosition="010" dimensionExternalID="F2">9999</AccountingValue>
</AccountingLine>
</Accounting>
</Line>
</Lines>
</Order>
</Envelope>
I am able to parse out all values correctly to table structure except for 1 value in a way that ensures its it associated with its tag. So where I stumble is that I am correctly getting 1 row per AdditionalItemProperty and I am able to get the Key and Description tag values, for example BESKRIVNING and Kort beskrivning:, but I can't (in a reasonable way) get the value between <Value> </Value> brackets that is also associated with each tag value. So for tag key value BESKRIVNING the associated value is 99999 which seem to be on same hierarchy level (insane I know) as the AdditionalItemProperty it is associated with. Seems like they use logic that value for a AdditionalItemProperty will be following the AdditionalItemProperty tag.
I am using SQL Server 2019. I have gotten this far:
-- Purchaseorderrowattributes
select top(10)
i.value(N'(./Header/OrderNumber/text())[1]', 'nvarchar(30)') as OrderNumber,
ap.value(N'(../LineNumber/text())[1]', 'nvarchar(30)') as LineNumber,
ap.value(N'(#Description)', 'nvarchar(50)') property_description
from
load.proceedo_orders t
outer apply
t.xml_data.nodes('Envelope/Order') AS i(i)
outer apply
i.nodes('Lines/Line/AdditionalItemProperty') as ap(ap)
where
file_name = #filename
Which produces the following output:
OrderNumber LineNumber property_description
--------------------------------------------
PO170006416 1 Antal timmar
PO170006416 1 Beskrivning
PO170006416 1 Kompetensområde
PO170006416 1 Roll
PO170006416 1 Ordernummer
PO170006416 1 Timpris
I can't find a way to add the value for each property in a correct way. Since the ordering of the values will always be same as the ordering of the AdditionalItemProperty i found solution to get ordering of the AdditionalItemProperty and then i could use rownumber and i was then hoping to input the rownumber value into the bracket in
ap.value(N'(../Value/text())[1]', 'nvarchar(50)') property_description
but SQL Server throws exception that it has to be string literal.
So to be clear what I tried doing with something like:
ap.value(CONCAT( N'(../Value/text())[', CAST(ROWNUMBER as varchar) ,']'), 'nvarchar(50)') property_description
SQL Server uses XQuery 1.0.
You can make use of the >> Node Comparison operator to find the Value sibling node with code similar to the following:
-- Purchaseorderrowattributes
select top(10)
i.value(N'(./Header/OrderNumber/text())[1]', 'nvarchar(30)') as OrderNumber
,ap.value(N'(../LineNumber/text())[1]', 'nvarchar(30)') as LineNumber
,ap.value(N'(#Description)', 'nvarchar(50)') property_description
,ap.query('
let $property := .
return (../Value[. >> $property][1])/text()
').value('.[1]', 'varchar(20)') as property_value
from load.proceedo_orders t
outer apply t.xml_data.nodes('Envelope/Order') AS i(i)
outer apply i.nodes('Lines/Line/AdditionalItemProperty') as ap(ap)
where file_name = #filename
So what's going on here?
let $property := . is creating a reference to the current AdditionalItemProperty node.
../Value[. >> $property] is ascending to the parent node, Line, and descending again to find Value nodes after the AditionalItemProperty node reference in document order, with [1] selecting the first one of those nodes.
See the 3.5.3 Node Comparisons section for a little more detail.

SQL Server - XQuery: delete parent node based on child value substring

I want to delete all parent nodes TxDtls of the following XML where position 20 of child value Ref is 2.
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.054.001.04" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:camt.054.001.04 camt.054.001.04.xsd">
<BkToCstmrDbtCdtNtfctn>
<Ntfctn>
<Ntry>
<TtlChrgsAndTaxAmt Ccy="CHF">1.60</TtlChrgsAndTaxAmt>
<Rcrd>
<Amt Ccy="CHF">1.60</Amt>
<CdtDbtInd>DBIT</CdtDbtInd>
<ChrgInclInd>false</ChrgInclInd>
<Tp>
<Prtry>
<Id>2</Id>
</Prtry>
</Tp>
</Rcrd>
</Chrgs>
<NtryDtls>
<TxDtls>
<RmtInf>
<Strd>
<CdtrRefInf>
<Ref>111118144400000000020076766</Ref>
</CdtrRefInf>
</Strd>
</RmtInf>
</TxDtls>
<TxDtls>
<RmtInf>
<Strd>
<CdtrRefInf>
<Ref>111117645600000000030076281</Ref>
</CdtrRefInf>
</Strd>
</RmtInf>
</TxDtls>
</NtryDtls>
</Ntry>
</Ntfctn>
</BkToCstmrDbtCdtNtfctn>
</Document>
So I want to delete the first TxDtls node (substring position 20 = 2) while I want to keep the second one (substring position 20 <> 2).
I tried this:
UPDATE mytable SET XMLData.modify('delete .//TxDtls[RmtInf/Strd/CdtrRefInf/Ref/substring(text(),20,1) = ''2'']')
However, I get the error "The XQuery syntax '/function()' is not supported". Any hints on how to achieve this?
Thanks
What a difference made by the partially provided minimal reproducible example.
The XML is still not well-formed. I had to comment out the following tag: </Chrgs>
A default namespace is easily handled by its declaration in the XQuery method.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata) VALUES
(N'<?xml version="1.0"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.054.001.04"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:camt.054.001.04 camt.054.001.04.xsd">
<BkToCstmrDbtCdtNtfctn>
<Ntfctn>
<Ntry>
<TtlChrgsAndTaxAmt Ccy="CHF">1.60</TtlChrgsAndTaxAmt>
<Rcrd>
<Amt Ccy="CHF">1.60</Amt>
<CdtDbtInd>DBIT</CdtDbtInd>
<ChrgInclInd>false</ChrgInclInd>
<Tp>
<Prtry>
<Id>2</Id>
</Prtry>
</Tp>
</Rcrd>
<!--</Chrgs>-->
<NtryDtls>
<TxDtls>
<RmtInf>
<Strd>
<CdtrRefInf>
<Ref>111118144400000000020076766</Ref>
</CdtrRefInf>
</Strd>
</RmtInf>
</TxDtls>
<TxDtls>
<RmtInf>
<Strd>
<CdtrRefInf>
<Ref>111117645600000000030076281</Ref>
</CdtrRefInf>
</Strd>
</RmtInf>
</TxDtls>
</NtryDtls>
</Ntry>
</Ntfctn>
</BkToCstmrDbtCdtNtfctn>
</Document>');
-- DDL and sample data population, end
-- before
SELECT * FROM #tbl;
UPDATE #tbl SET xmldata.modify('declare default element namespace "urn:iso:std:iso:20022:tech:xsd:camt.054.001.04";
delete /Document/BkToCstmrDbtCdtNtfctn/Ntfctn/Ntry/NtryDtls/TxDtls[RmtInf/Strd/CdtrRefInf/Ref[substring(./text()[1],20,1) = "2"]]');
-- after
SELECT * FROM #tbl;

Pull all of the child XML nodes in SQL Server

I have a SQL Server 2014 database that stores 2 million XML files. The XML file looks like:
<?xml version='1.0' encoding='UTF-16'?>
<PROJECTS xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
<row>
<APPLICATION_ID>7975883</APPLICATION_ID>
<ACTIVITY>N01</ACTIVITY>
<ADMINISTERING_IC>HL</ADMINISTERING_IC>
<APPLICATION_TYPE xsi:nil="true"/>
<ARRA_FUNDED>N</ARRA_FUNDED>
<PIS>
<PI>
<PI_NAME>MICHEL, MARY Q</PI_NAME>
<PI_ID>3704353</PI_ID>
</PI>
<PI>
<PI_NAME>SMITH, ROBERT B</PI_NAME>
<PI_ID>3704354</PI_ID>
</PI>
<PI>
<PI_NAME>DOE, JOHN A</PI_NAME>
<PI_ID>3704353</PI_ID>
</PI>
</PIS>
<ORG_DUNS>600044978</ORG_DUNS>
<ORG_COUNTRY>UNITED STATES</ORG_COUNTRY>
<ORG_DISTRICT>08</ORG_DISTRICT>
<ORG_ZIPCODE>208523003</ORG_ZIPCODE>
</row>
</PROJECTS>
My problem is that I want to pull all of the PI values based upon the ORG_DUNS numbers in a stored procedure. So the code that I have is:
SELECT
APPLICATION_ID,
nref.value('.','varchar(max)') TERM
INTO
ADMIN_MUSC_RePORTER_TERMS
FROM
[ADMIN_Grant_Exporter_Files_XML]
CROSS APPLY
XMLData.nodes('//PIS/PI') AS R(nref)
WHERE
RECID = 1
And that work fine when I use a WHERE cause based up another field in the database but if I need to reference a node in the xml file that's where I'm having a problem. I need to pull all the the XML files that have ORG_DUNS equal to 600044978 and I know that the nref.value('ORG_DUNS[1]', 'varchar(max)') does not exist because of the cross apply.
SELECT
APPLICATION_ID,
nref.value('.','varchar(max)') TERM
INTO
ADMIN_MUSC_RePORTER_TERMS
FROM
[ADMIN_Grant_Exporter_Files_XML]
CROSS APPLY
XMLData.nodes('//PIS/PI') as R(nref)
WHERE
nref.value('ORG_DUNS[1]', 'varchar(max)') = '600044978'
So how can I get all of the PI nodes using the ORG_DUNS as my WHERE? Thanks
Change your Cross Apply statement to include the filter logic in the XPath:
CROSS APPLY XMLData.nodes('//PIS[../ORG_DUNS/text() = ''600044978'']/PI') AS R(nref)
To explain, //PIS[../ORG_DUNS/text() = ''600044978'']/PI says:
//PIS - find all elements called PIS
[...] - filter the returned elements for those matching this condition
../ORG_DUNS/text() = ''600044978'' - Condition: the PIS's parent element's ORG_DUNS's text value equals 600044978.
Then return the child PI elements of any matching PIS elements.
Update per comments
Full SQL, including PI and PI_ID as separate values:
SELECT
APPLICATION_ID
, nref.value('(./PI_NAME/text())[1]','varchar(max)') PI
, nref.value('(./PI_ID/text())[1]','varchar(max)') PI_ID
INTO
ADMIN_MUSC_RePORTER_TERMS
FROM
[ADMIN_Grant_Exporter_Files_XML]
CROSS APPLY
XMLData.nodes('//PIS[../ORG_DUNS/text() = ''600044978'']/PI') AS R(nref)
WHERE
RECID = 1
Notes:
./PI_NAME - from the currently selected element (i.e. the one refered to by the nref column; which is pointing at a PI element), take its child element, PI_NAME.
/text() - from the PI_NAME element, take its child text element (strictly this is not required, since when pulling back the value & converting to an varchar we'd get the same result; but I like to be explicit).
(...)[1] - return a singleton. i.e. we only want 1 value back, even if there were multiple PI_NAME elements under the current PI. By putting brackets around our expression we're saying "for all values returned by this expression"; and [1] says take the first result (since XPATH uses one-based indexes rather than zero-based as most other languages would).
Filtering with a variable
Anticipating your next issue; i.e. "How do you change the number at runtime without building dynamic SQL?", the answer's to use the sql:variable function:
declare #DunningNumber int = 600044978 --9 digit code http://www.dnb.com/duns-number.html; so can easily hold in an int: https://learn.microsoft.com/en-us/sql/t-sql/data-types/int-bigint-smallint-and-tinyint-transact-sql
SELECT
APPLICATION_ID
, nref.value('(./PI_NAME/text())[1]','varchar(max)') PI
, nref.value('(./PI_ID/text())[1]','varchar(max)') PI_ID
INTO
ADMIN_MUSC_RePORTER_TERMS
FROM
[ADMIN_Grant_Exporter_Files_XML]
CROSS APPLY
XMLData.nodes('//PIS[../ORG_DUNS/text() = sql:variable("#DunningNumber")]/PI') AS R(nref)
WHERE
RECID = 1
The trick here is to first grab multiple levels in the document using multiple CROSS APPLY clauses. So first, starting from the root grab all the '/PROJECTS/row' and then use a relative path from each of those 'PIS/PI'.
Like this:
declare #t table(id int identity, APPLICATION_ID int default (2), XmlData xml)
insert into #t(XmlData) values (
'<PROJECTS xmlns:xsi=''http://www.w3.org/2001/XMLSchema-instance''>
<row>
<APPLICATION_ID>7975883</APPLICATION_ID>
<ACTIVITY>N01</ACTIVITY>
<ADMINISTERING_IC>HL</ADMINISTERING_IC>
<APPLICATION_TYPE xsi:nil="true"/>
<ARRA_FUNDED>N</ARRA_FUNDED>
<PIS>
<PI>
<PI_NAME>MICHEL, MARY Q</PI_NAME>
<PI_ID>3704353</PI_ID>
</PI>
<PI>
<PI_NAME>SMITH, ROBERT B</PI_NAME>
<PI_ID>3704354</PI_ID>
</PI>
<PI>
<PI_NAME>DOE, JOHN A</PI_NAME>
<PI_ID>3704353</PI_ID>
</PI>
</PIS>
<ORG_DUNS>600044978</ORG_DUNS>
<ORG_COUNTRY>UNITED STATES</ORG_COUNTRY>
<ORG_DISTRICT>08</ORG_DISTRICT>
<ORG_ZIPCODE>208523003</ORG_ZIPCODE>
</row>
</PROJECTS> '),(
'<PROJECTS xmlns:xsi=''http://www.w3.org/2001/XMLSchema-instance''>
<row>
<APPLICATION_ID>7975883</APPLICATION_ID>
<ACTIVITY>N01</ACTIVITY>
<ADMINISTERING_IC>HL</ADMINISTERING_IC>
<APPLICATION_TYPE xsi:nil="true"/>
<ARRA_FUNDED>N</ARRA_FUNDED>
<PIS>
<PI>
<PI_NAME>MICHEL, MARY Q</PI_NAME>
<PI_ID>3704353</PI_ID>
</PI>
<PI>
<PI_NAME>SMITH, ROBERT B</PI_NAME>
<PI_ID>3704354</PI_ID>
</PI>
<PI>
<PI_NAME>DOE, JOHN A</PI_NAME>
<PI_ID>3704353</PI_ID>
</PI>
</PIS>
<ORG_DUNS>600044979</ORG_DUNS>
<ORG_COUNTRY>UNITED STATES</ORG_COUNTRY>
<ORG_DISTRICT>08</ORG_DISTRICT>
<ORG_ZIPCODE>208523003</ORG_ZIPCODE>
</row>
</PROJECTS> ')
select APPLICATION_ID,
pinode.value('PI_NAME[1]','varchar(max)') PI_NAME,
pinode.value('PI_ID[1]','varchar(max)') PI_ID
FROM #t
cross apply XMLData.nodes('/PROJECTS/row') as r(rownode)
cross apply rownode.nodes('PIS/PI') as p(pinode)
where rownode.value('ORG_DUNS[1]','varchar(max)') = '600044978'

Querying (a lot of) XML data for specific text with in xml Elements (Tsql)

Sorry: this post got a little long (wanted to make sure everything relevant was in)
Struggling to wrap my brain around this a little.
I have a table that has 5 columns
ID (varchar)
Record value(XML)
date (datetime)
applicationtypeid (int)
applicationstatusid(int)
XML contains alot of data but part i'm interested in looks like this.
<GoodsAndServicesItemSummaryViewModelItems>
<GoodsAndServicesMasterViewModel>
<Id>1</Id>
<ClassId>1</ClassId>
<TermsText>text</TermsText>
<TermsCreationType>ManuallyEntered</TermsCreationType>
<Terms />
Specifically the "TermsCreationType" element. In it can be one of the following 3 strings:
ManuallyEntered
CopyFromExistingMarks
CopyFromPreapprovedTermsDatabase
Depending on what the customer files there could also be a mixture of all three and they could contain as many of these mixtures as they theoretically wanted i.e.:
</PriorityClaimAdditionalDetailWizardStepViewModel>
<GoodsAndServicesMasterViewModel>
<Id>9</Id>
<ClassId>2</ClassId>
<TermsText>texty</TermsText>
<TermsCreationType>ManuallyEntered</TermsCreationType>
<Terms />
</GoodsAndServicesMasterViewModel>
<GoodsAndServicesMasterViewModel>
<Id>10</Id>
<ClassId>1</ClassId>
<TermsText>text</TermsText>
<TermsCreationType>ManuallyEntered</TermsCreationType>
<Terms />
</GoodsAndServicesMasterViewModel>
</GoodsAndServicesItemSummaryViewModelItems>
<GoodsAndServicesItemSummaryViewModelItemsUnMerged>
<GoodsAndServicesMasterViewModel>
<Id>9</Id>
<ClassId>9</ClassId>
<TermsText>test</TermsText>
<TermsCreationType>CopyFromExistingMarks</TermsCreationType>
<Terms />
</GoodsAndServicesMasterViewModel>
I'm trying to find a count of how many records from a certain date range that
ONLY contain one of the aforementioned (whether it appears once or even 50 times as long as that's the only value in that element)
Records that contain any mixture of the three.
My attempt so far, for the "one element only" is thus:
SELECT Count (id) [pre-approved only]
FROM [TMWebForms].[dbo].[webformapplication]
WHERE trademarkid NOT IN (SELECT id
FROM [TMWebForms].[dbo].[webformapplication]
WHERE applicationtypeid = '5'
AND createddate BETWEEN '2016-08-01' AND '2016-08-31'
AND RECORDDATA.value('contains((//GoodsAndServicesWizardStepViewModel/GoodsAndServicesItemSummaryViewModelItems/GoodsAndServicesMasterViewModel/TermsCreationType/text())[1], "ManuallyEntered")', 'bit') = 1
AND applicationstatusid = 50
AND applicationtypeid = 5)
AND id NOT IN (SELECT id
FROM [TMWebForms].[dbo].[webformapplication]
WHERE applicationtypeid = '5'
AND createddate BETWEEN '2016-08-01' AND '2016-08-31'
AND RECORDDATA.value('contains((//GoodsAndServicesWizardStepViewModel/GoodsAndServicesItemSummaryViewModelItems/GoodsAndServicesMasterViewModel/TermsCreationType/text())[1], "CopyFromExistingMark")', 'bit') = 1
AND applicationstatusid = 50
AND applicationtypeid = 5)
AND createddate BETWEEN '2016-08-01' AND '2016-08-31'
AND RECORDDATA.value('contains((//GoodsAndServicesWizardStepViewModel/GoodsAndServicesItemSummaryViewModelItems/GoodsAndServicesMasterViewModel/TermsCreationType/text())[1], "CopyFromPreapprovedTermsDatabase")', 'bit') = 1
AND applicationstatusid = 50
AND applicationtypeid = 5
This is long winded and not the best performance wise by a long shot! (i thought about converting it to a string then using "like" - but that is probably just as bad, if not worse)
It doesn't pull exactly what i wanted back. To me it is searching only for "CopyFromPreapprovedTermsDatabase", but when interrogating the data, There are a few cases where this is the first line but, i can also see "manuallyentered" exists.
Any help appreciated here!
Try it like this
(I had to add a root and add some opening and closing tags)
DECLARE #xml XML=
N'<SomeRoot>
<GoodsAndServicesItemSummaryViewModelItems>
<GoodsAndServicesMasterViewModel>
<Id>9</Id>
<ClassId>2</ClassId>
<TermsText>texty</TermsText>
<TermsCreationType>ManuallyEntered</TermsCreationType>
<Terms />
</GoodsAndServicesMasterViewModel>
<GoodsAndServicesMasterViewModel>
<Id>10</Id>
<ClassId>1</ClassId>
<TermsText>text</TermsText>
<TermsCreationType>ManuallyEntered</TermsCreationType>
<Terms />
</GoodsAndServicesMasterViewModel>
</GoodsAndServicesItemSummaryViewModelItems>
<GoodsAndServicesItemSummaryViewModelItemsUnMerged>
<GoodsAndServicesMasterViewModel>
<Id>9</Id>
<ClassId>9</ClassId>
<TermsText>test</TermsText>
<TermsCreationType>CopyFromExistingMarks</TermsCreationType>
<Terms />
</GoodsAndServicesMasterViewModel>
</GoodsAndServicesItemSummaryViewModelItemsUnMerged>
</SomeRoot>';
DECLARE #tbl TABLE(ID VARCHAR(10),Record_Value XML,[date] DATETIME,applicationtypeid INT,applicationstautsid INT);
INSERT INTO #tbl VALUES('SomeTest',#xml,GETDATE(),11,22);
--The CTE will select all columns and add the actual count of the TermsCreationType with the given text()
WITH CTE AS
(
SELECT *
,Record_Value.value('count(//TermsCreationType[text()="ManuallyEntered"])','int') AS CountManuallyEntered
,Record_Value.value('count(//TermsCreationType[text()="CopyFromExistingMarks"])','int') AS CountCopyFromExistingMarks
,Record_Value.value('count(//TermsCreationType[text()="CopyFromPreapprovedTermsDatabase"])','int') AS CountCopyFromPreapprovedTermsDatabase
FROM #tbl
)
--The final SELECT uses as big CASE WHEN hierarchy to analyse the counts
SELECT *
,CASE WHEN CTE.CountManuallyEntered=0 AND CTE.CountCopyFromExistingMarks=0 AND CTE.CountCopyFromPreapprovedTermsDatabase=0 THEN 'None'
ELSE
CASE WHEN CTE.CountManuallyEntered>0 AND CTE.CountCopyFromExistingMarks=0 AND CTE.CountCopyFromPreapprovedTermsDatabase=0 THEN 'Manually'
ELSE
CASE WHEN CTE.CountManuallyEntered=0 AND CTE.CountCopyFromExistingMarks>0 AND CTE.CountCopyFromPreapprovedTermsDatabase=0 THEN 'Existing'
ELSE
CASE WHEN CTE.CountManuallyEntered=0 AND CTE.CountCopyFromExistingMarks=0 AND CTE.CountCopyFromPreapprovedTermsDatabase>0 THEN 'Preapproved'
ELSE
'Mixed'
END
END
END
END AS CountAnalysis
FROM CTE;

flattening xml data in sql server

I have the following xml data in a xml column (not typed) called xml_response
<SEIContent>
<Request>
<eq:Charge>
<v:Type>MaintCharge</v:Type>
<v:Term>0</v:Term>
<v:StartMonth>0</v:StartMonth>
<v:EndMonth>0</v:EndMonth>
<v:Rate>0</v:Rate>
<v:RebateRatio>0</v:RebateRatio>
<v:MaxRebate>0</v:MaxRebate>
<v:TieredCharge>
<v:Term>0</v:Term>
<v:Rate>0.75</v:Rate>
<v:LowerBand>0</v:LowerBand>
<v:UpperBand>249999.99</v:UpperBand>
<v:BandCurrency>GBP</v:BandCurrency>
</v:TieredCharge>
<v:TieredCharge>
<v:Term>0</v:Term>
<v:Rate>0.7</v:Rate>
<v:LowerBand>250000</v:LowerBand>
<v:UpperBand>499999.99</v:UpperBand>
<v:BandCurrency>GBP</v:BandCurrency>
</v:TieredCharge>
<v:TieredCharge>
<v:Term>0</v:Term>
<v:Rate>0.6</v:Rate>
<v:LowerBand>500000</v:LowerBand>
<v:UpperBand>999999.99</v:UpperBand>
<v:BandCurrency>GBP</v:BandCurrency>
</v:TieredCharge>
<v:TieredCharge>
<v:Term>0</v:Term>
<v:Rate>0.5</v:Rate>
<v:LowerBand>1000000</v:LowerBand>
<v:UpperBand>9999999.99</v:UpperBand>
<v:BandCurrency>GBP</v:BandCurrency>
</v:TieredCharge>
</eq:Charge>
<eq:Charge>
<v:Type>MaintCharge</v:Type>
<v:Term>0</v:Term>
<v:StartMonth>0</v:StartMonth>
<v:EndMonth>59</v:EndMonth>
<v:Rate>1.5</v:Rate>
<v:RebateRatio>0</v:RebateRatio>
<v:MaxRebate>0</v:MaxRebate>
</eq:Charge>
<eq:Charge>
<v:Type>MaintCharge</v:Type>
<v:Term>0</v:Term>
<v:StartMonth>60</v:StartMonth>
<v:EndMonth>0</v:EndMonth>
<v:Rate>0.5</v:Rate>
<v:RebateRatio>0</v:RebateRatio>
<v:MaxRebate>0</v:MaxRebate>
</eq:Charge>
<eq:Charge>
<v:Type>QAC</v:Type>
<v:Basis>FixedAmount</v:Basis>
<v:Term>0</v:Term>
<v:StartMonth>0</v:StartMonth>
<v:EndMonth>0</v:EndMonth>
<v:Rate>105</v:Rate>
<v:RebateRatio>0</v:RebateRatio>
<v:MaxRebate>0</v:MaxRebate>
</eq:Charge>
</Request>
<Response>
<eq:Ref>QV00000393</eq:Ref>
</Response>
</SEIContent>
So you'll notice that some charges contain a repeating element TieredCharge and some don't
I've written the following query:
WITH XMLNAMESPACES('http://lu/blah' AS eq, 'http://lu/blah2' AS v,
DEFAULT 'http://lu/blah3'
SELECT
nref.value('Response[1]/eqRef[1]', 'nvarchar(200)') Ref,
ncharge.value('v:Type[1]', 'nvarchar(50)') ChargeType,
ncharge.value('v:Basis[1]', 'nvarchar(50)') ChargeBasis,
ncharge.value('v:Term[1]', 'int') Term,
ncharge.value('v:StartMonth[1]', 'int') StartMonth,
ncharge.value('v:EndMonth[1]', 'int') EndMonth,
ncharge.value('v:Rate[1]', 'money') Rate,
ncharge.value('v:RebateRatio[1]', 'money') RebateRatio,
ncharge.value('v:MaxRebate[1]', 'money') MaxRebate,
tcharge.value('v:Term[1]', 'int') TieredTerm,
tcharge.value('v:Rate[1]', 'money') TieredRate,
tcharge.value('v:LowerBand[1]', 'money') TieredLowerBand,
tcharge.value('v:UpperBand[1]', 'money') TieredUpperBand
INTO #TempCharges
FROM xml_response CROSS APPLY response_body.nodes('//SEIContent') AS Quote(nref)
CROSS APPLY response_body.nodes('//Request//eq:Charge') AS Charge(ncharge)
CROSS APPLY response_body.nodes('//Request//eq:Charge//v:TieredCharge') AS TieredCharge(tcharge)
WHERE nref.value('Request[1]/eq:Product[1]', 'nvarchar(60)') <> 'AVL'
select * from #TempCharges
So although this flattens the xml, what I'm getting is repeated rows even if the eq:Charge element doesn't contain a v:TieredCharge repeating element?
For example I get 4 rows where the charge type is QAC even though there is only one element with that type?
How do I query this so that I only get one row for each Charge element, unless there are repeating child elements below it, in which case I'll only get the rows repeated for each element?
So I should get something like this:
MaintChange TieredCharge1 row
MaintChange TieredCharge2 row
MaintChange TieredCharge3 row
MaintChange TieredCharge4 row
MaintCharge
MaintCharge
QAC
So the trick is to use OUTER APPLY in the last bit of the query - its kind of like an inner join. Then it works.

Resources