Pivot table in SQL Server 2008 r2 - sql-server-2008

I have a table with Comma Separated values in multiple Columns as
dept_rule |dept_descr|dept |dept_pos
-----------+----------+-------+--------
four rules |No descrrr|aaa,bbb|xxx,yyy
I want to seperate the values, as the below table
dept_rule |dept_descr|dept|dept_pos
----------+----------+----+--------
four rules|No descrrr|aaa |xxx
four rules|No descrrr|aaa |yyy
four rules|No descrrr|bbb |xxx
four rules|No descrrr|bbb |yyy
How to write query to do this.
Thanks in advance...

Here is the code for 2 values only in a column:
declare #a table (dept_rule varchar(10), dept_descr varchar(50), dept varchar(50), dept_pos varchar(50))
insert into #a values ('four rules','No descrrr', 'aaa,bbb', 'xxx,yyy')
select
dept_rule,
dept_descr,
dept_unp,
dept_post_unp
from
(
select
dept_rule,
dept_descr,
substring(dept, 0, charindex(',', dept)) as dept,
substring(dept, charindex(',', dept)+1, len(dept)) as dept1,
dept_post_unp
from
(
select
dept_rule,
dept_descr,
dept,
substring(dept_pos, 0, charindex(',', dept_pos)) as dept_pos,
substring(dept_pos, charindex(',', dept_pos)+1, len(dept_pos)) as dept_pos1
from #a
)to_unpivot1
unpivot
(
dept_post_unp for depts in(dept_pos, dept_pos1)
)unpivoted1
)to_unpivot2
unpivot
(
dept_unp for depts in(dept, dept1)
)unpivoted2

If dept and dept_pos have two values separated with comma, then this should help
CREATE TABLE #T1 (
dept_rule nvarchar(20),
dept_descr nvarchar(20),
dept nvarchar(20),
dept_pos nvarchar(20))
INSERT INTO #T1 VALUES (
'four rules',
'No descrrr',
'aaaa,bbbbb',
'xxx,yy'
)
;with T as (
select
dept_rule,
dept_descr,
dept,
dept_pos,
charindex(',', dept) pos1,
charindex(',', dept_pos) pos2
from #T1
)
select
dept_rule,
dept_descr,
LEFT(dept, pos1 - 1),
LEFT(dept_pos, pos2 - 1)
from T
UNION ALL
select
dept_rule,
dept_descr,
LEFT(dept, pos1 - 1),
RIGHT(dept_pos, LEN(dept_pos) - pos2)
from T
UNION ALL
select
dept_rule,
dept_descr,
RIGHT(dept, LEN(dept) - pos1),
LEFT(dept_pos, pos2 - 1)
from T
UNION ALL
select
dept_rule,
dept_descr,
RIGHT(dept, LEN(dept) - pos1),
RIGHT(dept_pos, LEN(dept_pos) - pos2)
from T
DROP TABLE #T1
UPDATE: More flexible solution - multiple separators. But each column with separators adds more complexity
-- separator
DECLARE #sep nvarchar(1) = ','
-- our data table
CREATE TABLE #T (
dept_rule nvarchar(20),
dept_descr nvarchar(20),
dept nvarchar(20),
dept_pos nvarchar(20)
)
-- sample data
INSERT INTO #T VALUES
('four rules', 'No descrrr', 'aaaaa,bbb,ccc', 'kk,ll,mm'),
('four rules', 'No descrrr', 'x,yyyy', 'sss,rrr'),
('four rules', 'No descrrr', 'zzz', 'xxxx,lll')
-- find occurences of separator in the column 'dept'
;WITH T AS (
SELECT
0 AS row,
0 AS start,
CHARINDEX(#sep, dept) pos,
dept
FROM #T
UNION ALL
SELECT
pos + 1,
pos,
CHARINDEX(#sep, dept, pos + 1),
dept
FROM T
WHERE
pos > 0
)
-- remember result of splitting first column
SELECT
#T.*,
a.part
INTO #Result1
FROM (
-- result with string parts
SELECT
T.dept,
T.start,
T.pos AS finish,
SUBSTRING(T.dept, T.start + 1, T.pos - (T.start + 1)) AS part
FROM T
WHERE
T.pos > 0
UNION ALL
-- but separators don't give us last part, append
SELECT
T.dept,
T.start,
T.pos AS finish,
RIGHT(T.dept, LEN(T.dept) - T.start) AS part
FROM T
WHERE
T.pos = 0
) a
INNER JOIN #T
ON #t.dept = a.dept
ORDER BY
a.dept,
a.start,
a.finish
-- SELECT * FROM #Result1
-- now second column
;WITH T2 AS (
SELECT
0 AS row,
0 AS start,
CHARINDEX(#sep, dept_pos) pos,
dept_pos
FROM #T
UNION ALL
SELECT
pos + 1,
pos,
CHARINDEX(#sep, dept_pos, pos + 1),
dept_pos
FROM T2
WHERE
pos > 0
)
-- append second column's splits to first result
SELECT
a.dept_rule,
a.dept_descr,
a.part AS part1,
b.part AS part2
FROM (
-- result with string parts
SELECT
T2.dept_pos,
T2.start,
T2.pos AS finish,
SUBSTRING(T2.dept_pos, T2.start + 1, T2.pos - (T2.start + 1)) AS part
FROM T2
WHERE
T2.pos > 0
UNION ALL
-- but separators don't give us last part, append
SELECT
T2.dept_pos,
T2.start,
T2.pos AS finish,
RIGHT(T2.dept_pos, LEN(T2.dept_pos) - T2.start) AS part
FROM T2
WHERE
T2.pos = 0
) b
INNER JOIN #Result1 a
ON a.dept_pos = b.dept_pos
-- clean up
DROP TABLE #T
DROP TABLE #Result1
UPDATE2: I based on first answer of Alex.K. to question SQL Server - find nth occurrence in a string - now it's improved and you may apply them to my solution
UPDATE3: Improved code based on link from UPDATE2
-- separator
DECLARE #sep nvarchar(1) = ','
-- our data table
CREATE TABLE #T (
dept_rule nvarchar(20),
dept_descr nvarchar(20),
dept nvarchar(20),
dept_pos nvarchar(20)
)
-- sample data
INSERT INTO #T VALUES
('four rules', 'No descrrr', 'aaaaa,bbb,ccc', 'kk,ll,mm'),
('four rules', 'No descrrr', 'x,yyyy', 'sss,rrr'),
('four rules', 'No descrrr', 'zzz', 'xxxx,lll')
-- find occurences of separator in the column 'dept'
;WITH T (dept, start, pos) AS (
SELECT
dept,
1,
CHARINDEX(#sep, dept)
FROM #T
UNION ALL
SELECT
dept,
pos + 1,
CHARINDEX(#sep, dept, pos + 1)
FROM T
WHERE
pos > 0
)
-- remember result of splitting first column
SELECT
#T.*,
a.token
INTO #Result1
FROM (
SELECT
*,
SUBSTRING(T.dept, T.start, CASE WHEN T.pos > 0 THEN T.pos - T.start ELSE LEN(T.dept) END) AS token
FROM T
) a
INNER JOIN #T
ON #t.dept = a.dept
ORDER BY
a.dept
-- now second column
;WITH T2 (dept_pos, start, pos) AS (
SELECT
dept_pos,
1,
CHARINDEX(#sep, dept_pos)
FROM #T
UNION ALL
SELECT
dept_pos,
pos + 1,
CHARINDEX(#sep, dept_pos, pos + 1)
FROM T2
WHERE
pos > 0
)
-- append second column's splits to first result
SELECT
a.dept_rule,
a.dept_descr,
a.token AS token1,
b.token AS token2
FROM (
SELECT
*,
SUBSTRING(T2.dept_pos, T2.start, CASE WHEN T2.pos > 0 THEN T2.pos - T2.start ELSE LEN(T2.dept_pos) END) AS token
FROM T2
) b
INNER JOIN #Result1 a
ON a.dept_pos = b.dept_pos
-- clean up
DROP TABLE #T
DROP TABLE #Result1

Related

single column to rows in sqlserver

I want to split the two columns into two rows in a single table separated by ‘;’ in Sql Server 2008. Please help me to resolve to solve this.
Columns like:
1;2;3;4;5;6;7; and a;b;c;d;e;f;g;
Output Rows like:
1 a
2 b
3 c
4 d
5 e
6 f
7 g
First you are going to need a split function as such :
CREATE function [dbo].[Split]
(
#string nvarchar(max),
#delimiter nvarchar(20)
)
returns #table table
(
[Value] nvarchar(max)
)
begin
declare #nextString nvarchar(max)
declare #pos int, #nextPos int
set #nextString = ''
set #string = #string + #delimiter
set #pos = charindex(#delimiter, #string)
set #nextPos = 1
while (#pos <> 0)
begin
set #nextString = substring(#string, 1, #pos - 1)
insert into #table
(
[Value]
)
values
(
#nextString
)
set #string = substring(#string, #pos + len(#delimiter), len(#string))
set #nextPos = #pos
set #pos = charindex(#delimiter, #string)
end
return
end
Then using this code :
SELECT col1.Value as val1,
col2.Value as val2
FROM
(SELECT Value,
ROW_NUMBER() over(order by value asc) as rownum
FROM (
VALUES('1;2;3;4;5;6;7')
) valued(X) CROSS APPLY
DBO.SPLIT(X,';') AS SPLITEDCOL ) as col1 INNER JOIN
(SELECT Value,
ROW_NUMBER() over(order by value asc) as rownum
FROM (
VALUES('A;B;C;D;E;F;G')
) valued1(X) CROSS APPLY
DBO.SPLIT(X,';') AS SPLITEDCOL ) as col2
ON COL1.rownum = col2.rownum
if your two columns are from a table you can select them as such :
SELECT Value,
ROW_NUMBER() over(order by value asc) as rownum
FROM YourTable CROSS APPLY
DBO.SPLIT(YourColumnName,';') AS SPLITEDCOL
Note that 1 subset using cross apply is necessary for each column you want to return in rows
Hope this helps
Declare #ID as Varchar(1000)
set #ID = '1;2;3;4;5;6;7;'
SELECT
LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))) AS ID
FROM
(
SELECT CAST('<XMLRoot><RowData>' + REPLACE(#ID,';','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x
)t
CROSS APPLY x.nodes('/XMLRoot/RowData')m(n)
DECLARE #Table1 TABLE(ID INT, Value char)
INSERT INTO #Table1 VALUES (1,'a'),(1,'b'),(1,'c'),(1,'d')
SELECT STUFF((SELECT '; ' + CAST(ID AS VARCHAR(10)) [text()]
FROM #Table1
WHERE ID = t.ID
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') ID
,STUFF((SELECT '; ' + CAST(Value AS VARCHAR(10)) [text()]
FROM #Table1
WHERE ID = t.ID
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') value
FROM #Table1 t

Store values in different variables in SQL, separated by (Comma) ","

I need to separate values and store them in different variables in SQL,
for example
a='3100,3101,3102,....'
And the output should be
x=3100
y=3101
z=3102
.
.
.
create function [dbo].[udf_splitstring] (#tokens varchar(max),
#delimiter varchar(5))
returns #split table (
token varchar(200) not null )
as
begin
declare #list xml
select #list = cast('<a>'
+ replace(#tokens, #delimiter, '</a><a>')
+ '</a>' as xml)
insert into #split
(token)
select ltrim(t.value('.', 'varchar(200)')) as data
from #list.nodes('/a') as x(t)
return
end
GO
declare #cad varchar(100)='3100,3101,3102'
select *,ROW_NUMBER() over (order by token ) as rn from udf_splitstring(#cad,',')
token rn
3100 1
3101 2
3102 3
The results of the Parse TVF can easily be incorporated into a JOIN, or an IN
Declare #a varchar(max)='3100,3101,3102'
Select * from [dbo].[udf-Str-Parse](#a,',')
Returns
RetSeq RetVal
1 3100
2 3101
3 3102
The UDF if needed (much faster than recursive, loops, and xml)
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(25))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = LTrim(RTrim(Substring(#String, A.N, A.L)))
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Much faster than str-Parse, but limited to 8K
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')
I suggest you to use following query, it's much faster than other functions like cross apply and udf.
SELECT
Variables
,S_DATA
FROM (
SELECT
Variables
,CASE WHEN LEN(LIST2)>0 THEN LTRIM(RTRIM(SUBSTRING(LIST2, NUMBER+1, CHARINDEX(',', LIST2, NUMBER+1)-NUMBER - 1)))
ELSE NULL
END AS S_DATA
,NUMBER
FROM(
SELECT Variables
,','+COMMA_SEPARETED_COLUMN+',' LIST2
FROM Tb1
)DT
LEFT OUTER JOIN TB N ON (N.NUMBER < LEN(DT.LIST2)) OR (N.NUMBER=1 AND DT.LIST2 IS NULL)
WHERE SUBSTRING(LIST2, NUMBER, 1) = ',' OR LIST2 IS NULL
) DT2
WHERE S_DATA<>''
and also you should create a table 'NUMBER' before running the above query.
CREATE TABLE TB (Number INT)
DECLARE #I INT=0
WHILE #I<1000
BEGIN
INSERT INTO TB VALUES (#I)
SET #I=#I+1
END

How to combine multiple rows into one row for a field in MS SQL 2008 R2 without using temp table or variable?

How do I accomplish my goal without using temp table or variable?
Table:
ID ModelNum Qty
123 ABC 4
123 DEF 4
Expected Result:
ID Models Qty
123 ABC | DEF 4
Thanks in advance!
DECLARE #T TABLE (ID INT,ModelNum CHAR(3),Qty INT)
INSERT INTO #T
VALUES
(123,'ABC',4),
(123,'DEF',4),
(123,'GLK',4)
SELECT DISTINCT ID, STUFF(C.List, 1, 2, '') Models, Qty
FROM #T t
CROSS APPLY (
SELECT '| ' + ModelNum
FROM #T
WHERE ID = t.ID
FOR XML PATH('')
)C(List)
Result Set
ID Models Qty
123 ABC| DEF| GLK 4
Hi how about this Query below:
I have did the same example with some different logic and different attribute.
I can get the expected OP, please response if you have any suggestions for me on btechit#hotmail.com.
Declare:
#ConcatTable table (Ename varchar(30), Empno int)
Insert into #ConcatTable values ('Steve', 100),('mathew', 100),('jon', 101),('tom', 101)
--select * from #ConcatTable
--select ROW_NUMBER()over(order by Empno)Row2,* from
--(select distinct Empno from #ConcatTable)C
declare #p varchar(100) = ''
select #p = #p+ ' '+Ename from (
select DENSE_RANK()over(order by Empno)as dens, * from #ConcatTable )A
where A.dens = 1
declare #q varchar(100) = ''
select #q = #q+ ' '+Ename from (
select DENSE_RANK()over(order by Empno)as dens, * from #ConcatTable )A
where A.dens = 2
--SELECT #p
--SELECT #q
declare #M table (Name varchar(30))
insert into #M
select * from(
select #p as v
union all
select #q as vv
)G
--SELECT ROW_NUMBER()over (order by Name desc)Rown1,* from #M
SELECT A.Name,CC.Empno FROM(
SELECT ROW_NUMBER()over (order by Name desc)Rown1,* FROM #M)A
inner join
(select ROW_NUMBER()over(order by Empno)Row2,* from
(select distinct Empno from #ConcatTable)C
)CC
on A.Rown1 = CC.Row2

Convert String to comma separated values

I have a table as below
DECLARE #T TABLE(Data VARCHAR(MAX))
INSERT INTO #T
SELECT 'SQL' UNION ALL SELECT 'JOB'
need output as below but without using any UDF.
Data String
------------
SQL S,Q,L
JOB J,O,B
Please help me on this
Sure you can :). You can make it shorter too...
DECLARE #T TABLE(Data VARCHAR(MAX))
INSERT INTO #T
SELECT 'SQL' UNION ALL SELECT 'JOB';
With cte as
(
Select Data, Len(Data) DataLength, 1 level
From #t
Union All
Select Data, DataLength - 1, level + 1
From cte
Where DataLength > 1
),
cte2 as
(
Select Data, SUBSTRING(Data, DataLength, 1) DataLetter, level
From cte
),
cte3 as
(
Select Data,
(
SELECT DataLetter + ','
FROM cte2 c
Where c.Data = cte2.Data
Order By level desc
FOR XML PATH(''), TYPE
).value('.[1]', 'NVARCHAR(1000)') DataComa
From cte2
Group By Data
)
Select Data, substring(DataComa, 1, Len(DataComa) - 1) Data2
From cte3
Late to the party, but here's a slightly shorter version:
DECLARE #T TABLE(Data VARCHAR(MAX));
INSERT INTO #T VALUES('SQL'),('JOB'),('FLOOB');
;WITH n AS (SELECT TOP (SELECT MAX(LEN(Data)) FROM #T)
n = ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects
),
t AS (SELECT n, Data, Letter = SUBSTRING(t.Data, n.n, 1) FROM n
INNER JOIN #T AS t ON SUBSTRING(t.Data, n.n, 1) > ''
)
SELECT Data, STUFF((SELECT ',' + letter FROM t AS t2
WHERE t2.Data = t.Data ORDER BY t2.n FOR XML PATH(''),
TYPE).value(N'./text()[1]', N'varchar(max)'), 1, 1, '')
FROM t GROUP BY Data;
Results:
FLOOB F,L,O,O,B
JOB J,O,B
SQL S,Q,L
It is very easy to do with UDF.
But If you want with out UDF, the only one way I can think of is
something like this
DECLARE #T TABLE(Data VARCHAR(MAX))
INSERT INTO #T
SELECT 'SQL' UNION ALL SELECT 'JOB'
select replace(replace(replace(data,'S','S,'),'Q','Q,'),'L','L,') from #T
here you have to replace all the 26 characters with replace function. ie, 'A' with 'A,' 'B' with 'B,' .... 'Z' with 'Z,'
Using the same approach I used for Initcap function here http://beyondrelational.com/modules/2/blogs/70/posts/10901/tsql-initcap-function-convert-a-string-to-proper-case.aspx
DECLARE #T TABLE(Data VARCHAR(MAX))
INSERT INTO #T
SELECT 'SQL' UNION ALL SELECT 'JOB'
select data,
upper(replace(replace(replace(replace(replace(replace(replace(
replace(replace(replace(replace(replace(replace(replace(
replace(replace(replace(replace(replace(replace(replace(
replace(replace(replace(replace(replace(
' '+data ,
' a','a,'),' b','b,'),'c','c,'),'d','d,'),'e','e,'),'f','f,'),
' g','g,'),' h','h,'),'i','i,'),'j','j,'),'k','k,'),'l','l,'),
' m','m,'),' n','n,'),'o','o,'),'p','p,'),'q','q,'),'r','r,'),
' s','s,'),' t','t,'),'u','u,'),'v','v,'),'w','w,'),'x','x,'),
' y','y,'),' z','z,')) as splitted_data
from
#t

Unable to find the matching child record using recursive CTE

I have the following table #t:
ParentId SkuName ChildId
P1 X1 C1
C1 X2 C2
C2 X3 C2
If I pass the ParentId = P1, the desired output is x3
i.e. the stopping condition is the last row matching record and get the sku name for that
row. If no row matched, then return null
My attempt (no working though)
DECLARE #t TABLE (ParentId Varchar(100), Name VARCHAR(20), ChildId Varchar(100))
INSERT INTO #t(ParentId, Name, ChildId)
SELECT 'P1', 'X1', 'C1' UNION ALL
SELECT 'C1', 'X2', 'C2' UNION ALL
SELECT 'C2', 'X3', 'C2'
Declare #ParentId Varchar(10) = 'P1'
;With CTE As
(
Select
Rn = ROW_NUMBER() Over(Order By (Select 1))
,ParentId
, Name
, ChildId
From #t Where ParentId = #ParentId
Union All
Select
Rn + 1
,pc.ParentId as Parents
,pc.Name
,pc.ChildId
FROM #t pc
JOIN CTE gp on pc.Childid = gp.Parentid
)
Select *
From CTE
Please help
The problem is with the JOIN in your second UNION query. You are joining on pc.Childid = gp.Parentid. You should be joining on pc.ParentId = gp.Childid.
Also, since your data has both the child and the parent as the same value, you will end up with infinite recursion unless you specify that the recursion should stop when the ParentId equals the Childid. (WHERE gp.ParentId <> gp.Childid)
Is this the result you are looking for?
DECLARE #t TABLE (ParentId VARCHAR(100), Name VARCHAR(20), ChildId VARCHAR(100))
INSERT INTO #t(ParentId, Name, ChildId)
SELECT 'P1', 'X1', 'C1' UNION ALL
SELECT 'C1', 'X2', 'C2' UNION ALL
SELECT 'C2', 'X3', 'C2'
DECLARE #ParentId VARCHAR(10) = 'P1'
;With CTE As
(
SELECT
Rn = 1
,ParentId
,Name
,ChildId
FROM #t WHERE ParentId = #ParentId
UNION All
SELECT
Rn + 1
,pc.ParentId as Parents
,pc.Name
,pc.ChildId
FROM #t pc
JOIN CTE gp
on pc.ParentId = gp.Childid
WHERE gp.ParentId <> gp.Childid
)
SELECT TOP 1 *
FROM CTE
ORDER BY Rn DESC
OPTION (MAXRECURSION 0);