Shredding XML column data into rows in SQL - sql-server

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 |
+--------------+---------+----+-------+

Related

SQL SERVER: concatenate node values of XML column

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 |
+----+-------+----------------------------------+

convert xml data stored in a table to individual name/value pairs stored as records in a different table sql

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 |
+----+-----+--------------+---------------+

Loop on XML data

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 |
+----+-----------+-------+

using regex in microsoft sql server management studio

I have a table in which I copy the data based on an condition and I insert it into the same table with a different ID.As follows:
SET IDENTITY_INSERT Table ON
INSERT INTO Table (ID,GroupID,Name,link,etc..)
SELECT
(SELECT MAX(ID) FROM Table) + ROW_NUMBER()OVER (ORDER BY ID),
10500,
Name,
link
FROM Table
WHERE GroupID =10400
SET IDENTITY_INSERT Table OFF
this gives me the following table
**ID | GroupID | Link**
3 | 10400 |/testsDatas/10400/Uploads
4 | 10500 |/testsDatas/10400/Uploads //this is a new entry that the above query will enter.
The question I have is when the above query copies a row how can I change /testsDatas/10400/ to /testsDatas/10500/?
so that it looks like the following
**ID | GroupID | Link**
3 | 10400 |/testsDatas/10400/Uploads
4 | 10500 |/testsDatas/10500/Uploads //desired output
there is mulitple rows of data,with more columns that I did not add.How do I achieve this?
Would using REPLACE work for you? A simple example:]
DECLARE #table TABLE ( ID INT, name VARCHAR(50), link VARCHAR(50) )
INSERT INTO #table
VALUES
( 3, '10400', '/testsDatas/10400/Uploads' )
INSERT INTO #table
SELECT
(
SELECT MAX(ID)
FROM #table
) + ROW_NUMBER() OVER (ORDER BY ID),
10500,
REPLACE( link, name, 10500 )
FROM #table
SELECT *
FROM #table
My results:

How to display data horizontally in SQL Server?

How do I display my table data horizontally?
This is my table definition
create table [User]
(
Id int primary key identity(1,1),
Name varchar(50),
Gender varchar(10)
)
This is the data I have in my SQL Server table
+====+=======+========+
| Id | Name | Gender |
+====+=======+========+
| 1 | Fahad | Male |
+----+-------+--------+
| 2 | Saad | Male |
+----+-------+--------+
| 3 | Asif | Male |
+====+=======+========+
and I want to show it horizontally like this
+========+=======+======+======+
| Id | 1 | 2 | 3 |
+========+=======+======+======+
| Name | Fahad | Saad | Asif |
+--------+-------+------+------+
| Gender | Male | Male | Male |
+========+=======+======+======+
Perhaps a combination of UNPIVOT and PIVOT?
(Although your columns need to be of the same type for this to work, which I've changed in your table, or you can just CAST in a SELECT/CTE etc)
CREATE table [User](
Id int primary key identity(1,1),
Name varchar(50),
Gender varchar(50)
)
SET IDENTITY_INSERT [User] ON
INSERT INTO [User](Id,Name,Gender) VALUES
(1, 'Fahad','Male'),
(2,'Saad','Male'),
(3,'Asif','Male')
SELECT * FROM [User]
UNPIVOT ([Value] FOR Cols IN ([Name],[Gender])) Unp
PIVOT (MAX([Value]) FOR Id IN ([1],[2],[3])) Piv
Cols 1 2 3
------ ------ ------ -------
Gender Male Male Male
Name Fahad Saad Asif
(2 row(s) affected)
CASE can also be used to achieve the same - there are tons of examples on SO.
Edit: Excellent example Simple way to transpose columns and rows in Sql?
(and this is probably a dup of that question)
Yes, it seems we might need to do combination of UNPIVOT and PIVOT.
Try below, It may provide you the exact result as what you expect. Please change your design first
Gender varchar(10) to Gender varchar(50)
Try below,
;WITH cte AS(
SELECT *
FROM [User]
UNPIVOT([Value] FOR Cols IN ([Name], [Gender])) Unp
PIVOT(MAX([Value]) FOR Id IN ([1], [2], [3])) Piv
)
SELECT Cols AS Id,
[1],
[2],
[3]
FROM cte
ORDER BY
Id DESC
Here is a stored procedure that works on any given table. It presumes that the table key is in the first column.
IF OBJECT_ID(N'[Invert]','P') IS NOT NULL
DROP PROCEDURE [Invert]
GO
CREATE PROCEDURE dbo.[Invert] #tbl sysname, #top int=1000 AS
DECLARE #key sysname SELECT #key=COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME=#tbl AND ORDINAL_POSITION=1
DECLARE #sql nvarchar(max), #ids varchar(max)
SET #sql='SELECT TOP '+CAST(#top as varchar(9))+' #out=COALESCE(#out+'','','''')+QUOTENAME('
+QUOTENAME(#key)+') FROM '+QUOTENAME(#tbl)+' ORDER BY '+QUOTENAME(#key)
EXECUTE sp_executesql #sql, N'#out varchar(max) OUTPUT', #out=#ids OUTPUT
SET #sql=NULL
SELECT #sql=COALESCE(#sql+' UNION ALL ','')+'SELECT '''+COLUMN_NAME+''' AS '+QUOTENAME(#key)
+ ',* FROM (SELECT TOP '+CAST(#top as varchar(9))+' '+QUOTENAME(#key)+' k,CAST('
+ QUOTENAME(COLUMN_NAME)+'as varchar(8000)) m FROM '+QUOTENAME(#tbl)
+' ORDER BY '+QUOTENAME(#key)+') t PIVOT (MAX(m) FOR k IN ('+#ids+')) x'+CHAR(13)
FROM INFORMATION_SCHEMA.COLUMNS c WHERE TABLE_NAME=#tbl AND c.ORDINAL_POSITION>1
ORDER BY c.ORDINAL_POSITION
EXECUTE(#sql)
GO
The stored procedure uses PIVOT to pivot each column. UNPIVOT is nice, but can only be used if all the columns have the same type (including length). The procedure generates a dynamic SELECT that uses UNION ALL operator to combine PIVOTs for each column (except the key). The list of key values (#ids) is also dynamically generated because the PIVOT command expects an explicit column list.
Then you can call it like this:
EXEC Invert [User]
The second optional parameter is the top clause (the default is 1000). Below is an example that returns a maximum of 5 rows:
EXEC Invert [User], 5
create table [User]
(
Id int primary key identity(1,1),
Name varchar(50),
Gender varchar(50),sal varchar(50)
)SET IDENTITY_INSERT [User] ON
--give same data type and size to all of field
INSERT INTO [User](Id,Name,Gender,sal) VALUES
(1, 'Fahad','Male',10000),
(2,'Saad','Male',20000),
(3,'Asif','Male',30000)
SELECT * FROM [User]
UNPIVOT ([Val] FOR Cols IN (name,gender,sal)) Unp
PIVOT (MAX([Val]) FOR Id IN ([1],[2],[3])) Piv
Cols 1 2 3
------ ------ ------ -------
Gender Male Male Male
Name Fahad Saad Asif
sal 10000 20000 30000

Resources