grouping and switching the columns and rows - sql

I don't know if this would officially be called a pivot, but the result that I would like is this:
+------+---------+------+
| Alex | Charley | Liza |
+------+---------+------+
| 213 | 345 | 1 |
| 23 | 111 | 5 |
| 42 | 52 | 2 |
| 323 | | 5 |
| 23 | | 1 |
| 324 | | 5 |
+------+---------+------+
my input data is in this form:
+-----+---------+
| Apt | Name |
+-----+---------+
| 213 | Alex |
| 23 | Alex |
| 42 | Alex |
| 323 | Alex |
| 23 | Alex |
| 324 | Alex |
| 345 | Charley |
| 111 | Charley |
| 52 | Charley |
| 1 | Liza |
| 5 | Liza |
| 2 | Liza |
| 5 | Liza |
| 1 | Liza |
| 5 | Liza |
+-----+---------+
because I have approximately 100 names, I don't want to have to do a ton of sub queries lik this
select null, null, thirdcolumn from...
select null, seconcolumn from...
select firstcolumn from...
Is there a way to do this with PIVOT or otherwise?

You can do this with dynamic PIVOT and the ROW_NUMBER() function:
DECLARE #cols AS VARCHAR(1000),
#query AS VARCHAR(8000)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Name)
FROM (SELECT DISTINCT Name
FROM #test
)sub
ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)')
,1,1,'')
PRINT #cols
SET #query = '
WITH cte AS (SELECT DISTINCT *
FROM #test)
,cte2 AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Apt)RowRank
FROM cte)
SELECT *
FROM cte2
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
SQL Fiddle - Distinct List, Specific Order
Edit: If you don't want the list to be distinct, eliminate the first cte above, and if you want to keep arbitrary ordering change the ORDER BY to (SELECT 1):
DECLARE #cols AS VARCHAR(1000),
#query AS VARCHAR(8000)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Name)
FROM (SELECT DISTINCT Name
FROM #test
)sub
ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)')
,1,1,'')
PRINT #cols
SET #query = '
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY (SELECT 1))RowRank
FROM #test)
SELECT *
FROM cte
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
SQL Fiddle - Full List, Arbitrary Order
And finally, if you didn't want the RowRank field in your results, just re-use the #cols variable in your SELECT:
SET #query = '
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY (SELECT 1))RowRank
FROM #test)
SELECT '+#cols+'
FROM cte
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)

Oh, this is something of a pain, but you can do it with SQL. You are trying to concatenate the columns.
select seqnum,
max(case when name = 'Alex' then apt end) as Alex,
max(case when name = 'Charley' then apt end) as Charley,
max(case when name = 'Liza' then apt end) as Liza
from (select t.*, row_number() over (partition by name order by (select NULL)) as seqnum
from t
) t
group by seqnum
order by seqnum;
As a note: there is no guarantee that the original ordering will be the same within each column. As you know, SQL tables are inherently unordered, so you would need a column to specify the ordering.
To handle multiple names, I'd just get the list using a query such as:
select distinct 'max(case when name = '''+name+''' then apt end) as '+name+','
from t;
And copy the results into the query.

Related

SQL Server: Order cols in a row

I want to swap the value of columns to have them sorted.
Table:
pid | category1 | category2 | category3
----+-----------+-----------+----------
1 | a | b |
2 | b | a |
3 | a | c | b
Result should be:
pid | category1 | category2 | category3
----+-----------+-----------+----------
1 | a | b |
2 | a | b |
3 | a | b | c
My approach was to select the columns to rows, order them in groups an return the new columns:
pid | category
----+---------
1 | a
1 | b
2 | b
2 | a
3 | a
3 | c
3 | b
I found the pivot function, but didn't really understand how to use it in this context.
Sean Lange is right about how you should correct your database schema.
Here is something to get you your results until then:
Using cross apply(values ...) to unpivot your data along with row_number() to reorder your category inside a common table expression; then repivoting that data with conditional aggregation:
;with cte as (
select
t.pid
, u.category
, rn = row_number() over (partition by t.pid order by u.category)
from t
cross apply (values (category1),(category2),(category3)) u(category)
where nullif(u.category,'') is not null
)
select
pid
, category1 = max(case when rn = 1 then category end)
, category2 = max(case when rn = 2 then category end)
, category3 = max(case when rn = 3 then category end)
from cte
group by pid
rextester demo: http://rextester.com/GIG22558
returns:
+-----+-----------+-----------+-----------+
| pid | category1 | category2 | category3 |
+-----+-----------+-----------+-----------+
| 1 | a | b | NULL |
| 2 | a | b | NULL |
| 3 | a | b | c |
+-----+-----------+-----------+-----------+
you can pivot as below
select * from (
select *, RowN = row_number() over(partition by pid order by Category) from Categories
) a
pivot (max(category) for RowN in ([1],[2],[3])) p
Output as below:
+-----+-----------+-----------+-----------+
| Pid | Category1 | Category2 | Category3 |
+-----+-----------+-----------+-----------+
| 1 | a | b | NULL |
| 2 | a | b | NULL |
| 3 | a | b | c |
+-----+-----------+-----------+-----------+
For dynamic list of columns you can use as below:
declare #cols1 varchar(max)
declare #cols2 varchar(max)
declare #query nvarchar(max)
--Row Numbers with tally
;with c1 as (
select * from ( values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) v(n) )
,c2 as (select n1.* from c1 n1, c1 n2, c1 n3, c1 n4)
,RowNumbers as (
select top (select max(cnt) from (select cnt = count(*) from Categories group by pid ) a) convert(varchar(6), row_number() over (order by (select null))) as RowN
from c2 n1, c2 n2
)
select #cols1 = stuff((select ','+QuoteName(RowN) from RowNumbers group by RowN for xml path('')),1,1,''),
#cols2 = stuff((select ',' + QuoteName(RowN) + ' as '+ QuoteName(concat('Category' , RowN)) from RowNumbers group by RowN for xml path('')),1,1,'')
select #cols1, #cols2
Set #query = ' Select Pid, '+ #cols2 +' from ( '
Set #query += ' select *, RowN = row_number() over(partition by pid order by Category) from Categories) a '
Set #query += ' pivot (max(category) for RowN in (' + #cols1 + ')) p'
--select #query
exec sp_executesql #query

Dynamic field content as Row Sql

I have the following dataset on a sql database
----------------------------------
| ID | NAME | AGE | STATUS |
-----------------------------------
| 1ASDF | Brenda | 21 | Single |
-----------------------------------
| 2FDSH | Ging | 24 | Married|
-----------------------------------
| 3SDFD | Judie | 18 | Widow |
-----------------------------------
| 4GWWX | Sophie | 21 | Married|
-----------------------------------
| 5JDSI | Mylene | 24 | Singe |
-----------------------------------
I want to query that dataset so that i can have this structure in my result
--------------------------------------
| AGE | SINGLE | MARRIED | WIDOW |
--------------------------------------
| 21 | 1 | 1 | 0 |
--------------------------------------
| 24 | 1 | 1 | 0 |
--------------------------------------
| 18 | 0 | 0 | 1 |
--------------------------------------
And the status column can be dynamic so there will be more columns to come.
Is this possible?
Since you are using SQL Server, you can use the PIVOT table operator like this:
SELECT *
FROM
(
SELECT Age, Name, Status FROM tablename
) AS t
PIVOT
(
COUNT(Name)
FOR Status IN(Single, Married, Widow)
) AS p;
SQL Fiddle Demo
To do it dynamically you have to use dynamic sql like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(status)
FROM tablename
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = '
SELECT *
FROM
(
SELECT Age, Name, Status FROM tablename
) AS t
PIVOT
(
COUNT(Name)
FOR Status IN( ' +#cols + ')
) AS p;';
execute(#query);
Updated SQL Fiddle Demo

SQL separate columns by rows value (pivot)

Table1
| MODULE | COUNT | YEAR |
-------------------------
| M1 | 12 | 2011 |
| M1 | 43 | 2012 |
| M2 | 5 | 2011 |
| M3 | 24 | 2011 |
| M4 | 22 | 2011 |
| M4 | 11 | 2012 |
| M5 | 10 | 2012 |
I want to display like this
| MODULE | 2011 | 2012 |
----------------------------
| M1 | 12 | 43 |
| M2 | 5 | - |
| M3 | 24 | - |
| M4 | 22 | 11 |
| M5 | - | 10 |
This can be done using PIVOT query. Or the following:
select Module,
SUM(CASE WHEN Year='2011' then Count ELSE 0 END) as [2011],
SUM(CASE WHEN Year='2012' then Count ELSE 0 END) as [2012]
FROM T
GROUP BY Module
SQL Fiddle demo
You can use PIVOT for that:
SELECT
Module,
[2011], [2012]
FROM
(
SELECT
*
FROM Table1
) AS SourceTable
PIVOT
(
SUM([Count])
FOR [Year] IN ([2011], [2012])
) AS PivotTable;
You can also use this dynamic query if you don't have limited Year
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT distinct ',' + QUOTENAME([Year])
from Table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT Module,' + #cols + '
FROM
(
Select *
FROM Table1
) dta
PIVOT
(
SUM([Count])
FOR [Year] IN (' + #cols + ')
) pvt '
EXECUTE(#query);
Result:
| MODULE | 2011 | 2012 |
----------------------------
| M1 | 12 | 43 |
| M2 | 5 | (null) |
| M3 | 24 | (null) |
| M4 | 22 | 11 |
| M5 | (null) | 10 |
See this SQLFiddle
Update
You can also use this alternative dynamic method: (Dynamic of the query given by #valex)
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT distinct ','
+ ' SUM(CASE WHEN YEAR= ''' + CAST(Year AS varchar(50))
+ ''' THEN [COUNT] ELSE ''-'' END) AS ' + QUOTENAME([Year])
from Table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT Module, ' + #cols + '
FROM Table1 GROUP BY Module'
EXECUTE(#query);
See this SQLFiddle
SELECT *
FROM Tablename
PIVOT (
AVG([Count] FOR [Year] IN (2011, 2012, 2013)) AS AvgCount
)
I think you need to look into the relational operator PIVOT.
Using PIVOT and UNPIVOT
SELECT Module,
[2011], [2012]
FROM T
PIVOT (SUM(Count) FOR Year IN ([2011], [2012])) AS PivotTable;

Query to Get All IDs When Duplicate Data Has Different Keys

I've got a SQL table similar to this:
+-----------------------------------------------+
| ID | FirstName | LastName | SomeOtherData|
+-----------------------------------------------+
| 200 | Robert | Barone | Foo |
| 228 | Doug | Heffernan | Bar |
| 2091 | Robert | Barone | Foo |
| 3921 | Doug | Heffernan | Bar |
| 291 | Greg | Warner | Barfoo |
+-----------------------------------------------+
Now what I'm having trouble producing is a table that'll list both IDs for a given Person, assuming that FirstName and LastName are used to indicate duplicates. So, basically I'm trying to get:
+---------------------------------------------------------+
| ID | OtherID | FirstName | LastName | SomeOtherData|
+---------------------------------------------------------+
| 200 | 2091 | Robert | Barone | Foo |
| 228 | 3921 | Doug | Heffernan | Bar |
| 291 | | Greg | Warner | Barfoo |
+---------------------------------------------------------+
Would anyone be able to help me out with something like this? Thanks!
You can use a PIVOT which will transform the data from rows into columns:
select [1] Id,
[2] OtherId,
firstname,
lastname
from
(
select id, firstname, lastname,
row_number() over(partition by firstname, lastname
order by id) rn
from yourtable
) src
pivot
(
max(id)
for rn in ([1], [2])
) piv
See SQL Fiddle with Demo
Or you could use an aggregate function with a CASE expression:
select
max(case when rn = 1 then id end) Id,
max(case when rn = 2 then id end) OtherId,
firstname,
lastname
from
(
select id, firstname, lastname,
row_number() over(partition by firstname, lastname
order by id) rn
from yourtable
) src
group by firstname, lastname
The above will work great if you have a known number of duplicate values (1, 2, etc). You could also implement dynamic SQL if you have more than 2 id's. The dynamic SQL would look like:
DECLARE #cols AS NVARCHAR(MAX),
#colNames AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(cast(row_number() over(partition by firstname, lastname order by id) as varchar(50)))
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colNames = STUFF((SELECT distinct ', ' + QUOTENAME(cast(row_number() over(partition by firstname, lastname order by id) as varchar(50))) +' as Id_' + cast(row_number() over(partition by firstname, lastname order by id) as varchar(50))
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #colNames + ', firstname, lastname from
(
select id, firstname, lastname,
row_number() over(partition by firstname, lastname
order by id) rn
from yourtable
) x
pivot
(
max(id)
for rn in (' + #cols + ')
) p
'
execute(#query)
See SQL Fiddle with Demo
The result of all 3 would be:
| ID | OTHERID | FIRSTNAME | LASTNAME |
-----------------------------------------
| 200 | 2091 | Robert | Barone |
| 228 | 3921 | Doug | Heffernan |
| 291 | (null) | Greg | Warner |

Write advanced SQL Select

Item table:
| Item | Qnty | ProdSched |
| a | 1 | 1 |
| b | 2 | 1 |
| c | 3 | 1 |
| a | 4 | 2 |
| b | 5 | 2 |
| c | 6 | 2 |
Is there a way I can output it like this using SQL SELECT?
| Item | ProdSched(1)(Qnty) | ProdSched(2)(Qnty) |
| a | 1 | 4 |
| b | 2 | 5 |
| c | 3 | 6 |
You can use PIVOT for this. If you have a known number of values to transform, then you can hard-code the values via a static pivot:
select item, [1] as ProdSched_1, [2] as ProdSched_2
from
(
select item, qty, prodsched
from yourtable
) x
pivot
(
max(qty)
for prodsched in ([1], [2])
) p
see SQL Fiddle with Demo
If the number of columns is unknown, then you can use a dynamic pivot:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(prodsched)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT item,' + #cols + ' from
(
select item, qty, prodsched
from yourtable
) x
pivot
(
max(qty)
for prodsched in (' + #cols + ')
) p '
execute(#query)
see SQL Fiddle with Demo
SELECT Item,
[ProdSched(1)(Qnty)] = MAX(CASE WHEN ProdSched = 1 THEN Qnty END),
[ProdSched(2)(Qnty)] = MAX(CASE WHEN ProdSched = 2 THEN Qnty END)
FROM dbo.tablename
GROUP BY Item
ORDER BY Item;
Let's hit this in two phases. First, although this is not the exact format you wanted, you can get the data you asked for as follows:
Select item, ProdSched, max(qty)
from Item1
group by item,ProdSched
Now, to get the data in the format you desired, one way of accomplishing it is a PIVOT table. You can cook up a pivot table in SQL Server as follows:
Select item, [1] as ProdSched1, [2] as ProdSched2
from ( Select Item, Qty, ProdSched
from item1 ) x
Pivot ( Max(qty) for ProdSched in ([1],[2])) y