xmlquery returns all values as one long line instead of separate entities - sql

I am trying to query the telephone numbers from the following xml file
<xmlPhoneEntity>
<TelephoneEntity>
<number>123</number>
</TelephoneEntity>
<TelephoneEntity>
<number>456</number>
</TelephoneEntity>
<TelephoneEntity>
<number>789</number>
</TelephoneEntity>
</xmlPhoneEntity>
This XML is located in my DB - the table looks like this
id customer_id telephone blabla
1 111 xmlfile
2 222 xmlfile
etc
My sql query looks like this -
select xmlserialize(xmlquery('/xmlPhoneEntity/TelephoneEntity/number/text()
passing telephone"
the response: 123456789
I tried using /nodes() instead of /text() and the result is the same.
How do I separate the values ?

Related

Parse XML data from a column in SQL Server

I'm trying to put together a report for a badge program. Some the data for custom fields we created are stored in a single column in the table as XML. I need to return a couple of the items in there to the report and am having a hard time getting the proper syntax to get it to parse out.
Aside from the XML, the query itself is simple:
SELECT
Person1.firstName
,Person1.lastName
,Person1.idNumber
,Person1.idNumber2
,Person1.idNumber3
,Person1.status
,Person1.customdata
FROM
Person1
the field "customdata" is the XML field that I need to pull Title, and 2 different dates out of. This is what the XML looks like:
<person1_7:CustomData xmlns:person1_7="http://www.badgepass.com/Person1_7">
<Title>IT Director</Title>
<Gaming_x0020_Level>Level 1</Gaming_x0020_Level>
<Gaming_x0020_Issue_x0020_Date>2021-02-18T12:00:00Z</Gaming_x0020_Issue_x0020_Date>
<Gaming_x0020_Expire_x0020_Date>2022-02-18T12:00:00Z</Gaming_x0020_Expire_x0020_Date>
<Betting_x0020_Level>Level 1</Betting_x0020_Level>
<Betting_x0020_Issue_x0020_Date>2021-02-18T12:00:00Z</Betting_x0020_Issue_x0020_Date>
<Betting_x0020_Expire_x0020_Date>2022-02-18T12:00:00Z</Betting_x0020_Expire_x0020_Date>
<BadgeType>Dual Employee</BadgeType>
<Gaming_x0020_Status>TEMP</Gaming_x0020_Status>
<Betting_x0020_Status>TEMP</Betting_x0020_Status>
</person1_7:CustomData>
I have tried a couple of different methods trying to follow the advice from How to query for Xml values and attributes from table in SQL Server? and then tried declaring a XML namespace with the following query:
WITH XMLNAMESPACES ('http://www.badgepass.com/Person1_7' as X)
SELECT
Person1.firstName
,Person1.lastName
,Person1.idNumber
,Person1.idNumber2
,Person1.idNumber3
,Person1.status
,Person1.customdata.value('(/X:person1_7:customdata/X:Title)[1]', 'varchar(100)') AS Title
FROM
Person1
So far all of my results keep returning "XQuery [Person1.customData.value()]: ")" was expected.
" I'm assuming I have a syntax issue that I'm overlooking as I've never had to manipulate XML with SQL before. Thank you in advance for any help.
Please try the following solution.
Notable points:
XQuery .nodes() method establishes a context so you can access any
XML element right away without long XPath expressions.
Use of the text() in the XPath expressions is for performance
reasons.
SQL
-- DDL and sample data population, start
DECLARE #person1 TABLE (firstname varchar(50), customdata xml);
INSERT INTO #person1(firstname, customdata) VALUES
('John', '<person1_7:CustomData xmlns:person1_7="http://www.badgepass.com/Person1_7">
<Title>IT Director</Title>
<Gaming_x0020_Level>Level 1</Gaming_x0020_Level>
<Gaming_x0020_Issue_x0020_Date>2021-02-18T12:00:00Z</Gaming_x0020_Issue_x0020_Date>
<Gaming_x0020_Expire_x0020_Date>2022-02-18T12:00:00Z</Gaming_x0020_Expire_x0020_Date>
<Betting_x0020_Level>Level 1</Betting_x0020_Level>
<Betting_x0020_Issue_x0020_Date>2021-02-18T12:00:00Z</Betting_x0020_Issue_x0020_Date>
<Betting_x0020_Expire_x0020_Date>2022-02-18T12:00:00Z</Betting_x0020_Expire_x0020_Date>
<BadgeType>Dual Employee</BadgeType>
<Gaming_x0020_Status>TEMP</Gaming_x0020_Status>
<Betting_x0020_Status>TEMP</Betting_x0020_Status>
</person1_7:CustomData>');
-- DDL and sample data population, end
WITH XMLNAMESPACES ('http://www.badgepass.com/Person1_7' as person1_7)
SELECT firstName
, c.value('(Title/text())[1]', 'VARCHAR(100)') AS Title
, c.value('(Gaming_x0020_Issue_x0020_Date/text())[1]', 'DATETIME') GamingIssueDate
FROM #person1
CROSS APPLY customdata.nodes('/person1_7:CustomData') AS t(c);
Output
+-----------+-------------+-------------------------+
| firstName | Title | GamingIssueDate |
+-----------+-------------+-------------------------+
| John | IT Director | 2021-02-18 12:00:00.000 |
+-----------+-------------+-------------------------+

how to combine different schemas

I'm using a custom OUTPUTTER to generate XML from my "flat data" like so:
SELECT *..
OUTPUT #all_data
TO "/patient/{ID}.tsv"
USING new Microsoft.Analytics.Samples.Formats.Xml.XmlOutputter("Patient");
Which generates individual files that look like this:
<Patient>
<ID>5283293478</ID>
<ANESTHESIA_START>09/06/2019 11:52:00</ANESTHESIA_START>
<ANESHTHESIA_END>09/06/2019 14:40:00</ANESHTHESIA_END>
<SURGERY_START_TIME>9/6/2019 11:52:00 AM</SURGERY_START_TIME>
<SURGERY_END_TIME>9/6/2019 2:34:00 PM</SURGERY_END_TIME>
<INCISION_START>9/6/2019 12:45:00 PM</INCISION_START>
<INCISION_END>9/6/2019 2:18:00 PM</INCISION_END>
</Patient>
A separate script is generating data like this:
SELECT *..
OUTPUT #other_data
TO "/charge/{ID}.tsv"
USING new Microsoft.Analytics.Samples.Formats.Xml.XmlOutputter("Patient");
Yielding files that look like this:
<Charge>
<ID>5283293478</ID>
<PROVIDER_TYPE>CRNA</PROVIDER_TYPE>
</Charge>
<Charge>
<ID>5283293478</ID>
<PROVIDER_TYPE>Student Nurse Anesthetist</PROVIDER_TYPE>
</Charge>
As you can see, the files that are being created are:
/patient/{ID}.tsv
/charge/{ID}.tsv
How do I concatenate the two sets of files based on ID?
The result I'd like is:
<Patient>
<ID>5283293478</ID>
<ANESTHESIA_START>09/06/2019 11:52:00</ANESTHESIA_START>
<ANESHTHESIA_END>09/06/2019 14:40:00</ANESHTHESIA_END>
<SURGERY_START_TIME>9/6/2019 11:52:00 AM</SURGERY_START_TIME>
<SURGERY_END_TIME>9/6/2019 2:34:00 PM</SURGERY_END_TIME>
<INCISION_START>9/6/2019 12:45:00 PM</INCISION_START>
<INCISION_END>9/6/2019 2:18:00 PM</INCISION_END>
</Patient>
<Charge>
<ID>5283293478</ID>
<PROVIDER_TYPE>CRNA</PROVIDER_TYPE>
</Charge>
<Charge>
<ID>5283293478</ID>
<PROVIDER_TYPE>Student Nurse Anesthetist</PROVIDER_TYPE>
</Charge>
If you have the 2 files, you can simple extract both (using id)
DECLARE #patient string ="/patient/{Id}.tsv";
DECLARE #charge string ="/charge/{Id}.tsv";
#patients =
EXTRACT Id string, content string FROM #patient USING Extractors.Text();
#charges =
EXTRACT Id string, content string FROM #charge USING Extractors.Text();
Then you can simple join by id and concatenate patients and charges and output it.

TSQL - Need to query a database column which is populated by XML

TSQL - Need to query a database column which is populated by XML.
The Database has an iUserID column with an Application ID and VCKey
TxtValue is the Column name and the contained Data is similar to this
<BasePreferencesDataSet xmlns="http://tempuri.org/BasePreferencesDataSet.xsd">
<ViewModesTable>
<iViewID>1</iViewID>
</ViewModesTable>
<ViewMode_PreferenceData>
<iViewID>1</iViewID>
<iDataID>0</iDataID>
<strValue>False</strValue>
</ViewMode_PreferenceData>
<ViewMode_PreferenceData>
<iViewID>1</iViewID>
<iDataID>5</iDataID>
<strValue>True</strValue>
</ViewMode_PreferenceData>
<ViewMode_PreferenceData>
<iViewID>1</iViewID>
<iDataID>6</iDataID>
<strValue>True</strValue>
</ViewMode_PreferenceData>
<ViewMode_PreferenceData>
<iViewID>1</iViewID>
<iDataID>4</iDataID>
<strValue>False</strValue>
I want to be able to identify any iUserID in which the StrValue for iDataID's 5 and 6 are not set to True.
I have attempted to use a txtValue Like % statement but even if I copy the contents and query for it verbatim it will not yield a result leading me to believe that the XML data cannot be queried in this manner.
Screenshot of Select * query for this DB for reference
You can try XML-method .exist() together with an XPath with predicates:
WITH XMLNAMESPACES(DEFAULT 'http://tempuri.org/BasePreferencesDataSet.xsd')
SELECT *
FROM YourTable
WHERE CAST(txtValue AS XML).exist('/BasePreferencesDataSet
/ViewMode_PreferenceData[iDataID=5 or iDataID=6]
/strValue[text()!="True"]')=1;
The namespace-declaration is needed to address the elements without a namespace prefix.
The <ViewMode_PreferenceData> is filtered for the fitting IDs, while the <strValue> is filtered for a content !="True". This will return any data row, where there is at least one entry, with an ID of 5 or 6 and a value not equal to "True".
So without sample data (including tags; sorry you're having trouble with that) it's tough to craft the complete query, but what you're looking for is XQuery, specifically the .exists method in T-SQL.
Something like
SELECT iUserID
FROM tblLocalUserPreferences
WHERE iApplicationID = 30
AND vcKey='MonitorPreferences'
AND (txtValue.exist('//iDataID[text()="5"]/../strValue[text()="True"]') = 0
OR txtValue.exist('//iDataID[text()="6"]/../strValue[text()="True"]')=0)
This should return all userID's where either iDataID 5 or 6 do NOT contain True. In other words, if both are true, you won't get that row back.

Get multiple xml nodes (delimited)

I have a table with a xml that is formatted something like this (simplified for readability)
<parentItem xmlns:i="http://tempuri.org/1" xmlns="http://tempuri.org/2">
<ItemA></ItemA>
<ItemB></ItemB>
<ItemC xmlns:d2p1="http://tempuri.org/3">
<d2p1:string>value1</d2p1:string>
<d2p1:string>value2</d2p1:string>
<d2p1:string>value3</d2p1:string>
<!-- .... (0 to many strings here) -->
</ItemC>
</parentItem>
The only think I care about are the values in parentItem > ItemC > string
I would like to get those values delimited by something, such as a comma
Desired Result: "value1,value2,value3"
currently I can get one value by doing this:
SELECT CAST([QueryXml] as xml).value('(/*:parentItem/*:ItemC/node())[1]','nvarchar(max)')
FROM [opendb].[dbo].[MyTable]
Result: "value1"
I can also get all the values like this:
SELECT CAST([QueryXml] as xml).value('(/*:ConflictsSearchTermQuery/*:TermItems)[1]','nvarchar(max)')
FROM [opendb].[dbo].[ConflictsSearchTerms]
Result: "value1value2value3"
but I'm looking to get a delimited set of values
Desired Result: "value1,value2,value3"
To get multiple values out of XML you need to use the nodes() method of the XML data type.
However, since this method does not return a single, scalar value (but a rowset), you need to call it through CROSS APPLY.
WITH MyTable AS (
SELECT 1 AS ID, CAST('<parentItem xmlns:i="http://tempuri.org/1" xmlns="http://tempuri.org/2">
<ItemA></ItemA>
<ItemB></ItemB>
<ItemC xmlns:d2p1="http://tempuri.org/3">
<d2p1:string>value1</d2p1:string>
<d2p1:string>value2</d2p1:string>
<d2p1:string>value3</d2p1:string>
<!-- .... (0 to many strings here) -->
</ItemC>
</parentItem>' AS XML) AS QueryXml
)
SELECT
t.ID,
x.node.value('.', 'varchar(100)') AS nodeValue
FROM
MyTable t
CROSS APPLY QueryXml.nodes('
declare namespace i="http://tempuri.org/1";
declare namespace def="http://tempuri.org/2";
declare namespace d2p1="http://tempuri.org/3";
/def:parentItem/def:ItemC/d2p1:string'
) x(node)
gives you
ID nodeValue
----------- ------------------
1 value1
1 value2
1 value3
After that, if you really must, standard techniques for concatenating values in SQL Server apply.
Note that I have properly declared the namespaces in the XQuery instead of using *. Namespaces are important, don't ignore them.

SQL Server - XQuery for XML

Just similar other post, I need to retrieve any rows from table applying criteria on Xml column, for instance, supposing you have an xml column like this:
<DynamicProfile xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/WinTest">
<AllData xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:KeyValueOfstringstring>
<d2p1:Key>One</d2p1:Key>
<d2p1:Value>1</d2p1:Value>
</d2p1:KeyValueOfstringstring>
<d2p1:KeyValueOfstringstring>
<d2p1:Key>Two</d2p1:Key>
<d2p1:Value>2</d2p1:Value>
</d2p1:KeyValueOfstringstring>
</AllData>
</DynamicProfile>
My query would be able to return all rows where node value <d2p1:Key> = 'some key value' AND node value <d2p1Value = 'some value value'.
Imagine of that just as a dynamic table where KEY node represent the column name and Value node represent column's value.
The following query does not work because key and value nodes are not sequential:
select * from MyTable where
MyXmlField.exist('//d2p1:Key[.="One"]') = 1
AND MyXmlField.exist('//d2p1:Value[.="1"]') = 1
Instead of looking for //d2p1:key[.="One"] and //d2p1:Value[.="1"] as two separate searches, do a single query that looks for both at once, like so:
//d2p1:KeyValueOfstringstring[./d2p1:Key="One"][./d2p1:Value=1]