In my SQL Server table one column has datatype as XML. It contains data as:
<P1>
<P2>
<P3 name='[1] name1', value='val1'> </P3>
<P3 name='[2] name2', value='val2'> </P3>
<P3 name='[3] name3', value='val3'> </P3>
</P2>
</p1>
I am fetching name1, name2, name3 using query:
select top(1)
col.value('(/P1[1]/P2[1]/P3/#name)[1]', 'VARCHAR(max)') as q1,
col.value('(/P1[1]/P2[1]/P3/#name)[2]', 'VARCHAR(max)') as q2,
col.value('(/P1[1]/P2[1]/P3/#name)[3]', 'VARCHAR(max)') as q3,
FROM table
How can I loop on this data to get all the names. Something like:
declare #x=1
while #x<4:
begin
select top(1)
col.value('(/P1[1]/P2[1]/P3/#name)[#x]', 'VARCHAR(max)') as q#x
from table
set #x=#x+1
end
Also how to loop on the same XML to get all the values?
I took the liberty of making your XML well-formed. SQL Server XQuery .nodes() method and CROSS APPLY are delivering what you are after.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata)
VALUES
(N'<P1>
<P2>
<P3 name="[1] name1" value="val1">
</P3>
<P3 name="[2] name2" value="val2">
</P3>
<P3 name="[3] name3" value="val3">
</P3>
</P2>
</P1>');
-- DDL and sample data population, end
SELECT tbl.ID
, c.value('#name','VARCHAR(30)') AS [name]
, c.value('#value','VARCHAR(30)') AS [value]
FROM #tbl AS tbl
CROSS APPLY tbl.xmldata.nodes('/P1/P2/P3') AS t(c);
Output
+----+-----------+-------+
| ID | name | value |
+----+-----------+-------+
| 1 | [1] name1 | val1 |
| 1 | [2] name2 | val2 |
| 1 | [3] name3 | val3 |
+----+-----------+-------+
Related
I have the following table that holds a column which is in XML:
Id
Label
Details
1
Test1
<terms><destination><email>email11#foo.com</email><email>email12#foo.com</email></destination><content>blabla</content></terms>
2
Test2
<terms><destination><email>email21#foo.com</email><email>email22#foo.com</email></destination><content>blabla</content></terms>
I would like a query that produces the following output:
Id
Label
Destination
1
Test1
email11#foo.com, email12#foo.com
2
Test2
email21#foo.com, email22#foo.com
Any clue on how I can concat the XML email node values as a column along the related columns (Id and Label)?
Thanks ahead
select ID, Label,
stuff(
details.query('for $step in /terms/destination/email/text() return concat(", ", string($step))')
.value('.', 'nvarchar(max)'),
1, 2, '')
from #tbl;
Please try the following solution.
Because DDL and sample data population were not provided,
assumption is that the Details column is of XML data type.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Label VARCHAR(20), Details XML);
INSERT INTO #tbl (Label, Details) VALUES
('Test1',N'<terms><destination><email>email11#foo.com</email><email>email12#foo.com</email></destination><content>blabla</content></terms>'),
('Test2',N'<terms><destination><email>email21#foo.com</email><email>email22#foo.com</email></destination><content>blabla</content></terms>');
-- DDL and sample data population, end
SELECT ID, Label
, REPLACE(Details.query('data(/terms/destination/email/text())').value('.','VARCHAR(MAX)'), SPACE(1), ', ') AS Destination
FROM #tbl;
Output
+----+-------+----------------------------------+
| ID | Label | Destination |
+----+-------+----------------------------------+
| 1 | Test1 | email11#foo.com, email12#foo.com |
| 2 | Test2 | email21#foo.com, email22#foo.com |
+----+-------+----------------------------------+
I'm sorry, I didn't fully explain what I'm looking for. Here is whatI am using:
The table contains xml in the following format"
<row>
<POLICYNUMBER>IFH6000258-04</POLICYNUMBER>
<POLICY_NO>CFH6000258</POLICY_NO>
<POLICY_MOD>03</POLICY_MOD>
</row>
declare #mystuff xml;
set #mystuff = (select top 1 * from TMP_APP for xml path)
INSERT INTO [dbo].[FredTest]
(
[xmlstuff]
)
VALUES
(
#mystuff
)
DECLARE #hldit table (ID int not null,xmldata xml)
* set #hldit = (select xmlstuff from FredTest)
Select entr.value('local-name(.)', 'VARCHAR(50)') as nme,
entr.value('(.)[1]', 'varchar(50)') dta
From #hldit
Cross apply
XmlData.nodes('/root/row/*') as xt(entr)
I get an error on the set(see *). I can't seem to populate the table.
I want to insert the output into a table with 2 columns,'name' and 'value' and have a separate record for each pair.
The output should look like;
Name Value
record 1 Policynumber IFH6000258-04
record 2 Policy_no CFH6000258
record 3 Policy_mod. 03
I feel like I am close. Everything runs but I can't populate the #hldit table. I hope this explains my problem.
Thanks
I am new to XML. I have to extract records, store them in an xml data field in a different table, then extract the xml data and store the name/value pairs in another table. I'm good up to creating the xml data. What I can't figure out is how to take the raw xml from the table, create a table with the name/value pairs stored as individual records. I have looked and tried everything out there but still now luck. I'm hoping the final rows in the new table would look like:
name value
--------------------------
firstname Fred
lastname jones
address 123 here street
instead of like this:
firstname="fred" lastname="jones address="123 here street"
I'll even take it in xml PATH format instead of RAW.
Thanks for any help you can give.
It seems I know what you are after.
Please try the following conceptual example.
It will work in MS SQL Server 2005 onwards.
SQL #1, attributes based
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata) VALUES
(N'<root>
<r firstname="fred" lastname="jones" address="123 here street"/>
<r firstname="Laura" lastname="Bush" address="257 somewhere street"/>
</root>');
-- DDL and sample data population, end
SELECT c.value('local-name(.)','VARCHAR(30)') AS [Column]
, c.value('.', 'VARCHAR(100)') AS [Value]
FROM #tbl
CROSS APPLY xmldata.nodes('/root/r/#*') AS t(c);
Output
+-----------+----------------------+
| Column | Value |
+-----------+----------------------+
| firstname | fred |
| lastname | jones |
| address | 123 here street |
| firstname | Laura |
| lastname | Bush |
| address | 257 somewhere street |
+-----------+----------------------+
SQL #2, elements based
-- DDL and sample data population, start
DECLARE #destination TABLE (ID INT, pos INT, [Name] VARCHAR(30), [Value] VARCHAR(100));
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata) VALUES
(N'<row>
<POLICYNUMBER>IFH6000258-04</POLICYNUMBER>
<POLICY_NO>CFH6000258</POLICY_NO>
<POLICY_MOD>03</POLICY_MOD>
</row>');
-- DDL and sample data population, end
INSERT INTO #destination (ID, pos, [Name],[Value])
SELECT ID
, c.value('let $n := . return count(../*[. << $n])+1', 'INT') AS pos
, c.value('local-name(.)','VARCHAR(30)') AS [Name]
, c.value('.', 'VARCHAR(100)') AS [Value]
FROM #tbl
CROSS APPLY xmldata.nodes('/row/*') AS t(c);
-- test
SELECT * FROM #destination;
Output
+----+-----+--------------+---------------+
| ID | pos | Name | Value |
+----+-----+--------------+---------------+
| 1 | 1 | POLICYNUMBER | IFH6000258-04 |
| 1 | 2 | POLICY_NO | CFH6000258 |
| 1 | 3 | POLICY_MOD | 03 |
+----+-----+--------------+---------------+
I have an xml value
<ITEMS>
<ITEM><ID>1</ID><NAME>John</NAME></ITEM>
<ITEM><ID>5</ID><NAME>James</NAME></ITEM>
</ITEMS>
I am able to shred the above xml into tables of ID and Name column using the below query
Declare #X xml
select x.r.value('(ID)[1]','int') as [ID],
x.r.value('(DATA)[1]','VARCHAR(100)') AS [DATA]
FROM #X.nodes ('/ITEMS/ITEM') AS x(r)
But how will i able to do this when the above xml is present in a row.
S.No COMPANY DATA
1 ABC </ITEMS><ITEM><ID>1</ID><NAME>John</Name>....
I need to populate like below
S.No COMPANY ID NAME
1 ABC 1 John
2 ABC 5 James
Note : The Data column in the table is of varchar data type and not xml data type.
First, you can use a CTE to convert it to XML data type. Second way is via a derived table.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (SequentialNo INT PRIMARY KEY, Company VARCHAR(20), [DATA] VARCHAR(MAX));
INSERT INTO #tbl (SequentialNo, Company, [DATA])
VALUES
(1, 'ABC', '<ITEMS>
<ITEM>
<ID>1</ID>
<NAME>John</NAME>
</ITEM>
<ITEM>
<ID>5</ID>
<NAME>James</NAME>
</ITEM>
</ITEMS>');
-- DDL and sample data population, end
-- Method #1
-- CTE
;WITH rs AS
(
SELECT *, TRY_CAST([DATA] AS XML) AS [xmldata]
FROM #tbl
)
SELECT SequentialNo
, Company
, col.value('(ID/text())[1]','INT') AS ID
, col.value('(NAME/text())[1]','VARCHAR(40)') AS [Name]
FROM rs AS tbl
CROSS APPLY tbl.[xmldata].nodes('/ITEMS/ITEM') AS tab(col);
-- Method #2
-- Derived table
SELECT SequentialNo
, Company
, col.value('(ID/text())[1]','INT') AS ID
, col.value('(NAME/text())[1]','VARCHAR(40)') AS [Name]
FROM (SELECT *, TRY_CAST([DATA] AS XML) AS [xmldata]
FROM #tbl) AS tbl
CROSS APPLY tbl.[xmldata].nodes('/ITEMS/ITEM') AS tab(col);
Output
+--------------+---------+----+-------+
| SequentialNo | Company | ID | Name |
+--------------+---------+----+-------+
| 1 | ABC | 1 | John |
| 1 | ABC | 5 | James |
+--------------+---------+----+-------+
Create Table #tmp(ID int IDentity(1,1), XMLData XML)
Insert Into #tmp(XMLData)
Values('<SampleXML>
<Fruit>
<Fruits>Apple</Fruits>
<Fruits>Pineapple</Fruits>
</Fruit>
<Fruit>
<Fruits>Grapes</Fruits>
<Fruits>Melon</Fruits>
</Fruit>
</SampleXML>')
SELECT
ID,
A.x.query('data(.)') as name,
Row_Number() over(order by A.x) as number
FROM #tmp
CROSS APPLY XMLData.nodes('SampleXML/Fruit/Fruits') AS A(x)
This results into the following:
ID name number
1 Apple 1
1 Pineapple 2
1 Grapes 3
1 Melon 4
but I do want it to look like this:
ID name number
1 Apple 1
1 Pineapple 1
1 Grapes 2
1 Melon 2
I want to know in which "Fruit" element the "Fruits" were found.
You can use following SQL, please be aware that using "OVER XML nodes" is an undocumented and unsupported feature:
DECLARE #MyXML XML
SET #MyXML = '<SampleXML>
<Fruit>
<Fruits>Apple</Fruits>
<Fruits>Pineapple</Fruits>
</Fruit>
<Fruit>
<Fruits>Grapes</Fruits>
<Fruits>Melon</Fruits>
</Fruit>
</SampleXML>'
SELECT
Friuts.col.query('data(.)') as name,
DENSE_RANK() over(order by Friut.col) as number
FROM #MyXML.nodes('SampleXML/Fruit') AS Friut(col)
CROSS APPLY Friut.col.nodes('./Fruits') AS Friuts(col)
Here is another method by using a Node Order Comparison operator. Here is the link: Node Order Comparison Operators
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE(ID INT IDENTITY PRIMARY KEY, XMLData XML);
INSERT INTO #tbl (XMLData)
VALUES (N'<SampleXML>
<Fruit>
<Fruits>Apple</Fruits>
<Fruits>Pineapple</Fruits>
</Fruit>
<Fruit>
<Fruits>Grapes</Fruits>
<Fruits>Melon</Fruits>
</Fruit>
</SampleXML>');
-- DDL and sample data population, end
-- Method #1
-- by Piotr, adjusted for a table and optimized
SELECT Friuts.col.value('(./text())[1]', 'VARCHAR(30)') as [name],
DENSE_RANK() OVER(ORDER BY Friut.col) as number
FROM #tbl AS tbl
CROSS APPLY tbl.XMLData.nodes('/SampleXML/Fruit') AS Friut(col)
CROSS APPLY Friut.col.nodes('./Fruits') AS Friuts(col);
-- Method #2
-- by using a Node Order Comparison operator
SELECT ID
, col.value('let $n := . return count(../../*[. << $n])', 'INT') AS pos
, col.value('(./text())[1]', 'VARCHAR(30)') as [name]
FROM #tbl AS tbl
CROSS APPLY tbl.XMLData.nodes('/SampleXML/Fruit/Fruits') AS tab(col);
Output
+----+-----+-----------+
| ID | pos | name |
+----+-----+-----------+
| 1 | 1 | Apple |
| 1 | 1 | Pineapple |
| 1 | 2 | Grapes |
| 1 | 2 | Melon |
+----+-----+-----------+
I have two tables, tempUsers and tempItems. These two tables have a one to many relationship.
When I use an inner join on these two tables the result looks like this:
**user | Category**
Jack | Shoes
Jack | Tie
Jack | Glass
Peggy | Shoe
Peggy | Skirt
Peggy | Bat
Peggy | Cat
Bruce | Laptop
Bruce | Beer
Chuck | Cell Phone
I would instead like a result that looks like this:
**User | Category1 | Category2 | Category3 | Category4**
Jack | Shoes | Tie | Glass | .....
Peggy | Shoe | Skirt | Bat | Cat
Bruce | Laptop | Beer |..... |......
Chuck | Cell Phone | ..... |....... |
The number of distinct categories in the category is dynamic - there can be any number of them for a given item.
How can I produce this result?
There are a few ways that you can transform the data from rows into columns.
Since you are using SQL Server 2008, then you can use the PIVOT function.
I would suggest using the row_number() function to assist in pivoting the data. If you have a known number of values, then you could hard-code the query:
select user, category1, category2, category3, category4
from
(
select [user], category,
'Category'+cast(row_number() over(partition by [user]
order by [user]) as varchar(3)) rn
from yt
) d
pivot
(
max(category)
for rn in (category1, category2, category3, category4)
) piv;
See SQL Fiddle with Demo.
For your situation you stated that you will have an unknown number of values that need to be columns. In that case, you will want to use dynamic SQL to generate the query string to execute:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Category'+cast(row_number() over(partition by [user]
order by [user]) as varchar(3)))
from yt
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [user],' + #cols + '
from
(
select [user], category,
''Category''+cast(row_number() over(partition by [user]
order by [user]) as varchar(3)) rn
from yt
) d
pivot
(
max(category)
for rn in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo. Both give a result:
| USER | CATEGORY1 | CATEGORY2 | CATEGORY3 | CATEGORY4 |
----------------------------------------------------------
| Bruce | Laptop | Beer | (null) | (null) |
| Chuck | Cell Phone | (null) | (null) | (null) |
| Jack | Shoes | Tie | Glass | (null) |
| Peggy | Shoe | Skirt | Bat | Cat |
Sql Server does allow you to pivot data. However, like other relational database, it still requires that you know at the outset of a query how many columns (and of what type) the results will be, even with the PIVOT. The best you can hope for here is to use queries, combined with dynamic sql (building the query string in code at runtime), to first find out who has the most categories, and then build a query that PIVOTs your data to look for that many items.
The normal solution to pivoting with an unknown number of columns is do the pivot client side, from the code that calls into the server.
Here is the solution using multiple tables. This solution is entirely based on bluefeet's solution. I have just added user id.
create table #tmpUsers
(user_id int, user_name varchar(255));
insert into #tmpUsers values (1,'Jack');
insert into #tmpUsers values (2,'Peggy');
insert into #tmpUsers values (3,'Bruce');
insert into #tmpUsers values (4,'Chuck');
create table #tmpItems
(user_id int, category varchar(255));
insert into #tmpItems values(1,'Shoes');
insert into #tmpItems values(1,'Tie');
insert into #tmpItems values(1,'Glass');
insert into #tmpItems values(2,'Shoe');
insert into #tmpItems values(2,'Skirt');
insert into #tmpItems values(2,'Bat');
insert into #tmpItems values(2,'Cat');
insert into #tmpItems values(3,'Laptop');
insert into #tmpItems values(3,'Beer');
insert into #tmpItems values(4,'Cell Phone');
select TU.user_name,TI.category from #tmpUsers TU inner join #tmpItems TI on TU.user_id=TI.user_id
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Category'+cast(row_number() over(partition by TU.[user_id]
order by TU.[user_id]) as varchar(3)))
from #tmpUsers TU inner join #tmpItems TI on TU.user_id=TI.user_id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [user_name],' + #cols + '
from
(
select TU.[user_name], TI.category,
''Category''+cast(row_number() over(partition by TU.[user_id]
order by TU.[user_id] ) as varchar(3)) rn
from #tmpUsers TU inner join #tmpItems TI on TU.user_id=TI.user_id
) d
pivot
(
max(category)
for rn in (' + #cols + ')
) p '
execute(#query)
drop table #tmpUsers
drop table #tmpItems