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!
Related
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
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)
There are single SQL Table with xml-type field:
| EntityId | EntityType | Xml column |
------------------------------------------------------
| 1 | Employee | `<productId>1</productId>`|
------------------------------------------------------
| 1 | Product | `<name>apple</name>` |
------------------------------------------------------
| 7 | Shop | `<country>...</country>` | |
-----------------------------------------------------|
What I need is a to filter table row by Xml node value:
SELECT * WHERE (EntityId='1' AND EntityType='Employee')
OR ( EntityId=SomeFuncToGetXmlFieldByNodeName('productId') )
Can u point me on how to write that SomeFuncToGetXmlFieldByNodeName(fieldName)
Looks like you want a function like this.
CREATE FUNCTION [dbo].[SomeFuncToGetXmlFieldByNodeName]
(
#NodeName nvarchar(100),
#XML xml
)
RETURNS nvarchar(max)
AS
BEGIN
RETURN #XML.value('(*[local-name(.) = sql:variable("#NodeName")]/text())[1]', 'nvarchar(max)')
END
It takes a node name and a some XML as parameter and returns the value in the node.
Use the function like this:
select T.EntityId,
T.EntityType,
T.[Xml column]
from YourTable as T
where T.EntityID = 1 and
T.EntityType = 'Employee' or
T.EntityId = dbo.SomeFuncToGetXmlFieldByNodeName('productId', T.[Xml column])
Instead of using the above I want to recommend you to try a query that does not use the scalar valued function. It uses exist() Method (xml Data Type) instead.
select T.EntityId,
T.EntityType,
T.[Xml column]
from YourTable as T
where T.EntityID = 1 and
T.EntityType = 'Employee' or
T.[Xml column].exist('/productId[. = sql:column("T.EntityID")]') = 1
I think you're looking for the documentation!
The "query" method is likely what you'll need. See examples in the linked article.
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.
I need to perform a pivot on an XML column in a table, where the XML contains multiple elements with a number of attributes. The attributes in each element is always the same, however the number of elements will vary. Let me give an example...
FormEntryId | FormXML | DateCreated
====================================================================================
1 |<Root> | 10/15/2009
| <Form> |
| <FormData FieldName="Username" FieldValue="stevem" /> |
| <FormData FieldName="FirstName" FieldValue="Steve" /> |
| <FormData FieldName="LastName" FieldValue="Mesa" /> |
| </Form> |
|</Root> |
| |
------------------------------------------------------------------------------------
2 |<Root> | 10/16/2009
| <Form> |
| <FormData FieldName="Username" FieldValue="bobs" /> |
| <FormData FieldName="FirstName" FieldValue="Bob" /> |
| <FormData FieldName="LastName" FieldValue="Suggs" /> |
| <FormData FieldName="NewField" FieldValue="test" /> |
| </Form> |
|</Root> |
I need to wind up with a result set for each distinct FieldName attribute values (In this example, Username, FirstName, LastName, and NewField) with their corresponding FieldValue attributes as the value. The results for the example I gave above would look like:
FormEntryId | Username | FirstName | LastName | NewField | DateCreated
======================================================================
1 | stevem | Steve | Mesa | NULL | 10/15/2009
----------------------------------------------------------------------
2 | bobs | Bob | Suggs | test | 10/16/2009
I've figured out a way to accomplish this with static columns
SELECT
FormEntryId,
FormXML.value('/Root[1]/Form[1]/FormData[#FieldName="Username"][1]/#FieldValue','varchar(max)') AS Username,
FormXML.value('/Root[1]/Form[1]/FormData[#FieldName="FirstName"][1]/#FieldValue','varchar(max)') AS FirstName,
FormXML.value('/Root[1]/Form[1]/FormData[#FieldName="LastName"][1]/#FieldValue','varchar(max)') AS LastName,
FormXML.value('/Root[1]/Form[1]/FormData[#FieldName="NewField"][1]/#FieldValue','varchar(max)') AS NewField,
DateCreated
FROM FormEntry
However I would like to see if there's a method to have the columns be dynamic based on the distinct set of "FieldName" attribute values.
Have a look at this dynamic pivot and more recently this one - you basically need to be able to SELECT DISTINCT FieldName to use this technique to build your query dynamically.
Here's the full answer for your particular problem (note that there is a column order weakness when generating the list from the distinct attributes in knowing what order the columns should appear):
DECLARE #template AS varchar(MAX)
SET #template = 'SELECT
FormEntryId
,{#col_list}
,DateCreated
FROM FormEntry'
DECLARE #col_template AS varchar(MAX)
SET #col_template = 'FormXML.value(''/Root[1]/Form[1]/FormData[#FieldName="{FieldName}"][1]/#FieldValue'',''varchar(max)'') AS {FieldName}'
DECLARE #col_list AS varchar(MAX)
;WITH FieldNames AS (
SELECT DISTINCT FieldName
FROM FormEntry
CROSS APPLY (
SELECT X.FieldName.value('#FieldName', 'varchar(255)')
FROM FormXML.nodes('/Root[1]/Form[1]/FormData') AS X(FieldName)
) AS Y (FieldName)
)
SELECT #col_list = COALESCE(#col_list + ',', '') + REPLACE(#col_template, '{FieldName}', FieldName)
FROM FieldNames
DECLARE #sql AS varchar(MAX)
SET #sql = REPLACE(#template, '{#col_list}', #col_list)
EXEC (#sql)
Dynamic pivot isn't built into the language for good reason. It would be necessary to scan the entire table containing potential column names before the structure of the result were known. As a result, the table structure of the dynamic pivot statement would be unknown before run time. This creates many problems regarding parsing and interpretation of language.
If you decide to implement dynamic pivot on your own, watch out for SQL injection opportunities. Be sure to apply QUOTENAME or equivalent to the values you plan to use as column names in your result. Also consider what result you want if the number of distinct values in your source that will become column names exceeds the allowed number of columns of a result set.