We have to find a query parsing XML to have a result like this one:
COL_ACTION N COL_NAME_I COL_VALUE_AFTER
---------- ----------- ---------------- -------------------
INS 1 N 1
INS 1 TST_ID 28
INS 1 TST_DATA data2
INS 2 N 2
INS 2 TST_ID 27
INS 2 TST_DATA data1
The value of column N depend on the value of the first "row" (COL_NAME_I = N) of the XML dataset
The XML contains:
DECLARE #XML XML =
N'
<DATASET>
<XML_INS>
<IROW xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<N>1</N>
<TST_ID>28</TST_ID>
<TST_DATA>data2</TST_DATA>
</IROW>
<IROW xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<N>2</N>
<TST_ID>27</TST_ID>
<TST_DATA>data1</TST_DATA>
</IROW>
</XML_INS>
</DATASET>
';
The query I was able to do is:
SELECT RIGHT(ca.value('local-name(.)','CHAR(7)'), 3) AS COL_ACTION,
x.value('(//N)[1]', 'int') AS N,
-- cn.value('./text()[1]','int') AS NI,
ci.value('local-name(.)','sysname') AS COL_NAME_I,
ci.value('./text()[1]','nvarchar(max)') AS COL_VALUE_AFTER
FROM #XML.nodes('.') AS T(x)
OUTER APPLY #XML.nodes('DATASET/*') AS TA(ca)
OUTER APPLY #XML.nodes('DATASET/XML_INS/IROW/N/*') AS TN(cn)
OUTER APPLY #XML.nodes('DATASET/XML_INS/IROW/*') AS TI(ci);
The problem remains on the "N" column that does no have the expected value:
COL_ACTION N COL_NAME_I COL_VALUE_AFTER
---------- ----------- ---------------- -------------------
INS 1 N 1
INS 1 TST_ID 28
INS 1 TST_DATA data2
INS 1 N 2
INS 1 TST_ID 27
INS 1 TST_DATA data1
I tryed different approach, as seen in SQL comment, but does not produce the good result...
You need each .nodes to refer to the previous, in order to only bring back its children. Otherwise you get every descendant node for the whole document.
SELECT RIGHT(action.value('local-name(.)','CHAR(7)'), 3) AS COL_ACTION,
irow.value('(N/text())[1]', 'int') AS N,
ci.value('local-name(.)','sysname') AS COL_NAME_I,
ci.value('text()[1]','nvarchar(max)') AS COL_VALUE_AFTER
FROM #XML.nodes('DATASET/*') AS x1(action)
OUTER APPLY x1.action.nodes('IROW') AS x2(irow)
OUTER APPLY x2.irow.nodes('*') AS TI(ci);
db<>fiddle
This should do it:
SELECT
irow.value('local-name(..)', 'NVARCHAR(100)') AS COL_ACTION,
irow.value('(./N)[1]', 'NVARCHAR(100)') AS N,
child.value('local-name(.)', 'NVARCHAR(100)') AS COL_NAME_I,
child.value('.', 'NVARCHAR(100)') AS COL_VALUE_AFTER
FROM #XML.nodes('/DATASET/*/IROW') AS n1(irow)
CROSS APPLY irow.nodes('./*') AS n2(child)
i am a new contributer so sorry if i got something wrong.
I used this approach on my work : Link
My sample working code :
CREATE TABLE #FieldPermissionsTable
(
TE_ID VARCHAR(MAX),
Value VARCHAR(MAX),
Value2 VARCHAR(MAX),
NAVI_USER VARCHAR(MAX)
)
--SET #FieldPermissionAccessXML = '<Row_ID><Elements ID="1" Value="1" Value2="NULLnROLLA" Navi_User="testuser" /><Elements ID="3" Value="tespit" Value2="NULLnROLLA" Navi_User="testuser" /><Elements ID="6" Value="aciklama" Value2="NULLnROLLA" Navi_User="testuser" /></Row_ID>'
-- SELECT #FieldPermissionAccessXML = [dbo].[to_xml_replace] (#FieldPermissionAccessXML)
EXEC sp_xml_preparedocument #XmlHandle output,#FieldPermissionAccessXML
--'<FieldPermissions>
--<FieldPermission FieldName="" RoleID="1" Access="1" />
--<FieldPermission FieldName="" RoleID="2" Access="1" />'
--select * from JMP_FieldPermissions
INSERT INTO #FieldPermissionsTable
SELECT *--ID,Value, Value2, Navi_User
FROM OPENXML (#XmlHandle, './Row_ID/Elements')
WITH (TE_ID VARCHAR(MAX) '#ID',
Value VARCHAR(MAX) '#Value',
Value2 VARCHAR(MAX) '#Value2',
NAVI_USER VARCHAR(MAX) '#Navi_User'
)
I'm currently trying to select multiple nodes for a xml variable that get passed into a stored procedure. But I only get one node from it's xml. How can I list all of the item names and string values?
Test code:
DECLARE #T TABLE (AllXml ntext)
INSERT #T VALUES('<error>
<item name="item 1">
<value string="string 1" />
</item>
<item name="item 2">
<value string="string 2" />
</item>
<item name="item 3">
<value string="string 3" />
</item>
</error>')
SELECT
CAST(AllXml as xml).value('(/error/item/#name)[1]', 'varchar(100)' ),
CAST(AllXml as xml).value('(/error/item/value/#string)[1]', 'varchar(max)' )
FROM #T
Desired result:
Item 1 string 1
Item 2 string 2
Item 3 string 3
You can achieve it using the CROSS Apply. And Sub-Select.
SELECT
m.c.value('(#name)[1]', 'varchar(100)') AS Name,
m.c.value('(value/#string)[1]', 'varchar(max)') AS Value
FROM
(
SELECT CAST(AllXml as xml) AllXml
FROM #T
) AS data
CROSS APPLY AllXml.nodes('/error/item') as m(c)
Or you can use the one more CROSS APPLY as below,
SELECT
m.c.value('(#name)[1]', 'varchar(100)') AS Name,
m.c.value('(value/#string)[1]', 'varchar(max)') AS Value
FROM #T
CROSS APPLY (SELECT CAST(AllXml AS XML)) as D(D)
CROSS APPLY D.D.nodes('/error/item') as m(c)
I'm writing one stored procedure, which I have to create a xml column from db.
µ = CHAR(181) this is value separator,
¶ = CHAR(182) this is row separator
This is the statement I wrote. I know its not well formed.
SELECT #xmlString= CAST('<root><Section> ID =' + REPLACE(REPLACE ('20211µ1¶20212µ2', CHAR(182),
'</Section><Section> ID ='),CHAR(181), ' Slno=') + '</Section></root>' AS XML)
This is the pattern which I need to display like this.
<root>
<sections id="20211" slno="1" ></sections>
<sections id="20215" slno="2" ></sections>
</root>
declare #s varchar(50) = '20211µ1¶20212µ2'
declare #xmlString xml
;with C as
(
select T.N.value('value[1]', 'int') as id,
T.N.value('value[2]', 'int') as slno
from (select cast('<item><value>'+replace(replace(#s, 'µ','</value><value>'), '¶','</value></item><item><value>')+'</value></item>' as xml)) as X(XMLCol)
cross apply X.XMLCol.nodes('item') as T(N)
)
select #xmlString =
(
select C.id as [#id] ,
C.slno as [#slno]
from C
for xml path('sections'), root('root'), type
)
select #xmlString
Result:
<root>
<sections id="20211" slno="1" />
<sections id="20212" slno="2" />
</root>
I have a xml string from other system, I need select xml as a table in sql
<jrt>
<item>
<id>cell1</id>
<id>cell2</id>
<id>cell3</id>
</item>
<item>
<id>cell4</id>
<id>cell5</id>
<id>cell6</id>
</item>
</jrt>
The results should be:
row value
----------- -----
1 cell1
2 cell2
3 cell3
1 cell4
2 cell5
3 cell6
Notice: the row number is every item's id postion
Please help me how to use tsql select this xml?
declare #xml xml set #xml = '
<jrt>
<item>
<id>cell1</id>
<id>cell12</id>
<id>cell83</id>
</item>
<item>
<id>cell4</id>
<id>cell5</id>
<id>cell6</id>
</item>
</jrt>'
SELECT item.number itemrow,
id.number idrow,
o.c.value('.', 'nvarchar(max)') value
FROM master..spt_values item
inner join master..spt_values id on id.type='P' and id.number >= 1
CROSS APPLY #xml.nodes('/jrt/item[sql:column("item.number")]') n(c)
CROSS APPLY n.c.nodes('id[sql:column("id.number")]') o(c)
where item.type='P' and item.number >= 1
ORDER BY itemrow, idrow
With help from https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=383888
I have a three tables in SQL Server 2008 which are setup as follows:
EMPLOYEE TABLE
empid(PK)
1
2
joined to EMPLOYEEATTRIBUTES
dataId(PK) | empId(FK) | attributeid | attributeVal
10 | 1 | A1 | somevalue1
20 | 1 | A2 | somevalue2
30 | 2 | A1 | somevalue3
40 | 2 | A3 | somevalue4
joined to ATTRIBUTES
attributeid | attributeName
A1 | attribute1
A2 | attribute2
A3 | attribute3
I need to get the xml data out into the following format
<rows>
<row empid="1">
<attribute1>somevalue1</attribute1>
<attribute2>somevalue2</attribute1>
</row>
<row empid="2">
<attribute1>somevalue3</attribute1>
<attribute3>somevalue4</attribute1>
</row>
</rows>
Anyone know how this can be done??
If you want to skip all of the gory details and just see an answer, look at the SQL query at the bottom of this posting.
The main challenge here is that the various SQL Server FOR XML options cannot generate the dynamic element names stipulated in the desired output. Therefore, my first answer is to consider simply returning a conventional SQL result set and having the client generate the XML. It is a very simple streaming transformation. However, this might not be an option for you, so we continue on the path of having SQL Server generate the XML.
My second thought was to use SQL Server's built-in XQuery functionality to perform the transformation, thus:
/* WARNING: the following SQL does not work */
SELECT
CAST((SELECT * FROM data FOR XML RAW) AS XML)
.query('
<rows>
{
for $empId in distinct-values(/row/#empId)
return
<row empid="{$empId}">
{
for $attr in /row[#empId = $empId]
return
attribute { "attribute" } { $attr/#attributeValue }
}
</row>
}
</rows>
')
Alas, this does not work. SQL Server complains:
Msg 9315, Level 16, State 1, Line 25
XQuery [query()]: Only constant expressions are supported for the name expression
of computed element and attribute constructors.
Apparently, the XQuery implementation suffers from the same limitation as the FOR XML features. So, my second answer is to suggest generating the XML on the client side :) But if you insist on generating the XML from SQL, then fasten your seatbelts...
The overall strategy is going to be to abandon SQL Server's native facilities for SQL generation. Instead, we are going to build up the XML document using string concatenation. If this approach is offensive, you can stop reading now :)
Let's start with generating a sample dataset to play with:
SELECT NULL AS empId INTO employee WHERE 1=0
UNION SELECT 1
UNION SELECT 2
SELECT NULL AS dataId, NULL AS empId, NULL AS attributeId, NULL AS attributeVal INTO employeeAttributes WHERE 1=0
UNION SELECT 10, 1, 'A1', 'someValue1'
UNION SELECT 20, 1, 'A2', 'someValue2'
UNION SELECT 30, 2, 'A1', 'someValue3'
UNION SELECT 40, 2, 'A3', 'someValue4 & <>!'
SELECT NULL AS attributeId, NULL AS attributeName INTO attributes WHERE 1=0
UNION SELECT 'A1', 'attribute1'
UNION SELECT 'A2', 'attribute2'
UNION SELECT 'A3', 'attribute3'
Note that I have changed the value of the last attribute in the provided example to include some XML-unfriendly characters.
Now, put together a basic SQL query to perform the necessary joins:
SELECT
e.empId
, a.attributeName
, ea.attributeVal
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
which gives this result:
empId attributeName attributeVal
1 attribute1 someValue1
1 attribute2 someValue2
2 attribute1 someValue3
2 attribute3 someValue4 & <>!
Those funny characters in the last attribute are going to give us trouble. Let's change the query to escape them.
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
FROM cruftyData
)
SELECT * FROM data
with results:
empId attributeName attributeValXml
1 attribute1 someValue1
1 attribute2 someValue2
2 attribute1 someValue3
2 attribute3 someValue4 & <>!
This ensures that attribute values can now be safely used in an XML document. What about attribute names? The rules for XML attribute names are more strict than those for element content. We will assume that the attributes names are valid XML identifiers. If this is not true, then some scheme will need to be devised to convert the names in the database to valid XML names. This is left as an exercise to the reader :)
The next challenge is to ensure that the attributes are grouped together for each employee, and we can tell when we are at the first or last value in a group. Here is the updated query:
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName DESC) AS down
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName) AS up
FROM cruftyData
)
SELECT * FROM data ORDER BY 1, 2
The only change is to add the down and up columns to the result set:
empId attributeName attributeVal down up
1 attribute1 someValue1 2 1
1 attribute2 someValue2 1 2
2 attribute1 someValue3 2 1
2 attribute3 someValue4 & <>! 1 2
We can now identify the first attribute for an employee because up will be 1. The last attribute can be identified in similar fashion using the down column.
Armed with all of this, we are now equipped to perform the nasty business of building up the XML result using string concatenation.
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName DESC) AS down
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName) AS up
FROM cruftyData
)
, xmlData AS (
SELECT
empId
, up
, CASE WHEN up <> 1 THEN '' ELSE '<row id="' + CAST (empId AS NVARCHAR) + '">' END AS xml1
, '<' + attributeName + '>' + attributeVal + '</' + attributeName + '>' AS xml2
, CASE WHEN down <> 1 THEN '' ELSE '</row>' END AS xml3
FROM data
)
SELECT xml1, xml2, xml3
--SELECT #result = #result + 'wombat' + xmlString
FROM xmlData
ORDER BY empId, up
with the result:
xml1 xml2 xml3
<row id="1"> <attribute1>someValue1</attribute1>
<attribute2>someValue2</attribute2> </row>
<row id="2"> <attribute1>someValue3</attribute1>
<attribute3>someValue4 & <>!</attribute3> </row>
All that remains is to concatenate all of the rows together, and to add the root rows tags. Since T-SQL does not (yet) have a string concatenation aggregate function, we will resort to using a variable as an accumulator. Here is the final query, in all its hacky glory:
DECLARE #result AS NVARCHAR(MAX)
SELECT #result = '<rows>'
; WITH
cruftyData AS (
SELECT
e.empId
, a.attributeName
, (SELECT ea.attributeVal AS x FOR XML RAW) AS attributeValXml
FROM employee AS e
INNER JOIN employeeAttributes AS ea
ON ea.empId = e.empId
INNER JOIN attributes AS a
ON a.attributeId = ea.attributeId
)
, data AS (
SELECT
empId
, attributeName
, SUBSTRING(attributeValXml, 9, LEN(attributeValXml)-11) AS attributeVal
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName DESC) AS down
, ROW_NUMBER() OVER (PARTITION BY empId ORDER BY attributeName) AS up
FROM cruftyData
)
, xmlData AS (
SELECT
empId
, up
, CASE WHEN up <> 1 THEN '' ELSE '<row id="' + CAST (empId AS NVARCHAR) + '">' END AS xml1
, '<' + attributeName + '>' + attributeVal + '</' + attributeName + '>' AS xml2
, CASE WHEN down <> 1 THEN '' ELSE '</row>' END AS xml3
FROM data
)
SELECT #result = #result + xml1 + xml2 + xml3
FROM xmlData
ORDER BY empId, up
SELECT #result = #result + '</rows>'
SELECT #result
The XML ends up in the #result variable. You can check that it is well-formed XML using:
SELECT CAST(#result AS XML)
The final XML looks like this:
<rows><row id="1"><attribute1>someValue1</attribute1><attribute2>someValue2</attribute2></row><row id="2"><attribute1>someValue3</attribute1><attribute3>someValue4 & <>!</attribute3></row></rows>
You can get close - but you can't get your desired output 100%.
Using this query:
SELECT
EmpID AS '#empid',
(
SELECT
a.AttributeName AS '#name',
ea.AttributeVal
FROM dbo.EmployeeAttributes ea
INNER JOIN dbo.Attributes a ON ea.AttributeId = a.AttributeId
WHERE ea.EmpID = e.EmpID
FOR XML PATH ('attribute'), TYPE
)
FROM dbo.Employee e
FOR XML PATH('row'), ROOT('rows')
you get this output:
<rows>
<row empid="1">
<attribute name="Attribute1">
<AttributeVal>SomeValue1</AttributeVal>
</attribute>
<attribute name="attribute2">
<AttributeVal>SomeValue2</AttributeVal>
</attribute>
</row>
<row empid="2">
<attribute name="Attribute1">
<AttributeVal>SomeValue3</AttributeVal>
</attribute>
<attribute name="attribute3">
<AttributeVal>SomeValue4</AttributeVal>
</attribute>
</row>
</rows>
What you cannot do is make the inner XML nodes have tag names that match the attribute name - you have to use some fixed tag name (like <attribute> in my sample), and then apply the values that are retrieved from your tables as either attributes on those XML tags (like the name= attribute in my sample) or as XML element values.
As far as I know, there is no way to use the AttributeValue as the XML tag name....
Here's an answer, but the PIVOT command limits you in that you have to know the name of your attributes in advance. With a little tweaking, you could probably do this dynamically (try searching for dynamic pivot in SQL Server 2005):
DECLARE #Employee TABLE ( empid INT )
DECLARE #EA TABLE
(
dataid INT
, empid INT
, attributeid CHAR(2)
, AttributeVal VARCHAR(100)
)
DECLARE #Attributes TABLE
(
AttributeID CHAR(2)
, AttributeName VARCHAR(100)
)
INSERT INTO #Employee
VALUES ( 1 ),
( 2 )
INSERT INTO #EA
( dataid, empid, attributeid, AttributeVal )
VALUES ( 10, 1, 'A1', 'somevalue1' )
, ( 20, 1, 'A2', 'somevalue2' )
, ( 30, 2, 'A1', 'somevalue3' )
, ( 40, 2, 'A3', 'somevalue4' )
INSERT INTO #Attributes
( AttributeID, AttributeName )
VALUES ( 'A1', 'attribute1' )
,
( 'A2', 'attribute2' )
,
( 'A3', 'attribute3' )
SELECT empID as '#empid'
, attribute1
, attribute2
, attribute3
, attribute4
FROM ( SELECT e.empid
, a.AttributeName
, ea.AttributeVal
FROM #Employee e
JOIN #EA ea ON e.empid = ea.empid
JOIN #Attributes a ON ea.attributeid = a.attributeid
) ps PIVOT
( MIN(AttributeVal) FOR AttributeName IN ( [attribute1], [attribute2], [attribute3], [attribute4] ) ) AS pvt
FOR XML PATH('row'), ROOT('rows')