Splitting a string of unlimited length SQL - sql

I have a data column with values like this:
Table1
ID|GROUPNAME |MEMBER
1|GRP1_ML_Unit1_Role1|GRP=User1,DC=com;GRP=User2,DC=com
2|GRP2_ML_Unit2_Role2|GRP=User3,DC=com;GRP=User4,DC=com;GRP=User5,DC=com
3|GRP3_ML_Unit3_Role3|GRP=User6,DC=com;GRP=User7,DC=com;GRP=User8,DC=com;GRP=User8,DC=com
Expected output
ID|GRP1 |GRP2|GRP3 |GRP4 |MEM1 |MEM2 |MEM3 |MEM4|MEM5|
1 |GRP1 |ML |Unit1|Role1|GRP=User1,DC=com|GRP=User2,DC=com| | |
2 |GRP2 |ML |Unit2|Role2|GRP=User3,DC=com|GRP=User4,DC=com|GRP=User5,DC=com| |
3 |GRP3 |ML |Unit3|Role3|GRP=User6,DC=com|GRP=User7,DC=com|GRP=User8,DC=com|GRP=User8,DC=com |
Thanks,
Ryl

The completed solution is below with the sample data you gave me.
First, create a temp table and fill it with data.
-- Drop the table
drop table #member;
go
-- Sample table
create table #member
(
member_id int not null,
group_name varchar(256),
member_data varchar(8000)
);
go
-- Sample data
insert into #member values
(1, 'GRP1_ML_Unit1_Role1', 'GRP=User1,DC=com;GRP=User2,DC=com'),
(2, 'GRP2_ML_Unit2_Role2', 'GRP=User3,DC=com;GRP=User4,DC=com;GRP=User5,DC=com'),
(3, 'GRP3_ML_Unit3_Role3', 'GRP=User6,DC=com;GRP=User7,DC=com;GRP=User8,DC=com;GRP=User8,DC=com');
go
-- Show the data
select * from #member;
go
Second, copy down one of the many string splitters out there. I ended up installing Jeff Moden's string spliter for 8K max strings.
The query is almost there. However, each column we want is a row. We need to dynamically pivot the table.
--
-- Almost there!
--
-- Data in columns, instead of rows
select m.member_id, m.group_name, s.Item as cols_data, 'MEM' + cast(s.ItemNumber as varchar(6)) as cols_name from #member as m
CROSS APPLY dbo.DelimitedSplit8k(m.member_data,';') s
go
Last but not least, figure out the number of columns. Write dynamic TSQL to pivot our dat and get our result.
--
-- Write dynamic sql to solve
--
DECLARE
#cols AS nvarchar(MAX),
#query AS nvarchar(MAX);
-- Get a dynamic number of columns
SET #cols = STUFF(
(
SELECT distinct ',' + QUOTENAME(c.cols_name)
FROM
(
select m.member_id, m.group_name, s.Item as cols_data, 'MEM' + cast(s.ItemNumber as varchar(6)) as cols_name from #member as m
CROSS APPLY dbo.DelimitedSplit8k(m.member_data,';') s
) as c
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
,1,1,'');
print #cols;
-- Make dynamic pivot query
set #query = 'SELECT member_id as ID1, group_name as GROUP1, ' + #cols + ' from
(
select m.member_id, m.group_name, s.Item as cols_data, ''MEM'' + cast(s.ItemNumber as varchar(6)) as cols_name from #member as m
CROSS APPLY dbo.DelimitedSplit8k(m.member_data, '';'') s
) x
pivot
(
max(cols_data)
for cols_name in (' + #cols + ')
) p ';
execute(#query)
A screen shot of the results in the desired format.

Related

Execute create table query from column in SQL server

I have two select statements which are to create table and insert values into table. How to execute all the queries inside the column at one go? Below is my code and the output:
select n.*
into #norm
from specNormalization n
select n.*
into #raw
from rawdata n
select distinct 'CREATE TABLE raw' + c.desttablename + ' (' + STUFF(
(select ','+QUOTENAME( c.[destfieldname] ) + c.datatype
from #norm c
group by c.destfieldname, c.datatype
for xml path ('')),1,1,'') as createTableSQL
select distinct 'INSERT INTO raw' + c.desttablename +
select d.contents
from #raw d join #norm c on d.tablename = c.desttablename
as insertTableSQL
rawdata table
desttablename
destfieldname
datatype
rawtable
SbjNum
int
rawtable
Surveyor
nvarchar(20)
rawtable
Location
nvarchar(20)
rawtable2
SbjNum
int
rawtable2
Name
nvarchar(20)
rawtable2
Address
nvarchar(20)
specnomalization table
tablename
destfieldname
contents
rawtable
SbjNum
1
rawtable
Surveyor
Alex
rawtable
Location
Georgia
rawtable2
SbjNum
1
rawtable2
Name
Sandra
rawtable2
Address
Portland
createTableSQL
CREATE TABLE rawtable ([Sbjnum])int, ([Surveyor])nvarchar(200), ([Location])nvarchar(200)
CREATE TABLE rawtable2 ([Sbjnum])int, ([Name])nvarchar(200), ([Address])nvarchar(200)
insertTableSQL
INSERT INTO rawtable SELECT [Sbjnum], [Surveyor], [Location] from #raw
INSERT INTO rawtable2 SELECT [Sbjnum], [Name], [Address] from #raw
You can create the create query by using stuff. Since you would have multiple tables so you have to group by the query to return multiple table names and it's columns separately. So the result query you can set into a variable and can execute by using
EXEC sp_executesql
So you can easily create as much as tables based on your table data.
The following query will help you to create multiple tables. This you can optimize if needed, but this will give you the insight to do the same for inserting values to the tables respectively.
Declare #sql nvarchar(MAX) = ( SELECT ' CREATE TABLE '+ desttablename +' (' + STUFF((SELECT ', ' + sn.[destfieldname] + ' ' +datatype
FROM dbo.specNormalization As sn
ORDER BY sn.destfieldname
FOR XML PATH(''), TYPE).value('.[1]', 'varchar(max)'), 1, 2, '') + ' )'
FROM dbo.specNormalization as P2
GROUP BY P2.desttablename FOR XML PATH('') )
-- Uncomment to see the created query
-- SELECT #sql
-- To execute the created query. This will create the tables with column and datatype
EXEC sp_executesql #sql
Hope this helps. Happy coding :)
Too long for a comment. Try to generate SELECT .. INTO .. FROM to create and populate a target table. Kind of
SELECT cast([Sbjnum] as int) [Sbjnum], cast([Surveyor] as nvarchar(200)) [Surveyor], cast([Location] as nvarchar(200)) [Location]
INTO rawtable
FROM #raw
If the source types are the same as target, skip cast functions. Can't tell what is the query to generate it as no sample data are provided.

Pivot multiple rows in initial table

I have a table which i would like to pivot to show how many categories a person is affiliated with...
I would like to pivot this to show:
There are a lot more members and categories but the theory i believe should be the same.
I have attempted this however it only shows the first line for each.
Thanks in advance
Will
#Will, this is the logic you need. Basically a pivot function on the column of interest.
DECLARE #tbl TABLE (RegNo varchar(20), Category varchar(20), Number int)
INSERT INTO #tbl
SELECT 'R1050162', 'Gym', 1 UNION ALL
SELECT 'R1050162', 'Personal Trainer', 1 UNION ALL
SELECT 'R0093126', 'Group Exercise', 1 UNION ALL
SELECT 'R0143614', 'Yoga Teacher', 1
SELECT *
FROM
#tbl
PIVOT
(
SUM(Number)
FOR Category IN ([Gym], [Personal Trainer], [Group Exercise], [Yoga Teacher]
)
) AS PivotTable;
Output is below:
You need to use just sum the Number field in PIVOT function and for numerous categories get a category list:
DECLARE #categories AS NVARCHAR(MAX),
#your_query AS NVARCHAR(MAX);
select #categories = STUFF((SELECT distinct ',' + QUOTENAME(Category)
FROM your_table
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT RegNo, ' + #categories + ' from
(
SELECT RegNo, Category, Number FROM your_table) tab
PIVOT
(
SUM(Number)
FOR Category IN (' + #categories + ')
) p iv
ORDER BY piv.RegNo'
execute(#your_query)

Cast column name to specific character during Pivot table creation - SQL

I have a table test which has a column Label. It has Data which is longers than 50 characters in length.
When I create a pivot table from the 'test' table , it uses those long charactered data as column name.
My requirement is to user cast function to limit the column names to say 26 characters.
I use the below script , but it doesnt work as desired.
create table V_Test as
select * from
(select * from Test) x
pivot (sum(Average) for Label in (
S03_CreatePlansdadsada,
S03_CreatePlan_T01_NavigateTosdsadsaded,
S03_CreatePlan_T03_abcdefgmanagementsdasda,
S03_CreatePlan_T16_SetStatusToOngoingasdasda,
S03_CreatePlan_T17_Ldsdssdadsadas
)
) p
Thanks in advance for the help
Since the label names are hard-coded anyway, then using aliases for them should be fine.
On MS SQL Server it would look something like this.
Just using a variable table and a temporary table for the purpose of demonstration.
DECLARE #Test TABLE (PlanID int, Label varchar(50), Average int);
insert into #Test values
(1,'S03_CreatePlansdadsada',10),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',20),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',30),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',40),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',50),
(1,'S03_CreatePlansdadsada',60),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',70),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',80),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',90),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',100);
IF OBJECT_ID('tempdb..#tmpTest') IS NOT NULL DROP TABLE #tmpTest;
select
PlanID,
S03_CreatePlansdadsada as CreatePlans,
S03_CreatePlan_T01_NavigateTosdsadsaded as T01_NavigateTo,
S03_CreatePlan_T03_abcdefgmanagementsdasda as T03_managements,
S03_CreatePlan_T16_SetStatusToOngoingasdasda as T16_SetStatusToOng,
S03_CreatePlan_T17_Ldsdssdadsadas as T17_Lsd
into #tmpTest
from #Test
pivot (sum(Average) for Label in (
S03_CreatePlansdadsada,
S03_CreatePlan_T01_NavigateTosdsadsaded,
S03_CreatePlan_T03_abcdefgmanagementsdasda,
S03_CreatePlan_T16_SetStatusToOngoingasdasda,
S03_CreatePlan_T17_Ldsdssdadsadas
)
) p;
select * from #tmpTest;
This would return the following results:
PlanID CreatePlans T01_NavigateTo T03_managements T16_SetStatusToOng T17_Lsd
1 70 90 110 130 150
The dynamic way is a bit more complicated, since the SQL needs to be constructed so it can be executed. (beware of code injection)
IF OBJECT_ID('tempdb..#tmpTestData') IS NOT NULL DROP TABLE #tmpTestData;
CREATE TABLE #tmpTestData (PlanID int, Label varchar(50), Average int);
insert into #tmpTestData values
(1,'S03_CreatePlansdadsada',10),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',20),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',30),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',40),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',50),
(1,'S03_CreatePlansdadsada',60),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',70),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',80),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',90),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',100);
declare #Columns nvarchar(max);
declare #AliasedColumns nvarchar(max);
set #Columns = STUFF((SELECT ', ' + QUOTENAME(Label) FROM #tmpTestData GROUP BY Label FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(max)') ,1,1,'');
set #AliasedColumns = STUFF((SELECT ', ' + QUOTENAME(Label) +' as '+ QUOTENAME(substring(Label,5,14)) FROM #tmpTestData GROUP BY Label FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(max)') ,1,1,'');
declare #SQL nvarchar(max);
IF OBJECT_ID('tempdb..##tmpPivotTest') IS NOT NULL DROP TABLE ##tmpPivotTest;
set #SQL = 'select PlanID, '+ #AliasedColumns + '
into ##tmpPivotTest
from #tmpTestData pivot (sum(Average) for Label in ('+ #Columns +')) p';
--select #SQL;
exec (#SQL);
select * from ##tmpPivotTest;
IF OBJECT_ID('tempdb..##tmpPivotTest') IS NOT NULL DROP TABLE ##tmpPivotTest;
For an Oracle database it would look something like this:
CREATE TABLE V_Test AS
SELECT * FROM (select Label, Average from Test)
PIVOT (
SUM(Average) AS sum_average FOR (Label) IN (
'S03_CreatePlansdadsada' as CreatePlans,
'S03_CreatePlan_T01_NavigateTosdsadsaded' as T01_NavigateTo,
'S03_CreatePlan_T03_abcdefgmanagementsdasda' as T03_Managements,
'S03_CreatePlan_T16_SetStatusToOngoingasdasda' as T16_SetStatusToOng,
'S03_CreatePlan_T17_Ldsdssdadsadas' as T17_Lsd
)
) p;

SQL XML Column's Elements to separate columns

I have a SQL table with an XML column. I would like to separate out the XML elements into their own columns within a view.
I am able to do this using .value, however I don't always know what the element names are. For example in the select below the c element is missing.
create table #temp (Id int, Name varchar(32), taskdata xml)
insert into #temp values
(1, 'Fred','<data><a>Red</a><b>Apple</b></data>'),
(2, 'Mary','<data><a>Blue</a><b>Ball</b></data>'),
(3, 'Paul','<data><a>Green</a><b>Tree</b></data>'),
(4, 'Lisa','<data><a>Yellow</a><b>Hat</b><c>House</c></data>')
select Id
,Name
,Taskdata.value('(/data/a)[1]', 'nvarchar(max)') AS a
,Taskdata.value('(/data/b)[1]', 'nvarchar(max)') AS b
from #temp
drop table #temp
I can get a list of all the elements names using:
select distinct T.N.value('local-name(.)','nvarchar(64)') ColNames
from #temp
cross apply Taskdata.nodes('//data/*') as T(N)
However I cant work out how to replace:
Taskdata.value('(/data/a)[1]', 'nvarchar(max)') AS a
For something more dynamic.
EDIT
If you need a fully generic approach you might try dynamic SQL:
DECLARE #cmd VARCHAR(1000)=
'select Id
,Name' +
(
SELECT DISTINCT',Taskdata.value(''(/data/' + TheNode.value('local-name(.)','nvarchar(64)') + ')[1]'', ''nvarchar(max)'') AS [' + TheNode.value('local-name(.)','nvarchar(64)') + '] '
FROM #temp AS innerT
CROSS APPLY innerT.taskdata.nodes('/data/*') AS ThisIs(TheNode)
FOR XML PATH('')
)
+
'from #temp;'
EXEC (#cmd);
EDIT 2 - use this to create a VIEW
Views cannot use temp tables, had to change your #temp to a normal table...
create table temp (Id int, Name varchar(32), taskdata xml)
insert into temp values
(1, 'Fred','<data><a>Red</a><b>Apple</b></data>'),
(2, 'Mary','<data><a>Blue</a><b>Ball</b></data>'),
(3, 'Paul','<data><a>Green</a><b>Tree</b></data>'),
(4, 'Lisa','<data><a>Yellow</a><b>Hat</b><c>House</c></data>')
DECLARE #cmd VARCHAR(1000)=
'CREATE VIEW dbo.SomeName AS select Id
,Name' +
(
SELECT DISTINCT',Taskdata.value(''(/data/' + TheNode.value('local-name(.)','nvarchar(64)') + ')[1]'', ''nvarchar(max)'') AS [' + TheNode.value('local-name(.)','nvarchar(64)') + '] '
FROM temp AS innerT
CROSS APPLY innerT.taskdata.nodes('/data/*') AS ThisIs(TheNode)
FOR XML PATH('')
)
+
'from temp;'
EXEC (#cmd);
GO
SELECT * FROM dbo.SomeName;
GO
drop view dbo.SomeName;
drop table temp;
previous
The problem with XML is: You have to know the structure, at least some things your data has in common: Is there always a root element "data"? Are there always 1:n inner elements and nothing else? What is their maximum number? How would you know which element is missing, if you have a and c but no b?
This would be one approach:
select Id
,Name
,Taskdata.value('/data[1]/*[1]', 'nvarchar(max)') AS a
,Taskdata.value('/data[1]/*[2]', 'nvarchar(max)') AS b
,Taskdata.value('/data[1]/*[3]', 'nvarchar(max)') AS c
from #temp
You would get the same if you know the inner element's names by querying with
,Taskdata.value('(/data/c)[1]', 'nvarchar(max)') AS c
The result
Id Name a b c
1 Fred Red Apple NULL
2 Mary Blue Ball NULL
3 Paul Green Tree NULL
4 Lisa Yellow Hat House

sql query to show results vertically

How to transform this:
ID Name Description
1 Test1a TestDesc1a
1 Test1b TestDesc1b
2 Test2a TestDesc2a
2 Test2b TestDesc2b
into this:
ID Column 1 2
1 Name test1a test1b
1 Description testDesc1a testDesc1b
You ask for complex pivoting table. Read about this here: http://msdn.microsoft.com/en-us/library/ms177410.aspx
You need to use a PIVOT query.
A basic discussion of PIVOT queries can be found at [MSDN][1].
This is a tricky question to solve, however the output specified is not proper/ conflicting. Below is similar solution which you can try
Sample Table creation:
CREATE TABLE [dbo].[TestTable](
[Id] [int] NULL,
[Name] [nvarchar](50) NULL,
[Description] [nvarchar](50) NULL)
Inserting sample values:
INSERT INTO TestTable VALUES (1,'Test1a','TestDesc1a')
INSERT INTO TestTable VALUES (2,'Test1b','TestDesc1b')
INSERT INTO TestTable VALUES (3,'Test2a','TestDesc2a')
INSERT INTO TestTable VALUES (4,'Test2b','TestDesc2b')
Query to get the desired output using Pivot:
SELECT 'Name' AS [Column], [1], [2],[3],[4]
FROM
(SELECT Name, id from TestTable) AS ST
PIVOT
(Max(Name) FOR ID IN ([1], [2],[3],[4])) AS PT
UNION
SELECT 'Description' AS [Column], [1], [2],[3],[4]
FROM
(SELECT id,[Description] from TestTable) AS ST
PIVOT
(Max([Description]) FOR ID IN ([1], [2],[3],[4])) AS PT
ORDER BY [Column] DESC
OutPut:
Column 1 2 3 4
Name Test1a Test1b Test2a Test2b
Description TestDesc1a TestDesc1b TestDesc2a TestDesc2b
Hope this helps to solve your question.
You can use a static PIVOT if you only are going to have a few columns but I am guessing that you will have more than 2 IDs so you will probably want to use a Dynamic PIVOT for this query. Using a dynamic pivot will allow you to have more than the two ids you provided, it will grab all of the Ids from the table and then PIVOT:
create table temp
(
id int,
name varchar(10),
description varchar(20)
)
insert into temp values (1, 'Test1a', 'TestDesc1a')
insert into temp values (1, 'Test1b', 'TestDesc1b')
insert into temp values (2, 'Test2a', 'TestDesc2a')
insert into temp values (2, 'Test2b', 'TestDesc2b')
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.id)
FROM temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ''Name'' as [Column], ' + #cols + ' from
(
select id
, name
from temp
) x
pivot
(
max(name)
for id in (' + #cols + ')
) p
UNION
SELECT ''Description'' as [Column], ' + #cols + ' from
(
select id
, description
from temp
) x
pivot
(
max(description)
for id in (' + #cols + ')
) p '
execute(#query)
drop table temp
Results:
Column 1 2
Description TestDesc1b TestDesc2b
Name Test1b Test2b