PIVOT on hierarchical data - sql

I have a table like this:
Id Name parentId
--------------------------------
5 Rollers 2
2 Paint and Brushes 1
1 Decorating NULL
Using PIVOT or any other single query, can I get an output like this:
cat1id cat1name cat2id cat2name cat3id cat3Name
------------------------------------------------------------------------
1 Decorating 2 Paint and Brushes 5 Rollers

You can use PIVOT, UNPIVOT and a recursive query to perform this.
Static Version, is where you hard-code the values to the transformed:
;with hd (id, name, parentid, category)
as
(
select id, name, parentid, 1 as category
from yourtable
where parentid is null
union all
select t1.id, t1.name, t1.parentid, hd.category +1
from yourtable t1
inner join hd
on t1.parentid = hd.id
),
unpiv as
(
select value, 'cat_'+cast(category as varchar(5))+'_'+ col col_name
from
(
select cast(id as varchar(17)) id, name, parentid, category
from hd
) src
unpivot
(
value for col in (id, name)
) un
)
select [cat_1_id], [cat_1_name],
[cat_2_id], [cat_2_name],
[cat_3_id], [cat_3_name]
from unpiv
pivot
(
max(value)
for col_name in ([cat_1_id], [cat_1_name],
[cat_2_id], [cat_2_name],
[cat_3_id], [cat_3_name])
) piv
See SQL Fiddle with Demo
Dynamic Version, the values are generated at run-time:
;with hd (id, name, parentid, category)
as
(
select id, name, parentid, 1 as category
from yourtable
where parentid is null
union all
select t1.id, t1.name, t1.parentid, hd.category +1
from yourtable t1
inner join hd
on t1.parentid = hd.id
)
select category categoryNumber
into #temp
from hd
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + quotename('cat_'+cast(CATEGORYNUMBER as varchar(10))+'_'+col)
from #temp
cross apply (select 'id' col
union all
select 'name' col) src
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = ';with hd (id, name, parentid, category)
as
(
select id, name, parentid, 1 as category
from yourtable
where parentid is null
union all
select t1.id, t1.name, t1.parentid, hd.category +1
from yourtable t1
inner join hd
on t1.parentid = hd.id
),
unpiv as
(
select value, ''cat_''+cast(category as varchar(5))+''_''+ col col_name
from
(
select cast(id as varchar(17)) id, name, parentid, category
from hd
) src
unpivot
(
value for col in (id, name)
) un
)
select '+#cols+'
from unpiv
pivot
(
max(value)
for col_name in ('+#cols+')
) piv'
execute(#query)
drop table #temp
See SQL Fiddle with Demo
The Results are the same for both:
| CAT_1_ID | CAT_1_NAME | CAT_2_ID | CAT_2_NAME | CAT_3_ID | CAT_3_NAME |
--------------------------------------------------------------------------------
| 1 | Decorating | 2 | Paint and Brushes | 5 | Rollers |

Related

Pivot query is returning all the values in T-SQL

I am having a requirement that I want to build a pivot query for my table which is shown in screen shot.
This is my table
I want to make the rows values into columns.
My try:
SELECT rno, NO,Description,CarNo,ID from
(
select row_number()over(partition by columnname order by rno)rno, columnname,value
from mytable
group by rno, columnname,value
) x
pivot
(
max(value)
for columnname in (NO,Description,CarNo,ID)
) p
Actual Output:
Expected Output:
NO | Description | CradNo | ID
---------------------------------------------
Part1 | desc1 | Card1 | 1
Part2 | desc2 | Card1 | 1
You can use this.
DECLARE #mytable TABLE(columnname VARCHAR(20), value VARCHAR(10), rno INT)
INSERT INTO #mytable
VALUES
('ID', '1', 1),
('NO', 'Part1', 1),
('NO', 'Part2', 1),
('Description', 'desc1', 1),
('Description', 'desc2', 1),
('CarNo', 'car1', 1)
;WITH CTE AS
(SELECT * FROM
(SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY columnname Order BY rno, value)
FROM #mytable) SRC
PIVOT ( MAX(value) FOR columnName IN ([ID], [NO], [Description], [CarNo]) ) PVT
)
SELECT
ISNULL( T1.NO, T2.NO) NO
, ISNULL( T1.Description, T2.Description) Description
, ISNULL( T1.CarNo, T2.CarNo) CarNo
, ISNULL( T1.ID, T2.ID) ID
FROM CTE T1 OUTER APPLY ( SELECT TOP 1 * FROM CTE WHERE CTE.RN < T1.RN ) T2
Result:
NO Description CarNo ID
---------- ----------- ---------- ----------
Part1 desc1 car1 1
Part2 desc2 car1 1

How can I concatenate values from 3 column per id?

Consider the following table #temp:
objectid field1 field2 field3
--------------------------------
1 X 001 foo
2 Y 022 bar
2 Z 033 baz
3 A 111 abc
3 B 222 def
3 C 333 ghi
4 Q 900 tom
I need the concatenate field1, field2 and field3 for each objectid:
objectid field1 field2 field3
------------------------------------------
1 X 001 foo
2 Y;Z 022;033 barbaz
3 A;B;C 111;222;333 abc;def;ghi
4 Q 900 tom
I trief this first for only field1 as follows:
select
c.FLUSObjectNumber,
[field1] = stuff(
(
select ';' + field1
from #temp as c2
where c2.objectid = c.objectid and c2.field1 = c.field1
for xml path(''), type
).value('.', 'varchar(max)'), 1, 1, ''
)
from #temp c
group by c.objectid, c.field1
;
However, this returns the list I already have in #temp.
What am I doing wrong? How can I generate the desired output?
declare #t table (objectid INT, field1 VARCHAR(50), field2 VARCHAR(50), field3 VARCHAR(50))
INSERT INTO #t (objectid,field1,field2,field3)values (1,'X','001','foo')
INSERT INTO #t (objectid,field1,field2,field3)values (2,'Y','022','bar')
INSERT INTO #t (objectid,field1,field2,field3)values (2,'Z','033','baz')
INSERT INTO #t (objectid,field1,field2,field3)values (3,'A','111','abc')
INSERT INTO #t (objectid,field1,field2,field3)values (3,'B','222','def')
INSERT INTO #t (objectid,field1,field2,field3)values (3,'C','333','ghi')
INSERT INTO #t (objectid,field1,field2,field3)values (4,'Q','900','tom')
Select distinct t.objectid ,
STUFF((Select distinct ',' + t1.field1
from #t t1
where t.objectid = t1.objectid
ORDER BY 1
FOR XML PATH(''))
, 1, 1, '')As field,
STUFF((Select distinct ',' + t2.field2
from #t t2
where t.objectid = T2.objectid
ORDER BY 1
FOR XML PATH(''),TYPE).value('.', 'NVARCHAR(MAX)')
, 1, 1, ' ')As field2,
STUFF((Select distinct ',' + T3.field3
from #t t3
where t.objectid = t3.objectid
ORDER BY 1
FOR XML PATH(''),TYPE).value('.', 'NVARCHAR(MAX)')
, 1, 1, ' ')As field3
from #t t
group by
t.objectid
DECLARE #Temp TABLE
(
objectid INT,
field1 VARCHAR(20),
field2 VARCHAR(20),
field3 VARCHAR(20)
)
INSERT INTO #Temp
( objectid, field1, field2, field3 )
VALUES
(1 ,'X' ,'001' ,'foo'),
(2 ,'Y' ,'022' ,'bar'),
(2 ,'Z' ,'033' ,'baz'),
(3 ,'A' ,'111' ,'abc'),
(3 ,'B' ,'222' ,'def'),
(3 ,'C' ,'333' ,'ghi'),
(4 ,'Q' ,'900' ,'tom');
SELECT DISTINCT t.objectid, f1.f1, f2.f2, f3.f3
FROM #Temp t
OUTER APPLY
(
SELECT STUFF((SELECT ',' + Field1 FROM #Temp WHERE objectid = t.objectid ORDER BY field1 FOR XML PATH('')),1,1,'') f1
) f1
OUTER APPLY
(
SELECT STUFF((SELECT ',' + Field2 FROM #Temp WHERE objectid = t.objectid ORDER BY field2 FOR XML PATH('')),1,1,'') f2
) f2
OUTER APPLY
(
SELECT STUFF((SELECT ',' + Field3 FROM #Temp WHERE objectid = t.objectid ORDER BY field1 FOR XML PATH('')),1,1,'') f3
) f3
OUTPUT:
objectid f1 f2 f3
1 X 001 foo
2 Y,Z 022,033 bar,baz
3 A,B,C 111,222,333 abc,def,ghi
4 Q 900 tom
You don't need outer apply or anything, try this:
SELECT DISTINCT t.objectid,
(SELECT STUFF((SELECT ';' + Field1 FROM #temp WHERE objectid = t.objectid ORDER BY field1 FOR XML PATH('')),1,1,'')) AS [field1],
(SELECT STUFF((SELECT ';' + Field2 FROM #temp WHERE objectid = t.objectid ORDER BY field2 FOR XML PATH('')),1,1,'')) AS [field2],
(SELECT STUFF((SELECT ';' + Field3 FROM #temp WHERE objectid = t.objectid ORDER BY field1 FOR XML PATH('')),1,1,'')) AS [field3]
FROM #temp t
you missed taking field1 within the STUFF clause from the correct table.
select
c.FLUSObjectNumber,
[field1] = stuff(
(
select ';' + c2.field1
from #temp as c2
where c2.objectid = c.objectid and c2.field1 = c.field1
for xml path(''), type
).value('.', 'varchar(max)'), 1, 1, ''
)
from #temp c
group by c.objectid, c.field1
;

Simple SQL Query -

I have data in a SQLServer table like this:
ID Name Year Value
-- ---- ---- -----
2 Ted 2013 2000
2 Ted 2012 1000
I need the view syntax to output this:
ID Name Yr1 Value1 Yr2 Value2
-- ---- --- ------ --- ------
2 Ted 2013 2000 2012 1000
No cursors if possible.
Any clues would be greatful.
In SQL Server there are several ways that you can get the result.
If you have a limited number of values, then you can easily hard-code the result. One way you can get the result would be using an aggregate function with a CASE expression:
select d.id,
d.name,
max(case when seq = 1 then year end) year1,
max(case when seq = 1 then value end) value1,
max(case when seq = 2 then year end) year2,
max(case when seq = 2 then value end) value2
from
(
select id, name, year, value,
row_number() over(partition by id order by year desc) seq
from yourtable
) d
group by d.id, d.name;
See SQL Fiddle with Demo. If you want to use the PIVOT function, then I would suggest first unpivoting the data in the year and value columns first. The process of unpivot converts the multiple columns into multiple rows. You can use the UNPIVOT function, but in my example I used CROSS APPLY with a UNION ALL query and the code is:
select t.id, t.name,
col = c.col+cast(seq as varchar(4)),
c.val
from
(
select id, name, year, value,
row_number() over(partition by id order by year desc) seq
from yourtable
) t
cross apply
(
select 'year', t.year union all
select 'value', t.value
) c (col, val)
See SQL Fiddle with Demo. This converts your multiple columns into a slightly different format with multiple rows:
| ID | NAME | COL | VAL |
| 2 | Ted | year1 | 2013 |
| 2 | Ted | value1 | 2000 |
| 2 | Ted | year2 | 2012 |
| 2 | Ted | value2 | 1000 |
You can then apply the PIVOT function on this to get your final desired result:
select id, name, year1, value1, year2, value2
from
(
select t.id, t.name,
col = c.col+cast(seq as varchar(4)),
c.val
from
(
select id, name, year, value,
row_number() over(partition by id order by year desc) seq
from yourtable
) t
cross apply
(
select 'year', t.year union all
select 'value', t.value
) c (col, val)
) d
pivot
(
max(val)
for col in (year1, value1, year2, value2)
) piv;
See SQL Fiddle with Demo. Finally if you have an unknown number of values that you want to transform from rows into columns, then you can use dynamic SQL inside a stored procedure:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+cast(seq as varchar(4)))
from
(
select row_number() over(partition by id order by year desc) seq
from yourtable
) d
cross apply
(
select 'year', 1 union all
select 'value', 2
) c (col, so)
group by seq, col, so
order by seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, name,' + #cols + '
from
(
select t.id, t.name,
col = c.col+cast(seq as varchar(4)),
c.val
from
(
select id, name, year, value,
row_number() over(partition by id order by year desc) seq
from yourtable
) t
cross apply
(
select ''year'', t.year union all
select ''value'', t.value
) c (col, val)
) x
pivot
(
max(val)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. All versions will give a result:
| ID | NAME | YEAR1 | VALUE1 | YEAR2 | VALUE2 |
| 2 | Ted | 2013 | 2000 | 2012 | 1000 |

select columns from second row add to the end of the first row in sql group by ID

I have a table customer in the database as below.
ID UID Address1 Name code
10 5 A Jac 683501
11 5 B Joe 727272
13 6 C mat 373737
first two records (10,11) have a common uID -5 . These two records can be considered as a single unit.
fourth record is having a separate UID , so it is a separate unit
I need to produce an output in a csv file such a way that
ID UID Name code Address1 Name2 Code2
10 5 jac 683501 A Joe 727272
13 6 mat 373737 C
Name2 and code2 values are from the second row, since UID is same for first two records, we can considered it as a single unit.
Can anyone give hints to query for generating these records.
This process to transform data from rows into columns is known as a PIVOT. There are several ways that this can be done.
You can use a row_number() along with an aggregate function with a CASE expression:
select min(id),
uid,
max(case when seq = 1 then name end) Name,
max(case when seq = 1 then code end) Code,
max(case when seq = 1 then Address1 end) Address1,
max(case when seq = 2 then name end) Name2,
max(case when seq = 2 then code end) code2,
max(case when seq = 2 then Address1 end) Address1_2
from
(
select id, uid, address1, name, code,
row_number() over(partition by uid order by id) seq
from yourtable
) d
group by uid;
See SQL Fiddle with Demo.
You could use both the UNPIVOT and the PIVOT function:
select id, uid,
name1, code1, address1, name2, code2, address2
from
(
select id, uid, col+cast(seq as varchar(10)) col, value
from
(
select
(select min(id)
from yourtable t2
where t.uid = t2.uid) id,
uid,
cast(address1 as varchar(20)) address,
cast(name as varchar(20)) name,
cast(code as varchar(20)) code,
row_number() over(partition by uid order by id) seq
from yourtable t
) d
unpivot
(
value
for col in (address, name, code)
) unpiv
) src
pivot
(
max(value)
for col in (name1, code1, address1, name2, code2, address2)
) piv;
See SQL Fiddle with Demo.
Finally if you have an unknown number of values for each uid, then you can use dynamic SQL to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+(cast(seq as varchar(10))))
from
(
select row_number() over(partition by uid order by id) seq
from yourtable
) d
cross apply
(
select 'name', 1 union all
select 'code', 2 union all
select 'address', 3
) c (col, so)
group by seq, col, so
order by seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, uid,' + #cols + '
from
(
select id, uid, col+cast(seq as varchar(10)) col, value
from
(
select
(select min(id)
from yourtable t2
where t.uid = t2.uid) id,
uid,
cast(address1 as varchar(20)) address,
cast(name as varchar(20)) name,
cast(code as varchar(20)) code,
row_number() over(partition by uid order by id) seq
from yourtable t
) d
unpivot
(
value
for col in (address, name, code)
) unpiv
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. All versions will give a result:
| ID | UID | NAME1 | CODE1 | ADDRESS1 | NAME2 | CODE2 | ADDRESS2 |
---------------------------------------------------------------------
| 10 | 5 | Jac | 683501 | A | Joe | 727272 | B |
| 13 | 6 | mat | 373737 | C | (null) | (null) | (null) |
Try inner join.
select u2.firstname,u2.col2,
from users u
inner join users u2 on u.userid=u2.userid
where u.firstname=u2.lastname

SQL count ids in fields

I have a table contains IDs in field. It looks like:
FieldName
-------------------------
1,8,2,3,4,10,5,9,6,7
-------------------------
1,8
-------------------------
1,8
I need to count these IDs to get result:
ID | Count
---|------
1 | 3
8 | 3
2 | 1
3 | 1
Any ideas?
Thanks!
Try this :
Declare #demo table(FieldName varchar(100))
insert into #demo values('1,8,2,3,4,10,5,9,6,7')
insert into #demo values('1,8')
insert into #demo values('1,8')
select ID, COUNT(id) ID_count from
(SELECT
CAST(Split.a.value('.', 'VARCHAR(100)') AS INT) AS ID
FROM
(
SELECT CAST ('<M>' + REPLACE(FieldName, ',', '</M><M>') + '</M>' AS XML) AS ID
FROM #demo
) AS A CROSS APPLY ID.nodes ('/M') AS Split(a)) q1
group by ID
I like Devart's answer because of the faster execution. Here is a modified earlier answer to suite your need :
Declare #col varchar(200)
SELECT
#col=(
SELECT FieldName + ','
FROM #demo c
FOR XML PATH('')
);
;with demo as(
select cast(substring(#col,1,charindex(',',#col,1)-1) AS INT) cou,charindex(',',#col,1) pos
union all
select cast(substring(#col,pos+1,charindex(',',#col,pos+1)-pos-1)AS INT) cou,charindex(',',#col,pos+1) pos
from demo where pos<LEN(#col))
select cou ID, COUNT(cou) id_count from demo
group by cou
Try this one -
Query:
SET NOCOUNT ON;
DECLARE #temp TABLE (txt VARCHAR(8000))
INSERT INTO #temp (txt)
VALUES ('1,8,2,3,4,10,5,9,6,7'), ('1,8'), ('1,8')
SELECT
t.ID
, [Count] = COUNT(1)
FROM (
SELECT
ID =
SUBSTRING(
t.string
, number + 1
, ABS(CHARINDEX(',', t.string, number + 1) - number - 1)
)
FROM (
SELECT string = (
SELECT ',' + txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)')
) t
CROSS JOIN [master].dbo.spt_values n
WHERE [type] = 'p'
AND number <= LEN(t.string) - 1
AND SUBSTRING(t.string, number, 1) = ','
) t
GROUP BY t.ID
ORDER BY [Count] DESC
Output:
ID Count
----- -----------
1 3
8 3
9 1
10 1
2 1
3 1
4 1
5 1
6 1
7 1
Query cost: