MSSQL transpose multiple rows into one row(s) with columns - sql

I need to transform, transpose, whatever the right terminology is a data set with multiple rows and instead return a single row with multiple columns. Here is a sample of the data that I start with
EID PM Project HOURS WeekStarting
joe#test.com tom#test.com Proj A 6 11/28/2016
joe#test.com tom#test.com Proj A 3 12/5/2016
joe#test.com tom#test.com Proj A 7 12/12/2016
joe#test.com tom#test.com Proj A 3 12/19/2016
sue#test.com sam#test.com Proj B 3 11/28/2016
sue#test.com sam#test.com Proj B 6 12/12/2016
sue#test.com sam#test.com Proj B 7 12/19/2016
I would like to format the data like so
EID PM Project Week1 Week2 Week3 Week4
joe#test.com tom#test.com Proj A 6 3 7 3
sue#test.com sam#test.com Proj B 3 0 6 7
Note that for sue there is no data in week2 in my source data so the result is 0 for week 2.
Currently I am doing this in Javascript using the reduce function but it's very messy. I would rather try to fix the data and have clean Javascript.
Any help is much appreciated.

Declare #SQL varchar(max)
Select #SQL = Stuff((Select ','+QuoteName('Week'+cast(WeekNr as varchar(25)))
From (Select Distinct Top 100 Percent WeekNr=Dense_Rank() over (Order By WeekStarting) From YourTable
Order By 1) A
For XML Path ('')),1,1,'')
Select #SQL = 'Select EID,PM,Project,' + #SQL + '
From (
Select EID
,PM
,Project
,Item = ''Week''+cast(Dense_Rank() over (Order By WeekStarting) as varchar(25))
,Val = Hours
From YourTable
) A
Pivot (max(Val) For Item in (' + #SQL + ') ) p'
Exec(#SQL);
Returns

You can query like this:
;with cte as (
select *, DATEPART(WEEK, weekstarting) -
DATEPART(WEEK, DATEADD(MM, DATEDIFF(MM,0,weekstarting), 0)) + 1 AS WEEK_OF_MONTH
from yourtranspose
)
select Eid, PM, Project, [1], [2], [3], [4], [5] from
( select EID, PM, Project, WEEK_OF_MONTH , hours from cte ) a
pivot (max(hours) for week_of_month in ([1], [2], [3], [4], [5])) p

for each extra row you need to join the table against itself

Related

Count a column according to another column and display the count in different table

This is table A
Wallet Type State
------------------------
106 1
106 2
106 1
106 1
106 2
112 1
112 2
112 2
Now i need a table where it counts wallet type according to the state
Table B would be like this
State Distributor(106) Agent(112)
----------------------------------------
1 3 1
2 2 2
Try this:
You can get this using Aggregate functions.
SUM:
SELECT State
,SUM(CASE WHEN Wallet_Type = 106 THEN 1 ELSE 0 END) Distributor_106
,SUM(CASE WHEN Wallet_Type = 112 THEN 1 ELSE 0 END) Distributor_112
FROM Your_Table
WHERE Wallet_Type IN (106, 112)
GROUP BY State
COUNT:
SELECT State
,COUNT(CASE WHEN Wallet_Type = 106 THEN 1 END) Distributor_106
,COUNT(CASE WHEN Wallet_Type = 112 THEN 1 END) Distributor_112
FROM Your_Table
WHERE Wallet_Type IN (106, 112)
GROUP BY State
These type of problems can also be solved using PIVOT keyword in SQL server.
To know more about PIVOT read here.
see working demo
Your query using PIVOT should be like
select
[state],
[Distributor(106)]=[106],
[Agent(112)]=[112]
from
(
select
[state],
[cstate]=state, -- created duplicate as this will be consumed in count operation and will not be available as output column later
[Wallet Type]
from tableA
) src
pivot
(
count(cstate) for [Wallet Type] in ([106],[112])
)p
You can use pivot table for this case
declare #t1 table (wallet_type int, state int)
insert into #t1
values (106, 1),
(106 , 2 ),
(106 , 1 ),
(106 , 1 ),
(106 , 2 ),
(112 , 1 ),
(112 , 2 ),
(112 , 2 )
--select * from #t1
select *
from
(
select state, wallet_type, count(wallet_type) 'count' from #t1
group by state, wallet_type
) src
pivot
(
sum(count)
for wallet_type in ([106], [112])
) piv;
Online Demo: http://rextester.com/QBTOQ8569
Try this one... (Dynamic PIVOT)
DECLARE #cols AS NVARCHAR(max) = Stuff((SELECT DISTINCT ', ' + Quotename(WalletType)
FROM TableName
FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
DECLARE #query AS NVARCHAR(max) = ' SELECT *
FROM TableName
PIVOT ( Count(wallettype)
FOR wallettype IN ('+#cols+') ) pvt';
EXECUTE(#query)
Output
+-------+-----+-----+
| State | 106 | 112 |
+-------+-----+-----+
| 1 | 3 | 1 |
| 2 | 2 | 2 |
+-------+-----+-----+
First of All, you need to declare a cols Variable that will give you all the distinct Wallet Types that will be used later in the query:
DECLARE #cols AS NVARCHAR(max) =
Stuff((SELECT DISTINCT ', ' + Convert(nvarchar, WalletType)
FROM [IHC].[dbo].[TABLENAMEHERE]
FOR XML PATH ('')), 1, 2, '')
what it does is, it gets all the distinct comma separated WalletTypes. as XML Path gives you data in XML so we can play with it.
then use PIVOT operator as suggested by some people. here:
select *
from
(
select [state], [WalletType], count([WalletType]) as count FROM [IHC].[dbo].[TABLENAMEHERE]
group by [state], [WalletType]
) SourceTable
pivot
(
sum(count)
for [WalletType] in (#cols)
) piv;
the first part of this query gives you state, WalletType, 'Count of Wallet' Types grouped by state and Wallet Type or say broken w.r.t State and Wallet Type
like this:
state WalletType count
1 106 4
2 106 2
1 112 1
2 112 2
now this 'Count of Wallet' as count is used in PIVOT function to get the SUM of COUNT w.r.t the Wallet Types so it sums the count w.r.t WalletType and gives the final result
make sure you use it in a dynamic query.

Cumulative list operations on Rows in SQL

I have a scenario where I need to compare the list of a values for a particular day with the previous day for same person. And for every day I need to maintain a difference of those two consecutive days.
This is data of sales people, every day they visit few houses from the whole list of houses in their town.
Below is the data I have:
Houses:
Select Distinct House_id from tbl_houses;
Visits:
Select sales_person_id, Date, visited_house_id;
Now, I have two different tables like this:
House_id
1
2
3
4
5
And, the sales persons visit's data is as follows:
sales_person_id Date visited_house
1 12/6/16 1
1 12/6/16 2
2 12/6/16 1
2 13/6/16 3
3 12/6/16 3
1 13/6/16 1
1 13/6/16 3
A sales person can visit a already visited house and more than one sales person can visit the same house on same day. No restrictions on this.
And, the desired output as follows:
Sales_person_Id Date Visited_Houses Not_Visited_Houses
1 12/6/16 [1,2] [3,4,5]
1 13/6/16 [1,2,3] [4,5]
2 12/6/16 [1] [2,3,4,5]
2 13/6/16 [1,3] [2,4,5]
3 12/6/16 [3] [1,2,4,5]
How can we achieve this in sql?
For SQL Server (you can alter the CTE at the top to use whatever date range you like):
WITH cteDates
AS
(
SELECT CAST(GETDATE() AS date) DT
UNION
SELECT CAST(GETDATE() - 1 AS date)
)
SELECT
SP.sales_person_id
, D.[DT] [Date]
, '[' + LEFT(V.Visited, LEN(V.Visited) - 1) + ']' Visited
, '[' + LEFT(NV.NotVisited, LEN(NV.NotVisited) - 1) + ']' Visited
FROM
cteDates D
CROSS JOIN (SELECT DISTINCT sales_person_id FROM tbl_visits) SP
OUTER APPLY
(
SELECT
CAST(
(
SELECT CAST(visited_house_id AS varchar) + ','
FROM tbl_visits
WHERE
sales_person_id = SP.sales_person_id
AND [Date] = D.DT
ORDER BY visited_house_id
FOR XML PATH ('')
)
AS varchar) Visited
) V
OUTER APPLY
(
SELECT
CAST(
(
SELECT CAST(House_id AS varchar) + ','
FROM tbl_houses
WHERE House_id NOT IN
(
SELECT visited_house_id
FROM tbl_visits
WHERE
sales_person_id = SP.sales_person_id
AND [Date] = D.DT
)
ORDER BY House_id
FOR XML PATH ('')
)
AS varchar) NotVisited
) NV
ORDER BY
sales_person_id
, [Date]
Please check below small piece of code which could help you :
SELECT DISTINCT
H.sales_person_id,
H.Date,
Visited_Houses = STUFF(
(
SELECT ','+CONVERT(NVARCHAR(MAX), houseid)
FROM history
WHERE [sales_person_id] = H.sales_person_id
AND [Date] = H.Date FOR XML PATH('')
), 1, 1, '[')+']',
Not_Visited_Houses = STUFF(
(
SELECT ','+CONVERT(NVARCHAR(MAX), TT.House_id)
FROM
(
SELECT HS1.House_id,
H1.sales_person_id
FROM house HS1
LEFT JOIN #history H1 ON H1.houseid = HS1.House_id
AND H1.sales_person_id = H.sales_person_id AND H1.Date = H.Date
) TT
WHERE TT.sales_person_id IS NULL FOR XML PATH('')
), 1, 1, '[')+']'
FROM house HS
INNER JOIN #history H ON H.houseid = HS.House_id;
Desired Output :
sales_person_id Date Visited_Houses Not_Visited_Houses
--------------- ---------- ---------------- -------------------
1 2016-06-12 [1,2] [3,4,5]
1 2016-06-13 [1,3] [2,4,5]
2 2016-06-12 [1] [2,3,4,5]
2 2016-06-13 [3] [1,2,4,5]
3 2016-06-12 [3] [1,2,4,5]
Note : The above output is as per your Data that you have given.
Hope, it will help you.

SQL Server - Transpose Date from One Table To Another

Can't figure out how to transpose data from one table to another. Do I use a cursor?
Sample Data:
Build Part SN DateShipped
A 1 123 2017-01-01
A 2 234 2017-02-02
A 3 345 2017-03-03
B 1 987 2017-01-01
B 2 876 2017-02-02
B 3 765 2017-03-03
Desired Result:
Build Part1SN Part1Ship Part2SN Part2Ship Part3SN Part3Ship
A 123 2017-01-01 234 2017-02-02 345 2017-03-03
B 987 2017-01-01 876 2017-02-02 765 2017-03-03
Since you are mixing data types (date & int) in the Pivot, I'll give a working example of a dynamic Pivot. Date note of what we are doing within the Cross Apply.
I'm also assuming Part is sequential within build, otherwise we would need to apply/nest a Row_Number()
Example
Declare #SQL varchar(max) = '
Select *
From (
Select A.Build
,B.*
From YourTable A
Cross Apply ( values (concat(''Part'',A.Part,''SN''), concat('''',A.SN))
,(concat(''Ship'',A.Part,''Ship''),concat('''',A.DateShipped))
) B (Item,Value)
) A
Pivot (max([Value]) For [Item] in (' + Stuff((Select ','+QuoteName(concat('Part',Part,'SN'))
+','+QuoteName(concat('Ship',Part,'Ship'))
From (Select Distinct Part From YourTable ) A
Order By 1
For XML Path('')),1,1,'') + ') ) p'
Exec(#SQL)
--Print #SQL
Returns
The Generated SQL Looks Like This
Select *
From (
Select A.Build
,B.*
From YourTable A
Cross Apply ( values (concat('Part',A.Part,'SN'), concat('',A.SN))
,(concat('Ship',A.Part,'Ship'),concat('',A.DateShipped))
) B (Item,Value)
) A
Pivot (max([Value]) For [Item] in ([Part1SN],[Ship1Ship],[Part2SN],[Ship2Ship],[Part3SN],[Ship3Ship]) ) p
select Build,
max(case Part when 1 then SN end) 'Part1SN', max(case Part when 1 then DateShipped end) 'Part1Ship',
max(case Part when 2 then SN end) 'Part2SN', max(case Part when 2 then DateShipped end) 'Part2Ship',
max(case Part when 3 then SN end) 'Part3SN', max(case Part when 3 then DateShipped end) 'Part3Ship'
from TempTable
group by Build
You should try using pivot Table.
For reference follow the below link
https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/

How to use SQL Server 2005 Pivot based on lookup table

table [Status] has the following data:
ID Status
1 PaymentPending
2 Pending
3 Paid
4 Cancelled
5 Error
====================================
Data Table has the following structure:
ID WeekNumber StatusID
1 1 1
2 1 2
3 1 3
4 2 1
5 2 2
6 2 2
7 2 3
Looking for a Pivot
Week # PaymentPending Pending Paid Cancelled
Week 1 1 1 1 0
Week 2 1 2 1 0
SELECT 'Week '+CAST(coun.WeekNumber AS VARCHAR(10)) [Week #],[PaymentPending],[Pending],[Paid],[Cancelled],[Error] FROM
(SELECT [WeekNumber],[Status] FROM dbo.WeekDetails
INNER JOIN [dbo].[Status] AS s
ON [dbo].[WeekDetails].[StatusID] = [s].[ID]) AS wee
PIVOT (COUNT(wee.[Status]) FOR wee.[Status]
IN ([PaymentPending],[Pending],[Paid],[Cancelled],[Error])) AS Coun
A pivot might look like this:
SELECT * FROM
(SELECT
'Week ' + CAST(D.WeekNumber AS varchar(2)) [Week #],
S.Status
FROM DataTbl D
INNER JOIN Status S ON D.StatusID = S.ID
) Derived
PIVOT
(
COUNT(Status) FOR Status IN
([PaymentPending], [Pending], [Paid], [Cancelled]) -- add [Error] if needed
) Pvt
If you expect the number of items in theStatustable to change you might want to consider using a dynamic pivot to generate the column headings. Something like this:
DECLARE #sql AS NVARCHAR(MAX)
DECLARE #cols AS NVARCHAR(MAX)
SELECT #cols = ISNULL(#cols + ',','') + QUOTENAME(Status)
FROM (SELECT ID, Status FROM Status) AS Statuses ORDER BY ID
SET #sql =
N'SELECT * FROM
(SELECT ''Week '' + CAST(D.WeekNumber AS varchar(2)) [Week #], S.Status
FROM Datatbl D
INNER JOIN Status S ON D.StatusID = S.ID) Q
PIVOT (
COUNT(Status)
FOR Status IN (' + #cols + ')
) AS Pvt'
EXEC sp_executesql #sql;
Sample SQL Fiddle
You can use CASE based aggregation with GROUP BY
SELECT 'Week ' + cast(WeekNumber as varchar(10)) as 'Week#',
SUM ( CASE WHEN StatusId =1 THEN 1 else 0 end) as 'PaymentPending',
SUM ( CASE WHEN StatusId =2 THEN 1 else 0 end) as 'Pending',
SUM ( CASE WHEN StatusId =3 THEN 1 else 0 end) as 'Paid',
SUM ( CASE WHEN StatusId =4 THEN 1 else 0 end) as 'Cancelled'
FROM DataTbl D
GROUP BY 'Week ' + cast(WeekNumber as varchar(10))

extend current query, calculated columns

My table looks for example like this:
Name date result
A 2012-01-01 1
A 2012-02-01 2
B 2013-01-01 1
...
For a full example: http://sqlfiddle.com/#!3/0226b/1
At the moment I have a working query that counts the rows by person and year: http://sqlfiddle.com/#!3/0226b/3
This is perfect, but what I want is some extra information for 2014. i need to count how many rows I have for every result.
something like this:
NAME 1 2 3 2014 2013 2012 TOTAL
Person B 4 0 2 6 2 2 10
Person A 2 1 1 4 3 4 11
Person C 1 1 1 3 1 0 4
Even better would be that I give the result-columns a good name (1 = lost, 2= draw, 3=won):
NAME lost draw won 2014 2013 2012 TOTAL
Person B 4 0 2 6 2 2 10
Person A 2 1 1 4 3 4 11
Person C 1 1 1 3 1 0 4
I tried to add some extra code, like:
select #colsResult
= STUFF((SELECT ',' + QUOTENAME(result)
from list
group by result
order by result
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
I have as result:
,[1]
,[2]
,[3]
But if I run the whole code I get an error, invallid column name...
Since you have two columns that you now want to PIVOT, you'll first have to unpivot those columns and then convert those values into the new columns.
Starting in SQL Server 2005, you could use CROSS APPLY to unpivot the columns. The basic syntax will be similar to:
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select 'dt', dt union all
select 'result', result
) c (old_col_name, new_col)
See SQL Fiddle with Demo. This query gets you a list of names, with the "new columns" and then the Total entries for each name.
| NAME | NEW_COL | TOTAL |
|----------|---------|-------|
| Person A | 2012 | 11 |
| Person A | 1 | 11 |
| Person A | 2012 | 11 |
| Person A | 2 | 11 |
You'll see that the dates and the results are now both stored in "new_col". These values will now be used as the new column names. If you have a limited number of columns, then you would simply hard-code the query:
select name, lost = [1],
draw=[2], won = [3],
[2014], [2013], [2012], Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select 'dt', dt union all
select 'result', result
) c (old_col_name, new_col)
) src
pivot
(
count(new_col)
for new_col in([1], [2], [3], [2014], [2013], [2012])
) piv
order by [2014];
See SQL Fiddle with Demo
Now since your years are dynamic, then you'll need to use dynamic sql. But it appears that you have 3 results and potentially multiple years - so I'd use a combination of static/dynamic sql to make this easier:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#orderby nvarchar(max)
select #cols
= STUFF((SELECT ',' + QUOTENAME(year(date))
from list
group by year(date)
order by year(date) desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #orderby = 'ORDER BY ['+cast(year(getdate()) as varchar(4)) + '] desc'
set #query = 'SELECT name, lost = [1],
draw=[2], won = [3],' + #cols + ', Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select ''dt'', dt union all
select ''result'', result
) c (old_col_name, new_col)
) x
pivot
(
count(new_col)
for new_col in ([1], [2], [3],' + #cols + ')
) p '+ #orderby
exec sp_executesql #query;
See SQL Fiddle with Demo. This gives a result:
| NAME | LOST | DRAW | WON | 2014 | 2013 | 2012 | TOTAL |
|----------|------|------|-----|------|------|------|-------|
| Person B | 7 | 1 | 2 | 6 | 2 | 2 | 10 |
| Person A | 5 | 3 | 3 | 4 | 3 | 4 | 11 |
| Person C | 2 | 1 | 1 | 3 | 1 | 0 | 4 |
If you want to only filter the result columns for the current year, then you can perform this filtering a variety of ways but the easiest you be to include a filter in the unpivot. The hard-coded version would be:
select name, lost = [1],
draw=[2], won = [3],
[2014], [2013], [2012], Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select 'dt', dt union all
select 'result', case when dt = 2014 then result end
) c (old_col_name, new_col)
) src
pivot
(
count(new_col)
for new_col in([1], [2], [3], [2014], [2013], [2012])
) piv
order by [2014] desc;
See SQL Fiddle with Demo. Then the dynamic sql version would be:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#orderby nvarchar(max),
#currentYear varchar(4)
select #currentYear = cast(year(getdate()) as varchar(4))
select #cols
= STUFF((SELECT ',' + QUOTENAME(year(date))
from list
group by year(date)
order by year(date) desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #orderby = 'ORDER BY ['+ #currentYear + '] desc'
set #query = 'SELECT name, lost = [1],
draw=[2], won = [3],' + #cols + ', Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select ''dt'', dt union all
select ''result'', case when dt = '+#currentYear+' then result end
) c (old_col_name, new_col)
) x
pivot
(
count(new_col)
for new_col in ([1], [2], [3],' + #cols + ')
) p '+ #orderby
exec sp_executesql #query;
See SQL Fiddle with Demo. This version will give a result:
| NAME | LOST | DRAW | WON | 2014 | 2013 | 2012 | TOTAL |
|----------|------|------|-----|------|------|------|-------|
| Person B | 4 | 0 | 2 | 6 | 2 | 2 | 10 |
| Person A | 2 | 1 | 1 | 4 | 3 | 4 | 11 |
| Person C | 1 | 1 | 1 | 3 | 1 | 0 | 4 |