SQL XML parsing using a attribute value supplied by another field in the same row - sql-server

Context: I'm scraping some XML form descriptions from a Web Services table in hopes of using that name to identify what the user has inputted as response. Since this description changes for each step (row) of the process and each product I want something that can evaluate dynamically.
What I tried: The following was quite useful but it returns a dynamic attribute query result in it's own field ans using a coalesce to reduce the results as one field would lead to it's own complications: Get values from XML tags with dynamically specified data fields
Current Attempt:
I'm using the following code to generate the attribute name that I will use in the next step to query the attribute's value:
case when left([Return], 5) = '<?xml'
then lower(cast([Return] as xml).value('(/response/form/*/#name)[1]','varchar(30)'))
else ''
end as [FormRequest]
And as part of step 2 I have used the STUFF function to try and make the row-level query possible
case when len(FormRequest)>0
then stuff( ',' + 'cast([tmpFormResponse] as xml).value(''(/wrapper/#' + [FormRequest] + ')[1]'',''varchar(max)'')', 1, 1, '')
else ''
end as [FormResponse]
Instead of seeing 1 returned as my FormReponse feild value for the submit attribute (please see in yellow below) it's returning the query text -- cast([tmpFormResponse] as xml).value('(/wrapper/#submit)1','varchar(max)') -- instead (that which should be queried).
How should I action the value method so that I can dynamically strip out the response per row of XML data in tmpFormResponse based on the field value in the FormRequest field?
Thanx

You can check this out:
DECLARE #xml XML=
N'<root>
<SomeAttributes a="a" b="b" c="c"/>
<SomeAttributes a="aa" b="bb" c="cc"/>
</root>';
DECLARE #localName NVARCHAR(100)='b';
SELECT sa.value(N'(./#*[local-name()=sql:variable("#localName")])[1]','nvarchar(max)')
FROM #xml.nodes(N'/root/SomeAttributes') AS A(sa)

Ended up hacking up a solution to the problem by using PATINDEX and CHARINDEX to look for the value in the [FormRequest] field in the he tmpFormResponse field.

Related

XPath 'contains()' requires a singleton (or empty sequence)

Given the XML:
<Dial>
<DialID>
24521
</DialID>
<DialName>
Base Price
</DialName>
</Dial>
<Dial>
<DialID>
24528
</DialID>
<DialName>
Rush Options
</DialName>
<DialValue>
1.5
</DialValue>
</Dial>
<Dial>
<DialID>
24530
</DialID>
<DialName>
Bill Rush Charges
</DialName>
<DialValue>
School
</DialValue>
</Dial>
I can use the contains() function in my xpath:
//Dial[DialName[contains(text(), 'Bill')]]/DialValue
To retrieve the values I'm after:
School
The above XML is stored in a field in my SQL database so I'm using the .value method to select from that field.
SELECT Dials.DialDetail.value('(//Dial[DialName[contains(text(), "Bill")]]/DialValue)[1]','VARCHAR(64)') AS BillTo
FROM CampaignDials Dials
I can't seem to get the syntax right though... the xpath works as expected (tested in Oxygen and elsewhere) but when I use it in the XQuery argument of the .value() method, I get an error:
Started executing query at Line 1
Msg 2389, Level 16, State 1, Line 36
XQuery [Dials.DialDetail.value()]: 'contains()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'
Total execution time: 00:00:00.004
I've tried different variations of single and double quotes with no effect. The error refers to an XPath data type for attributes, but I'm not retrieving an attribute; I'm getting the text value. I receive the same error if I type the response with //Dial[DialName[contains(text(), 'Bill')]]/DialValue/text() instead.
What is the correct way to use contains() in an XQuery when it's used in the XML.value() method? Or is this the wrong approach to begin with?
You nearly have it right, you just need [1] on the text() function to guarantee a single value.
You should also use text() on the actual node you are pulling out, for performance reasons.
Also, // can be inefficient, so only use it if you really need recursive descent. You can instead use /*/ to get the first node of any name.
SELECT
Dials.DialDetail.value(
'(//Dial[DialName[contains(text()[1], "Bill")]]/DialValue/text())[1]',
'VARCHAR(64)') AS BillTo
FROM CampaignDials Dials
As Yitzhak Kabinsky notes, this only gets you one value per row of the table, you need .nodes if you want to shred the XML itself into rows.
The difference between your actual database case that fails and your reduced sample case that works is likely one of different data.
The error,
contains() requires a singleton (or empty sequence)
indicates that one of your DialName elements has multiple text node children rather than a single text node child as you're expecting.
You can abstract away such variations by testing the string-value of DialName rather than its text node children:
//Dial[contains(DialName, 'Bill')]/DialValue
See also
Testing text() nodes vs string values in XPath
Here is how to do XML shredding in MS SQL Server correctly.
You need to apply filter in the XQuery .nodes() method.
The .value() method is just for the actual value retrieval.
It is possible to pass SQL Server variable as a parameter instead of the hard-coding "Bill" value.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, DialDetail XML);
INSERT INTO #tbl (DialDetail) VALUES
(N'<Dial>
<DialID>24521</DialID>
<DialName>Base Price</DialName>
</Dial>
<Dial>
<DialID>24528</DialID>
<DialName>Rush Options</DialName>
<DialValue>1.5</DialValue>
</Dial>
<Dial>
<DialID>24530</DialID>
<DialName>Bill Rush Charges</DialName>
<DialValue>School</DialValue>
</Dial>');
-- DDL and sample data population, end
SELECT ID
, c.value('(DialID/text())[1]', 'INT') AS DialID
, c.value('(DialName/text())[1]', 'VARCHAR(30)') AS DialName
, c.value('(DialValue/text())[1]', 'VARCHAR(30)') AS DialValue
FROM #tbl CROSS APPLY DialDetail.nodes('/Dial[contains((DialName/text())[1], "Bill")]') AS t(c);
Output
+----+--------+-------------------+-----------+
| ID | DialID | DialName | DialValue |
+----+--------+-------------------+-----------+
| 1 | 24530 | Bill Rush Charges | School |
+----+--------+-------------------+-----------+

Parse XML using SQL

I'm using MS SQL2016 and I have an XML file that I need to parse to put various data elements into the separate fields. For the most part everything works find except I need a little help to identify a particular node value. If I have (I put only a snippet of the xml here but it does show the problem)
DECLARE #xmlString xml
SET #xmlString ='<PubmedArticle>
<MedlineCitation Status="PubMed-not-MEDLINE" Owner="NLM">
<PMID Version="1">25685064</PMID>
<Article PubModel="Electronic-eCollection">
<Journal>
<ISSN IssnType="Electronic">1234-5678</ISSN>
<ISSN IssnType="Print">1475-2867</ISSN>
<JournalIssue CitedMedium="Print">
<Volume>15</Volume>
<Issue>1</Issue>
<PubDate>
<Year>2015</Year>
</PubDate>
</JournalIssue>
</Journal>
</Article>
</MedlineCitation>
</PubmedArticle>'
select
nref.value('Article[1]/Journal[1]/ISSN[1]','varchar(max)') ISSN
from #xmlString.nodes ('//MedlineCitation[1]') as R(nref)
I bypass the second ISSNType and read the first value available. I need to pull both values. What do I need to change? Thanks
You can read as second column:
SELECT
nref.value('Article[1]/Journal[1]/ISSN[1]','varchar(max)') ISSN,
nref.value('Article[1]/Journal[1]/ISSN[2]','varchar(max)') ISSN2
FROM #xmlString.nodes('//MedlineCitation[1]') as R(nref)
Or
SELECT
nref.value('ISSN[1]','varchar(max)') ISSN,
nref.value('ISSN[2]','varchar(max)') ISSN2
FROM #xmlString.nodes('//MedlineCitation[1]/Article[1]/Journal[1]') as R(nref)
Or as a separate row:
SELECT nref.value('.','varchar(MAX)') ISSN
from #xmlString.nodes('//MedlineCitation[1]/Article[1]/Journal[1]/ISSN') as R(nref)
Update
If number of ISSNs may vary, I recommend normalize your resultset:
SELECT
nref.value('.','varchar(MAX)') Issn,
nref.value('#IssnType','varchar(MAX)') IssnType
FROM #xmlString.nodes('//MedlineCitation[1]/Article[1]/Journal[1]/ISSN') as R(nref)

Updating XML value within MSSQL

replacing XML tag value within a large XML text value MSSQL.
Within MSSQL I have a column called form which is a text column with an extremely large XML. I need to find a certain tag and change the value of that sub tag within the tag from False to True.
This is what I currently have:
USE trainset;
UPDATE dbo.users
SET formxml = REPLACE(CAST(formxml as nvarchar(max)), '%<ttaycheckbox><name>cbTermsConditions</name><cargo>F</cargo></ttaycheckbox>%', '<ttaycheckbox><name>cbTermsConditions</name><cargo>T</cargo></ttaycheckbox>')
WHERE usersid = '0000GARX'
and formname ='ffOrderRpt'
and formxml LIKE ('%<ttaycheckbox><name>cbTermsConditions</name><cargo>F</cargo></ttaycheckbox>%')
It seems like it is doing the update;
However, after this when I do a select on this particular value the value of is still False rather than True.
What am I missing in there that is not causing it to update properly?
replace() doesn't support wildcards. So your where ... like finds the relevant records, but replace finds NOTHING, because it's looking for a literal %.
You can use XML modify and exist:
UPDATE users
SET formxml.modify('replace value of (/ttaycheckbox/cargo/text())[1] with "T"')
WHERE usersid = '0000GARX'
and formname ='ffOrderRpt'
and formxml.exist('/ttaycheckbox/name[text()[1] eq "cbTermsConditions"]') = 1
and formxml.exist('/ttaycheckbox/cargo[text()[1] eq "F"]') = 1

SQL Server - remove HTML Tag from a varchar

I know there are a few about similar topics on here but I can't find one related to my issue, this is it:
I have a table with an ID Column and a QRCode column. each time an item is added the primary key auto increments. The QRCode will scan in to be like the following:
"http://somewebsite.com/12345/987654321"
i want to be able to remove the "http://somewebsite.com/" from the string, I know how to do this in C# however I am unsure of how to do this in Sql Server. any guidance would be great, thanks
Regular formats are like the following, and used in the example below.
"http://somewebsite.com/12345/456564654"
"http://somewebsite.com/12345/989886765"
"http://somewebsite.com/12346/987654321"
the query returns the following results:
SELECT
REPLACE
(
REPLACE(QRCode, 'http://somewebsite.com/', '')
,'/', ' '
) AS QRCode
FROM
QRTable
WHERE
QRCode LIKE '%http://somewebsite.com/%'
"12345 456564654"
"12345 989886765"
"12346 987654321"
Now i need to update the table with those new results however as there's 3 results, i get the error message "Subquery returned more than 1 value". is there a way to replace the selected values in the table with the ones that exist based on the primary key field?
**Removed previous example
A more complete answer based on your updated question. This removes the first portion of the URL as well as the trailing / so that you get your desired output.
DECLARE #Variable VARCHAR(50)
SET #Variable = 'http://somewebsite.com/12345/456564654'
SET #Variable =
REPLACE
(
REPLACE(#Variable, 'http://somewebsite.com/', '')
,'/', ' '
)
PRINT #Variable
Output = 12345 456564654
Looking at your SQL statement you want this:
SELECT
REPLACE
(
REPLACE(QRCode, 'http://somewebsite.com/', '')
,'/', ' '
) AS QRCode
FROM
QRTable
WHERE
QRCode LIKE '%http://somewebsite.com/%'

Checking if an XML element is marked with `xsi:nil` in SQL

I am working on a stored procedure which shreds an XML document. One of the child elements in the records being processed can sometimes be marked with the xsi:nil="true" attribute. Other times, it can contain a dateTime. I'm trying to insert a string into a column of my table which depends on whether or not this element has a value. For example:
[Status] = CASE WHEN (Rt.Item.value('(./Date)[1]', 'nvarchar(max)') = '') THEN N'SUBMITTED' ELSE N'PROCESSED' END
Unfortunately, this doesn't seem to be working. What's the correct to check if an element has a value in SQL Server?
Generally:
theElementName[not(#xsi:nil eq 'true')]/any/other/needed/location/steps
If the association of the "xsi" prefix to the appropriate namespace isn't registered (the way to do this is implementation-specific and you need to check how this is to be done in your situation), one still can use:
theElementName[not(#*[name() eq 'xsi:nil'] eq 'true')]

Resources