In SQL, in XML datatype field, how do I query the data like a simple sql record? - sql

I have one XML field in table.
It stores data as
'<NewDataSet>
<ClaimExpense>
<ClaimNo>3003-LOB-0003</ClaimNo>
<Office>3003</Office>
<BranchId>1</BranchId>
<CostCenterId>35</CostCenterId>
<ServiceLineId>14</ServiceLineId>
<ProjectId>62</ProjectId>
<LCAmountCurr>AED</LCAmountCurr>
<LCAmount>367.25</LCAmount>
<FCCurr>USD</FCCurr>
<FCAmount>100</FCAmount>
<ExchangeRate>3.67252</ExchangeRate>
<ExpenseDate>2020-11-03T00:00:00+04:00</ExpenseDate>
<ClaimItemNo>ITM-004</ClaimItemNo>
<GLAccount>10000000</GLAccount>
<VatRate>5%</VatRate>
<VatBaseAmount>349.76</VatBaseAmount>
<VatAmount>17.49</VatAmount>
<ClaimType>LOB</ClaimType>
<ForPayment>357.25</ForPayment>
<ForDeduction>0.00</ForDeduction>
<EmpCode>2019-1194</EmpCode>
</ClaimExpense>
<ClaimExpense>
<ClaimNo>3003-LOB-0003</ClaimNo>
<Office>3003</Office>
<BranchId>1</BranchId>
<CostCenterId>35</CostCenterId>
<ServiceLineId>14</ServiceLineId>
<ProjectId>62</ProjectId>
<LCAmountCurr>AED</LCAmountCurr>
<LCAmount>90.00</LCAmount>
<FCCurr>AED</FCCurr>
<FCAmount>90</FCAmount>
<ExchangeRate>1</ExchangeRate>
<ExpenseDate>2020-11-03T00:00:00+04:00</ExpenseDate>
<ClaimItemNo>ITM-005</ClaimItemNo>
<GLAccount>10000000</GLAccount>
<VatRate />
<ClaimType>LOB</ClaimType>
<ForPayment>357.25</ForPayment>
<ForDeduction>0.00</ForDeduction>
<EmpCode>2019-1194</EmpCode>
</ClaimExpense>
</NewDataSet>'
Now I want to simply query it like an sql record how do I do that?
I tried with conversion and everything but no luck with that. I just want it be simply like any sql record which can be selected, inserted deleted.
Tired but no luck with this:
SELECT
Tbl.Col.value('GLAccount[0]', 'varchar')
FROM #xml.nodes('/NewDataSet/ClaimExpense/GLAccount') Tbl(Col)

Check it out how to do it for your XML.
SQL Fiddle
SQL
CREATE table tbl (ID INT IDENTITY PRIMARY KEY, xmldata XML);
insert into tbl (xmldata) values
(
N'<NewDataSet>
<ClaimExpense>
<ClaimNo>3003-LOB-0003</ClaimNo>
<Office>3003</Office>
<BranchId>1</BranchId>
<CostCenterId>35</CostCenterId>
<ServiceLineId>14</ServiceLineId>
<ProjectId>62</ProjectId>
<LCAmountCurr>AED</LCAmountCurr>
<LCAmount>367.25</LCAmount>
<FCCurr>USD</FCCurr>
<FCAmount>100</FCAmount>
<ExchangeRate>3.67252</ExchangeRate>
<ExpenseDate>2020-11-03T00:00:00+04:00</ExpenseDate>
<ClaimItemNo>ITM-004</ClaimItemNo>
<GLAccount>10000000</GLAccount>
<VatRate>5%</VatRate>
<VatBaseAmount>349.76</VatBaseAmount>
<VatAmount>17.49</VatAmount>
<ClaimType>LOB</ClaimType>
<ForPayment>357.25</ForPayment>
<ForDeduction>0.00</ForDeduction>
<EmpCode>2019-1194</EmpCode>
</ClaimExpense>
<ClaimExpense>
<ClaimNo>3003-LOB-0003</ClaimNo>
<Office>3003</Office>
<BranchId>1</BranchId>
<CostCenterId>35</CostCenterId>
<ServiceLineId>14</ServiceLineId>
<ProjectId>62</ProjectId>
<LCAmountCurr>AED</LCAmountCurr>
<LCAmount>90.00</LCAmount>
<FCCurr>AED</FCCurr>
<FCAmount>90</FCAmount>
<ExchangeRate>1</ExchangeRate>
<ExpenseDate>2020-11-03T00:00:00+04:00</ExpenseDate>
<ClaimItemNo>ITM-005</ClaimItemNo>
<GLAccount>10000000</GLAccount>
<VatRate/>
<ClaimType>LOB</ClaimType>
<ForPayment>357.25</ForPayment>
<ForDeduction>0.00</ForDeduction>
<EmpCode>2019-1194</EmpCode>
</ClaimExpense>
</NewDataSet>');
select c.value('(ClaimNo/text())[1]', 'VARCHAR(20)') as ClaimNo
-- everything in between with proper data types
, c.value('(EmpCode/text())[1]', 'VARCHAR(10)') as EmpCode
from tbl cross apply xmldata.nodes('/NewDataSet/ClaimExpense') t(c);

As I mention in the comments, you need to use the names of your nodes; your XML has no node "row" so of course SQL Server isn't going to find any data. I also recommend use the text() function, as it is far more efficient. For example:
SELECT NDS.CE.value('(ClaimNo/text())[1]','int') AS ClaimNo
FROM #XML.nodes('NewDataSet/ClaimExpense') NDS(CE);

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 |
+-----------+-------------+-------------------------+

Getting XML out of a table created in SQL

I have created this table in Oracle SQL Developer
CREATE TABLE Test_T (
COL_1 VARCHAR(30),
COL_2 XMLTYPE
);
And have inserted this into it
INSERT INTO Test_T VALUES ('two', ('<?xml version="1.0" encoding="UTF-8"?>
<CATALOG>
<PLANT>
<COMMON>Bloodroot</COMMON>
<BOTANICAL>Sanguinaria canadensis</BOTANICAL>
<ZONE>4</ZONE>
<LIGHT>Mostly Shady</LIGHT>
<PRICE>$2.44</PRICE>
<AVAILABILITY>031599</AVAILABILITY>
</PLANT>
<PLANT>
<COMMON>Columbine</COMMON>
<BOTANICAL>Aquilegia canadensis</BOTANICAL>
<ZONE>3</ZONE>
<LIGHT>Mostly Shady</LIGHT>
<PRICE>$9.37</PRICE>
<AVAILABILITY>030699</AVAILABILITY>
</PLANT></CATALOG>'));
My goal is to return the <COMMON> Name </COMMON> ONLY WHERE the zone is 3 or LESS. So this should return Columbine.
I thought about using XMLExists Im not too familiar with XML so this is what I had so far.
SELECT COL_2 FROM Test_T WHERE XMLExists('//ZONE[COL_2 <= 3]' PASSING BY REF COL_2);
I'm not sure if I am accessing the ZONE right.
Could anyone guide me in the right direction?
Try the below select query :
SELECT COMMON_NAME FROM Test_T WHERE XMLExists( 'CATALOG/PLANT[ZONE<=3]/COMMON[text()]' PASSING COMMON_NAME )
The problem is with your path( '//ZONE[COL_2 <= 3]' ). COL_2 is not a valid XML node, it's just the name of your column.
The proper path would be //ZONE[text() <= 3].
text() is a special node reference that tells oracle to grab the text inside the ZONE node <ZONE>THIS TEXT</ZONE>. You can only target nodes in your actual XML schema.
Also, be aware that the path is CASE SENSITIVE to what's in your XML. Remembering that will save you time.
Additionally, another way to write your select would be this. In this example, Oracle does an implicit join and returns a row for each //PLANT with /ZONE/text() <= 3. The path in the XMLSEQUENCE is important here because it determines how oracle splits up each row, meaning that you can't just target //ZONE because you would only get a row for each ZONE rather than a row for each PLANT.
In the select clause, you can extract individual node values for each PLANT if you have more than one.
SELECT VALUE(P) --THE FULL XML FOR EACH PLANT
VALUE(P).EXTRACT('//COMMON/text()').getstringval() AS COMMON, --INDIVIDUAL NODE VALUE
VALUE(P).EXTRACT('//BOTANICAL/text()').getstringval() AS BOTANICAL --INDIVIDUAL NODE VALUE
FROM Test_T, TABLE(XMLSEQUENCE(EXTRACT(COL_2, '//PLANT[ZONE<= 3]'))) p

Querying XML colum for values

I have a SQL Server table with an XML column, and it contains data something like this:
<Query>
<QueryGroup>
<QueryRule>
<Attribute>Integration</Attribute>
<RuleOperator>8</RuleOperator>
<Value />
<Grouping>OrOperator</Grouping>
</QueryRule>
<QueryRule>
<Attribute>Integration</Attribute>
<RuleOperator>5</RuleOperator>
<Value>None</Value>
<Grouping>AndOperator</Grouping>
</QueryRule>
</QueryGroup>
</Query>
Each QueryRule will only have one Attribute, but each QueryGroup can have many QueryRules. Each Query can also have many QueryGroups.
I need to be able to pull all records that have one or more QueryRule with a certain attribute and value.
SELECT *
FROM QueryBuilderQueries
WHERE [the xml contains any value=X where the attribute is either Y or Z]
I've worked out how to check a specific QueryRule, but not "any".
SELECT
Query
FROM
QueryBuilderQueries
WHERE
Query.value('(/Query/QueryGroup/QueryRule/Value)[1]', 'varchar(max)') like 'UserToFind'
AND Query.value('(/Query/QueryGroup/QueryRule/Attribute)[1]', 'varchar(max)') in ('FirstName', 'LastName')
You can use two exist(). One to check the value and one to check Attribute.
select Q.Query
from dbo.QueryBuilderQueries as Q
where Q.Query.exist('/Query/QueryGroup/QueryRule/Value/text()[. = "UserToFind"]') = 1 and
Q.Query.exist('/Query/QueryGroup/QueryRule/Attribute/text()[. = ("FirstName", "LastName")]') = 1
If you really want the like equivalence when you search for a Value you can use contains().
select Q.Query
from dbo.QueryBuilderQueries as Q
where Q.Query.exist('/Query/QueryGroup/QueryRule/Value/text()[contains(., "UserToFind")]') = 1 and
Q.Query.exist('/Query/QueryGroup/QueryRule/Attribute/text()[. = ("FirstName", "LastName")]') = 1
According to http://technet.microsoft.com/pl-pl/library/ms178030%28v=sql.110%29.aspx
"The XQuery must return at most one value"
If you are quite certain that for example your XML has let's say maximum 10 QueryRules you could maybe use WHILE to loop everything while droping your results into temporary table?
maybe below can help you anyway
CREATE TABLE #temp(
Query type)
DECLARE #i INT
SET #i = 1
WHILE #i >= 10
BEGIN
INSERT INTO #temp
SELECT
Query
FROM QueryBuilderQueries
WHERE Query.value('(/Query/QueryGroup/QueryRule/Value)[#i]', 'varchar(max)') LIKE 'UserToFind'
AND Query.value('(/Query/QueryGroup/QueryRule/Attribute)[#i]', 'varchar(max)') IN ('FirstName', 'LastName')
#i = #i + 1
END
SELECT
*
FROM #temp
It's a pity that the SQL Server (I'm using 2008) does not support some XQuery functions related to string such as fn:matches, ... If it supported such functions, we could query right inside XQuery expression to determine if there is any. However we still have another approach. That is by turning all the possible values into the corresponding SQL row to use the WHERE and LIKE features of SQL for searching/filtering. After some experiementing with the nodes() method (used on an XML data), I think it's the best choice to go:
select *
from QueryBuilderQueries
where exists( select *
from Query.nodes('//QueryRule') as v(x)
where LOWER(v.x.value('(Attribute)[1]','varchar(max)'))
in ('firstname','lastname')
and v.x.value('(Value)[1]','varchar(max)') like 'UserToFind')

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.

Trying to get all xml nodes with SQL Server, what am I doing wrong?

I have the following xml in a table cell, in column MyColumn:
<BSDL xmlns="..." xmlns:i="...">
<dateTime>2012-12-30T00:00:00Z</dateTime>
<dateTime>2013-01-07T00:00:00Z</dateTime>
<dateTime>2013-01-14T00:00:00Z</dateTime>
<dateTime>2013-01-21T00:00:00Z</dateTime>
<dateTime>2013-01-29T00:00:00Z</dateTime>
<dateTime>2013-02-05T00:00:00Z</dateTime>
<dateTime>2013-02-12T00:00:00Z</dateTime>
<dateTime>2013-02-19T00:00:00Z</dateTime>
<dateTime>2013-03-22T00:00:00Z</dateTime>
<dateTime>2013-03-29T00:00:00Z</dateTime>
<dateTime>2013-04-19T00:00:00Z</dateTime>
</BSDL>
I'm just trying to query it (get all xml nodes) using:
SELECT BSDL.item.value('(dateTime)[1]', 'datetime')
from [MyTable]
CROSS APPLY [MyColumn].nodes ('//BSDL') BSDL(item)
it yields no result, although my MyColumn in MyTable has the same number of entries as above, for each row.
Since there's only one <BSDL> node - your call to //BSDL selects that single node and then item.value('(dateTime)[1]', 'datetime') returns the first <dateTime> child.
You need to change your XQuery to (use that xmlns=.... namespace you mention in your sample here):
;WITH XMLNAMESPACES('.....' as ns)
SELECT
item.value('.', 'datetime')
from
dbo.MyTable
CROSS APPLY
MyColumn.nodes ('/ns:BSDL/ns:dateTime') BSDL(item)
You need to get a list of all <dateTime> nodes under <BSDL> - then you'll get all of them and you can inspect them one by one.