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
Related
I have a XML(edited) File with the following data:
<dept dept_id="1" dept_name="Marketing">
<progress>1</progress>
<employee empname="a">
<end> 1 </end>
<address addr1="123 abc">
...
</address>
</employee>
</dept>
<dept dept_id="2" dept_name="Sales">
<progress>1</progress>
<employee empname="b">
<end> 1 </end>
<address addr1="456 cde">
...
</address>
</employee>
</dept>
I put this file in AWS S3 and then used 'copy into' to transfer this data into a external table in snowflake in a variant column. Like Below:
copy into DB.AWS_S3_STAGE_SCHEMA.test_XML_copy
from #AWS_S3_LANDING/websiteXML/Test_xml.xml
FILE_FORMAT = ( TYPE = XML STRIP_OUTER_ELEMENT = TRUE ) ;
Now, I can query the data from this table and retrieve data like 'marketing' and 'sales', which are inside tags using the following query(I got the syntax from snowflake docs and used it. ):
SELECT
GET(xmldata, '#dept_id')::integer as dept_id,
GET(xmldata, '#dept_name')::string as dept_name
FROM test_XML_copy;
But, I cannot query the data inside tags which are in child nodes. For example: I need data like 'a' and '123 abc'.
If anybody can help me in this query, that would be appreciated.
Use a combination of XMLGET and GET functions to traverse nested objects in an XML document.
The former helps fetch an entire tag object found below the current tag, while the latter allows querying attributes and regular values within the current tag.
SELECT
-- <dept> (root)
GET(xmldata, '#dept_id')::integer as dept_id,
GET(xmldata, '#dept_name')::string as dept_name,
-- <dept>.<employee>
GET(XMLGET(xmldata, 'employee'), '#empname')::string as employee_name,
-- <dept>.<employee>.<address>
GET(XMLGET(XMLGET(xmldata, 'employee'), 'address'), '#addr1')::string as address_1
FROM test_XML_copy;
Which should yield:
+---------+-----------+---------------+-----------+
| DEPT_ID | DEPT_NAME | EMPLOYEE_NAME | ADDRESS_1 |
|---------+-----------+---------------+-----------|
| 1 | Marketing | a | 123 abc |
| 2 | Sales | b | 456 def |
| ... | ... | ... | ... |
+---------+-----------+---------------+-----------+
Your example data shows no repetition of child tags, but if they do repeat (such as multiple employee in each dept) then FLATTEN can first be used to produce one employee per row and the above approach can be reapplied. Alternatively, if it is a fixed form of tag structure and they are always ordered, you can use the instance number in XMLGET to point to each one (implicit default is 0, the first object).
An example that explodes the document into one row per employee and per address inner tags:
SELECT
xmldata:"#dept_id"::integer as dept_id,
xmldata:"#dept_name"::string as dept_name,
emp.value:"#empname"::string as employee_name,
addr.value:"#addr1"::string as address_1
FROM
test_XML_copy,
LATERAL FLATTEN(xmldata:"$") emp,
LATERAL FLATTEN(emp.value:"$") addr
WHERE emp.value:"#" = 'employee' AND addr.value:"#" = 'address';
(This yields results similar to the above, for the example provided in OP's question)
Note: You can also use the path syntax with $ and # characters to navigate the structure instead of nesting functions, but these rely on strict ordering of the input data structure:
-- See outer / inner structures in 'JSON' form
SELECT
xmldata:"#" dept_tag,
xmldata:"$" dept_tag_contents
FROM test_XML_copy;
-- Sample equivalent query using path expressions, relying on ordering:
SELECT
xmldata:"#dept_id",
xmldata:"#dept_name",
xmldata:"$"[1]."#empname",
xmldata:"$"[1]."$"[1]."#addr1"
FROM test_XML_copy;
To extract data from the child nodes you will need to use LATERAL FLATTEN to transform the data, e.g. to flatten the <employee> element and get the emp_name attribute:
select
XMLGET( xmldata, '#dept_id' ):"$"::string AS dept_id
, XMLGET( xmldata, '#dept_name' ):"$"::string AS dept_name
, XMLGET( emp.value, '#empname' ):"$"::string as empname
from
test_XML_copy
, lateral FLATTEN(test_XML_copy.xmldata:"$") emp
where emp.value like '<employee>%'
order by dept_id, empname;
This article has some very useful info on querying XML in Snowflake: https://community.snowflake.com/s/article/Querying-Nested-XML-in-Snowflake
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)
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.
I'm trying to pull data from XML (stored as NText) in a SQL Table.
Suppose we have two tables, each with XML:
| TABLE 1 | | TABLE 2 |
|ID| NAME |FIELD_DEFINITION| |ID|DEF_ID|VALUES|
|1 |FIELD 1| <XML1> | |1 | 1 |<XML2>|
|---------------------------| |----------------|
And suppose that rows 1 and 2 of XML1 looks like so:
ROW 1
-----
<def>
<prop name="Property 1" pdid="1"/>
<prop name="Property 2" pdid="2"/>
</def>
ROW 2
-----
<def>
<prop name="Property 1" pdid="3"/>
<prop name="Property 2" pdid="4"/>
</def>
And XML2 looks like so:
ROW 1
-----
<ps>
<p pdid="1" pvalue="Value 1"/>
<p pdid="2" pvalue="Value 2"/>
</ps>
ROW 2
-----
<ps>
<p pdid="3" pvalue="Value 3"/>
<p pdid="4" pvalue="Value 4"/>
</ps>
I'm trying to get all values for any properties named "Property 1" however the definition of the XML that denotes where the value is stored in Table 1 and the values are stored in Table 2.
I'm getting the pdid of the Property 1 field for each entry in Table 1 like so:
SELECT
t1.ID, t1.NAME,
CAST(t1.FIELD_DEFINITION AS XML).value('(/def/prop[#name = "Property 1"]/#pdid)[1]','varchar(10)') as FIELD_ID
FROM
[Table 1] t1
But how do I now pass that pdid value into an XQuery to pull the pvalue from Table 2? I was hoping I could do the above, and join Table 2 to Table 1 on t1.ID = t2.DEF_ID and then pass cp.FIELD_ID into the XQuery on t2.VALUES.value().
Is this possible? Or am I taking the wrong approach here?
After much head-banging (and "re-allocating" my time away from other work), I've managed to do it, I'll post here for anybody interested.
SELECT
t1.ID AS T1_ID, t1.NAME, t2.ID AS T2_ID,
CAST(CAST(t1.FIELD_DEFINITION AS NVARCHAR(MAX)) + CAST(t2.VALUES AS NVARCHAR(MAX)) AS XML).value
('for $d in (/def/prop[#name = "Property 1"]/#pdid)[1] return (/ps/p[#pdid = $d]/#pvalue)[1]','varchar(50)') as VAL
FROM
[Table 1] t1 INNER JOIN
[Table 2] t2 ON t1.ID = t2.DEF_ID
Good old FLWOR came to the rescue. Playing with XML in SQL will never be quick (~6s to return 1000 rows in my case) but it gets the job done!
I want to write a stored procedure that queries XML files after I have input a certain string pattern to look for.
I'm already stuck at the input parameters, consider the following XML-document.
<root>
<container>
<element>A</element>
<option>1</option>
</container>
<container>
<element>B</element>
<option>-1</option>
</container>
<container>
<element>C</element>
</container>
</root>
What I want to achieve is find and output a certain pattern combining element-tag and option-tag to a table.
For example: EXEC search "A1,B-1,C" is the input string and would be true which then needs to be put in a table. But "A,B,C" would be false.
I'm not that great with TSQL, so I don't know how I could split or arrange the search pattern so that I could use this to work with both element-tag and option-tag and put them into variables or so.
EDIT: The code below I think is going in the right direction but I have another big issue.
I need to analyze each value of the pattern with a corresponding table.
I better give an example: Input is "A1,B-1,C" btw input length should be flexible.
In my existing table I have 4 columns with the following data:
ID| Value | Meaning | Info
1 | A | text | text
2 | A-1 | text | text
3 | A1 | text | text
4 | B | text | text
5 | B-1 | text | text
and so on...
Now somehow I need to check each single input-string with the value and output the input-string with both "Meaning" and "Info" column to another table.
With the example above I would have to find the sequence of "A1,B-1,C" and then output the corresponding text (including the string) of the table above to a new table.
So that it could look like this:
| Value | Meaning | Info
| A1 | text | text
| B-1 | text | text
| C | text | text
I don't know if I'm making it too complicated with the above table or if a CASE / IF-ELSE structure in the procedure would work better.
Does anyone know how this can be achieved?
It looks as if you're using the wrong tools for the job but if these are pretty firm constraints you're working against then you could use the following, for example:
drop table #xml
drop table #queryparams
declare #x xml
select #x = '<code><root><items><element>A</element><option>1</option></items><items><element>B</element><option>-1</option></items><items><element>C</element></items></root></code>'
create table #xml (element varchar(60), [option] int null)
insert into #xml
select code.item.value('(element)[1]', 'varchar(60)'),
code.item.value('(option)[1]', 'int')
from
#x.nodes('/code/root/items') AS code(item)
declare #queryParam varchar(60)
select #queryParam = 'A1,B-1,C'
create table #queryparams (element varchar(60), [option] int null)
insert into #queryparams
select left(data,1), RIGHT(data, len(data)-1)
from dbo.split(#queryParam,',')
if not exists (
select *
from #xml x
left join #queryparams q
on x.element = q.element
where q.[option] is null
)
select 1
else
select 0
As marc_s suggests you're better off with <element> and <option> tags inside a common tag. I've opted for the poorly named <items> in this case.
This solution is nasty enough to suggest the approach isn't quite right somehow.