Extract multi-value field in XML format in SQL - sql

I'm currently working with a database that stores XML record for all of its field, please see below example. Lets name the table CUSTOMER table.
customer table
------------------------------------------------------
| RECID | XMLRECORD |
| 1 | <row id='1' xml:space="preserve"><c1>... |
| 2 | <row id='2' xml:space="preserve"><c1>... |
| 3 | <row id='3' xml:space="preserve"><c1>... |
------------------------------------------------------
All of the record of each customer is stored in 1 field called XMLRECORD, below is one example of XML RECORD of a customer.
<row id="1" xml:space="preserve">
<c1>James</c1>
<c2>Anderson</c2>
<c3>25</c3>
<c4>District 2 1657</c4>
<c4 m="2">Riverside Drive Redding</c4>
<c4 m="3">California, USA</c4>
</row>
Where c1 would be the customer's first name, c2 for last name, c3 for age and c4 would be the customer's address.
To query or extract values for each column, I usually use .value function to extract and return single value.
SELECT XMLRECORD.value('(/row/c4)[1]','NVARCHAR(20)') as ADDRESS
FROM CUSTOMER
Now my problem is this function only returns a single value, what I want is to return all the values under c4, which is multi value field. Can someone advise a way to do this?

Initiating the table
declare #xml as table
(
recid int,
xmlrecord xml
)
insert into #xml
values
( 1 , '<row id="1" xml:space="preserve">
<c1>James</c1>
<c2>Anderson</c2>
<c3>25</c3>
<c4>District 2 1657</c4>
<c4 m="2">Riverside Drive Redding</c4>
<c4 m="3">California, USA</c4>
</row>' )
DECLARE #XMLRECORD as xml =
'<row id="1" xml:space="preserve">
<c1>James</c1>
<c2>Anderson</c2>
<c3>25</c3>
<c4>District 2 1657</c4>
<c4 m="2">Riverside Drive Redding</c4>
<c4 m="3">California, USA</c4>
</row>' ;
Using .nodes functionality to fetch all the nodes
SELECT T.C.value('.','NVARCHAR(1000)') as c4_nodes FROM #XMLRECORD.nodes('(/row/c4)') as T(C)
Output -
c4_nodes
---------
District 2 1657
Riverside Drive Redding
California, USA
Since this fetches multiple records, using stuff command to concatenate the rows
SELECT
recid
,STUFF((
SELECT ',' + T.C.value('.','NVARCHAR(1000)')
FROM XMLRECORD.nodes('(/row/c4)') as T(C)
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') c4
FROM #xml
Output -
recid | c4
-----------
1 District 2 1657,Riverside Drive Redding,California, USA

c4 should not repeat multiple time. data should be stored in single node. For all node value, you should use [*] to get all node value.

You can try something like this
SELECT Tmp.record.value('.','NVARCHAR(20)')
FROM [customer]
CROSS APPLY [XMLRECORD].nodes('/row/c4') as Tmp(record)

Related

XML select statement to loop over all rows

I have
mytable consists of one XMLTYPE column called 'RS'. RS contains:
<test>
<mycol>
<name>a</name>
<number>1</number>
<number>2</number>
<number>50</number>
<number>60</number>
</mycol>
<mycol>
<name>b</name>
<number>5</number>
<number>820</number>
<number>601</number>
</mycol>
<mycol>
<name>c</name>
<number>6</number>
<number>8</number>
<number>62</number>
</mycol>
etc...
</test>
I'm looking to run a select statement that will display ALL names and up to 2 numbers from mytable.
something like this select statement but for all rows and without calling mycol[] several times.
select a.RS.extract('/test/mycol[1]/name[1]/text()').getstringval() as Names,
a.RS.extract('/test/mycol[1]/a[1]/text()').getstringval() ||''||
a.RS.extract('/test/mycol[1]/a[2]/text()').getstringval() ||''||
a.RS.extract('/test/mycol[1]/a[3]/text()').getstringval()
as num
from mytable a;/
output should be:
Names | num
a | 1 2
b | 5 820
c | 6 8
etc...
Thanks in advance.
Xml_table and string-join may be helpful

Concatenate multiple rows to form one single row in SQL Server?

Overview
I need to build a description field that describes an entity. The data I am working with has the property description split for each individual key in my table. Below is an example of what the data looks like:
+------------+--------------------+----------+
| Key | Desc | Order_Id |
+------------+--------------------+----------+
| 5962417474 | Big Yellow Door | 14775 |
| 5962417474 | Orange Windows | 14776 |
| 5962417474 | Blue Triangle Roof | 14777 |
+------------+--------------------+----------+
Originally, I wrote a query using an aggregate function like so:
SELECT
[P].[KEY],
CONCAT (MIN([P].[Desc]), + ' ' + MAX([P].[Desc])) [PROPERTY_DESCRIPTION]
FROM [dbo].[PROP_DESC] [P]
WHERE [P].[KEY] = '5962417474'
GROUP BY [P].[KEY];
This worked great for two row entries but then I realized what if I have multiple records for a property description? So I wrote the following query to check if I had multiple property descriptions:
SELECT
[P].[KEY], COUNT([P].[KEY])
FROM [dbo].[PROP_DESC] [P]
GROUP BY [P].[KEY]
HAVING COUNT(*) > 2; -- Returns one record which is the above table result.
This gave me back a record with three descriptions so my original query will not work. How can I tackle this problem down when there are multiple fields?
Desired Output
+------------+---------------------------------------------------+----------+
| Key | Desc | Order_Id |
+------------+---------------------------------------------------+----------+
| 5962417474 | Big Yellow Door Orange Windows Blue Triangle Roof | 14775 |
+------------+---------------------------------------------------+----------+
It depends on what SQL language you're using, but you'll want to use some kind of group concat / array agg function. Eg:
SELECT
Key,
STRING_AGG(desc, ', ')
FROM TABLE
GROUP BY Key;
I have solved my problem with the following query for those that have the same problem and do not have access to STRING_AGG which is introduced in SQL Server 2017:
SELECT
[P].[KEY],
[PROPERTY_DESCRIPTION] = STUFF((
SELECT ' ' + [P2].[DESC]
FROM [dbo].[PROP_DESC] [P2]
WHERE [P].[KEY] = [P2].[KEY]
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 1, '')
FROM [dbo].[PROP_DESC] [P]
WHERE [P].[KEY] = '5962417474'
GROUP BY [P].[KEY]
There are many ways to do it in SQL server:
Below is one way:
SELECT key
,STUFF((SELECT '| ' + CAST(prop_desc AS VARCHAR(MAX)) [text()]
FROM PROP_DESC
WHERE key = t.key
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') prop_desc
FROM PROP_DESC t
GROUP BY key

Grouping values under elements FOR XML

I am trying to format the output of a query using FOR XML in SQL Server 2012.
Each PART_NO can have a varying number of SUPPLIER_PART_NUMBER's mapped to it.
The table has data in the following format.
PART_NO SUPPLIER_PART_NO
------- ----------------
AAA 1
AAA 2
BBB 3
BBB 4
BBB 5
The desired output is as follows where part AAA has two supplier part numbers and part BBB has three supplier part numbers, and the supplier part numbers are nested below the part number.
<root>
<item PartNo ="AAA">
<mpn>1</mpn>
<mpn>2</mpn>
</item>
<item PartNo ="BBB">
<mpn>3</mpn>
<mpn>4</mpn>
<mpn>5</mpn>
</item>
</root>
The closest I can get is below, but this does not group the mpn under PartNo:
SELECT
[PART_NO] as 'item/#PartNo',
[SUPPLIER_PART_NO] as 'mpn'
FROM
[dbo].[supplier_part_mapping2]
ORDER BY
PART_NO
FOR XML PATH('') , ROOT('root');
Thank you in advance
Try this:
SELECT
p1.PART_NO as 'item/#PartNo',
(SELECT
SUPPLIER_PART_NO AS 'mpn'
FROM
[dbo].[supplier_part_mapping2] p2
WHERE
p1.PART_NO = p2.PART_NO
FOR XML PATH(''), TYPE) AS 'item'
FROM
[dbo].[supplier_part_mapping2] p1
GROUP BY
PART_NO
ORDER BY
PART_NO
FOR XML PATH('') , ROOT('root');
This should produce:
You basically need to group by the PART_NO so that you get only one <item> entry for each distinct PART_NO, and you need to grab the "sub-elements" as a subquery to list them all together under one parent node.

Query (column) that lacks a root node

I am supposed to be extracting data from an XML column in sql server 2012. Some values for this column will have multiple nodes. Unfortunately, the XML does not have a root node, and so using the CROSS APPLY does not seem to work.
Simplified example:
<header><msg_type>TYPE_ONE</msg_type>
<status><status_1>aaaa</status_1><status_2>bbbb</status_2>
<node_1><customerID>1234</customerID><zipcode>11111</zipcode>...</node_1>
<node_2><customerID>1234</customerID><ordernum>12345</ordernum><data2>A</ordernum>...</node_2>
<node_2><customerID>1234</customerID><ordernum>34567></ordernum><data2>B</ordernum>...</node_2>
<node_3><customerID>1234</customerID><delivery>2014-05-05 14:00:00></delivery>...</node_3>
<node_1><customerID>ABCD</customerID><zipcode>12345</zipcode>...</node_1>
<node_2><customerID>ABCD</customerId><ordernum>123536</ordernum><data2>C</ordernum>...</node_2>
<node_3><customerID>ABCD</customerID><delivery>2014-05-05 16:00:00>...</node_3>
.
.
.
(... = more elements)
Here's an example using CROSS APPLY against one of the multi-node types:
select
t.InfoXML.value( '/node_1/customerID)[1]', 'varchar(50)' ) as CustomerId
, CA.Det.value( '(/node_2/ordernum)[1]', 'varchar(20)') as OrderNumber
, CA.Det.value( '(/node_2/data2)[1]', 'varchar(5)' ) as Data2
from TableUnderTest as t
CROSS APPLY t.InfoXML.nodes( '/node_2') as CA(Det)
where t.InfoXML.value( '(/header/msg_type)[1]', 'varchar(20)') = 'TYPE_ONE'
This had results
CustomerID OrderNumber Data2
================================
1234 12345 A
1234 12345 A
1234 12345 A
My current thought is to create a temporary table and insert the XML fields (that match the WHERE clause) after wrapping the XML value in a root node, and then trying to get the data from the temporary table. My current effort to set the first part up:
Declare #Rooted Table (Rec XML);
insert into #Rooted(Rec)
(
select (convert (XML, '<root>', + convert(varchar(MAX),
t.XmlData.query('./') + '<\root>')) as Rec
from TableUnderTest t
where t.XmlData.value( '(/header/msg_type)[1]', 'varchar(20)' ) = 'TYPE_ONE'
)
Right now, the above gives a syntax error.
What I want for output is something as follows:
CustomerID ZipCode OrderNumber Data2 Delivery status2
-------------------------------------------------------------------
1234 11111 12345 A 2014-05-05 14:00:00 aaaa
1234 11111 34567 B 2014-05-05 14:00:00 aaaa
ABCD 12345 123456 D 2014-05-05 15:00:00 aaaa
What would be the best approach to take? (This is for testing, not production, so performance is not critical.) I've only been learning to write sql queries for XML for the last month, so perhaps I'm overlooking something. It appears the critical issue is the lack of a root node for the XML, but how do I work around it?

How select XML fields node for all rows

I have a table like this :
YEAR int,
Fields XML
My XML column has this structure for all rows but with different values:
How I can get this result:
YEAR ID NAME LASTNAME
---------------------------------------------------
2011 1000 Nima Agha
2011 1001 Begha Begha
2011 1002 Jigha Jigha
2011 1003 Aba Aba
2012 1034 AAA BBB
...
thanks
How about this:
SELECT
Year,
E.P.value('(ID)[1]', 'INT') AS 'ID',
E.P.value('(Name)[1]', 'VARCHAR(50)') AS 'Name',
E.P.value('(LastName)[1]', 'VARCHAR(50)') AS 'LastName'
FROM
dbo.YourTable
CROSS APPLY
Fields.nodes('/Employees/Person') AS E(P)
You're basically selecting Year from the base table and then extracting each <Person> node from the Fields column into an "inline XML table" called E with a single XML column called P (you can choose whatever names you like for those) that you again query and extract the individual elements from .