Long to wide - SQL [duplicate] - sql-server

This question already has answers here:
SQL Server dynamic PIVOT query?
(9 answers)
Closed 6 years ago.
I have a table that is long (for example)
Date Person Number
2015-01-03 A 4
2015-01-04 A 2
2015-01-05 A 3
2015-01-03 B 5
2015-01-04 B 6
2015-01-05 B 7
2015-01-03 C 1
2015-01-04 C 3
2015-01-05 C 4
2015-01-05 D 4
2015-01-04 E 1
2015-01-05 E 3
And I need it to look like (wide):
Date A B C D E
2015-01-03 4 5 1 0 0
2015-01-04 2 6 3 0 1
2015-01-05 3 7 4 4 3
Any help would be much appreciated. I am using Transact. Thanks.

Using PIVOT:
select date, isNull([A], 0) as A,
isNull([B], 0) as B,
isNull([C], 0) as C,
isNull([D], 0) as D,
isNull([E], 0) as E
from
( select date, person, number
from tbl ) AS SourceTable
PIVOT
( max(number) for
Person in ( [A], [B], [C], [D], [E]) ) AS PivotTable;
Dynamic version is:
DECLARE #columns NVARCHAR(MAX), #whereColumns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SET #whereColumns = N'';
SELECT #columns += N', isNull(p.' + QUOTENAME(Person) +', 0) as ' + Person,
#whereColumns += N', ' +QUOTENAME (Person)
FROM (select Person from tbl group by Person) AS x;
SET #sql = N'
select date, ' + STUFF(#columns, 1, 2, '') +
N'
from
( select date, person, number
from tbl ) AS SourceTable
PIVOT
( max(number) for
Person in ( '
+ stuff(#whereColumns, 1, 2, '')
+ ' )
) AS P '
print #sql;
exec (#sql);

You can use conditional aggregation:
SELECT
Date,
[A] = MAX(CASE WHEN Person = 'A' THEN Number ELSE 0 END),
[B] = MAX(CASE WHEN Person = 'B' THEN Number ELSE 0 END),
[C] = MAX(CASE WHEN Person = 'C' THEN Number ELSE 0 END),
[D] = MAX(CASE WHEN Person = 'D' THEN Number ELSE 0 END),
[E] = MAX(CASE WHEN Person = 'E' THEN Number ELSE 0 END)
FROM #tbl
GROUP BY Date
If you have unknown number of Persons, then you have to do it dynamically:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql =
'SELECT
Date' + CHAR(10) +
(
SELECT DISTINCT
' , MAX(CASE WHEN Person = ''' + Person + ''' THEN Number ELSE 0 END) AS ' + QUOTENAME(Person) + CHAR(10)
FROM #tbl
FOR XML PATH('')
) +
'FROM #tbl
GROUP BY Date;';
PRINT (#sql);
EXEC (#sql);
ONLINE DEMO

Related

How to Mentioned Leavetype in employee attendance register?

I have two tables. One is employee attendance and second one is Employee Leave.
Employee attendance col( Empid,INTIME,OUTTIME,Status)
Employee Leave col(Empid,StartDate,Endate,leavetype(PL,CL,SL))
I have created pivot table in which present, absent and HD mentioned and created another separate query for sum of row (Present ,absent and HD) From EmployeeAttendance Table.
===Sum query===
SELECT SUM(CASE WHEN status = 'P' THEN 1
WHEN status = 'HD' THEN 0.5 WHEN status = 'A' THEN 0 END) AS [T.P],
SUM(CASE WHEN status = 'A' THEN 1 WHEN status = 'HD' THEN 0.5 END) AS [A],
SUM(CASE WHEN status = 'P' THEN 1
WHEN status = 'HD' THEN 1 WHEN status = 'A' THEN 1 END) AS [TDay ]
FROM EmployeesAttendance
--WHERE (ReportingDate BETWEEN #StartDate AND #Enddate)
GROUP BY EmpID
===Pivot Table ===
for converting col into row
SELECT DISTINCT ReportingDate INTO #Dates
FROM EmployeesAttendance
ORDER BY ReportingDate
DECLARE #cols NVARCHAR(4000)
SELECT #cols = COALESCE(#cols + ',[' + CONVERT(varchar, DATEPART(DAY, ReportingDate), 112)
+ ']','[' + CONVERT(varchar,DATEPART(DAY, ReportingDate), 112) + ']')
FROM #Dates
ORDER BY ReportingDate
DECLARE #qry NVARCHAR(4000) =
N'SELECT *
FROM (SElECT EmployeeDetails.EmpID,EmployeeDetails.EmpName,EmployeesAttendance.Status,
DATEPART(DAY, EmployeesAttendance.ReportingDate)as DDate
FROM EmployeesAttendance Inner Join EmployeeDetails on EmployeesAttendance.EmpID=EmployeeDetails.Empid )
emp
PIVOT (MAX(Status) FOR DDate IN (' + #cols + ')) AS stat
-- Executing the query
EXEC(#qry)
I want this Employee attendance Register.
Date 1 2 3 4 5 Total Absent Present Leave
Emp1 P P A PL P 5 1 3 1
Emp2 Cl pl A PL P 5 1 2 2
Now I want to merge above bother query of Sum and Pivot, also mentioned leave type in attendance register if employee availed leaves From 01-01-2019 to 02-01-2019 Noofdays equal to 2 for (emp2)

SQL, using rank to generate columns

I have a list of occupants of a property and need to manipulate the data so it instead shows the property as one row with each additional occupant appearing in a new column.
Here is what I've managed to do so far:
with RANKING AS
( Select
Postcode
, Number
, Occupant
, RANK() OVER
(Partition by Postcode order by Occupant) as [Rank]
from Reporting.dbo.Test --order by [Rank] desc
)
The query in RANKING outputs the following table:
Postcode | Number | Occupant | Rank
AA001AA | 12 | D | 1
AA001AA | 12 | E | 2
AA001AA | 12 | K | 3
AA001AA | 12 | M | 4
AA001AA | 12 | T | 5
BB001BB | 8 | M | 1
BB001BB | 8 | R | 2
etc.
I've then tried to use the value of ranking to create columns, like so:
Select distinct
i.Postcode
, i.Number
, case when i.[rank] = 1 then i.Occupant end as [First Tennant]
, case when i.[rank] = 2 then i.Occupant end as [Second Tennant]
, case when i.[rank] = 3 then i.Occupant end as [Third Tennant]
, case when i.[rank] = 4 then i.Occupant end as [Fourth Tennant]
, case when i.[rank] = 5 then i.Occupant end as [Fifth Tennant]
from Reporting.dbo.Test u
inner join RANKING i on i.Postcode = u.Postcode
Now, 2 questions:
1) Is there any way to automate this process so for a rank of x we have x tenant rows
2) The table this outputs is
Postcode | Number | First Tennant | Second Tennant | Third Tennant | Fourth Tennant | Fifth Tennant |
AA001AA | 12 | D | NULL | NULL | NULL | NULL |
AA001AA | 12 | NULL | E | NULL | NULL | NULL |
etc.
How do I condense this list so each postcode only has one row and all the non-null values appear on that row.
To answer your second question first (how do you condense the list), the easiest way would just to group by postcode and number. You can just take the max() of each column (i.e if there's a value, it gets chosen; all the nulls fall out).
To answer the first question (can this be automated), you probably want to look into a dynamic pivot. One such article posted in the comments is this: SQL Server dynamic PIVOT query?
The idea is basically to serialize the distinct tennants, and concantenate that into a dynamic SQL string which performs a pivot. There are several ways to skin this cat, but here's how I approached it.
use tempdb
go
if object_id('tempdb.dbo.#data') is not null drop table #data
create table #data
(
PostCode varchar(10),
Number int,
Occupant char(1),
Rnk int,
ColName as 'Tennant ' + cast(Rnk as varchar(10))
)
insert into #Data(PostCode, Number, Occupant, Rnk)
select 'AA001AA', 12, 'D',1
union all select 'AA001AA', 12, 'E',2
union all select 'AA001AA', 12, 'K',3
union all select 'AA001AA', 12, 'M',4
union all select 'AA001AA', 12, 'T',5
union all select 'BB001BB', 8, 'M',1
union all select 'BB001BB', 8, 'R',2
declare
#PivotColumns nvarchar(max),
#SelectColumns nvarchar(max),
#sql nvarchar(max)
select
#PivotColumns = stuff((select ',' + quotename(ColName)
from #data
group by ColName
order by ColName
for xml path('')), 1, 1, ''),
#SelectColumns = stuff((select ',' + quotename(ColName) + ' = max(' + quotename(ColName) + ')'
from #data
group by ColName
order by ColName
for xml path('')), 1, 1, ''),
#sql = '
select
PostCode,
Number,
' + #SelectColumns + '
from #data d
pivot (max(Occupant) for ColName in (' + #PivotColumns + ' )) p
group by PostCode, Number'
print #sql
exec sp_executesql #sql
you can use dynamic pivot to get your result. please see below code-
create table #tab (Postcode varchar(10) , Number int , Occupant char(1) , Rank int)
insert into #tab
select 'AA001AA' , 12 , 'D' , 1
union all select 'AA001AA' , 12 , 'E' , 2
union all select 'AA001AA' , 12 , 'K' , 3
union all select 'AA001AA' , 12 , 'M' , 4
union all select 'AA001AA' , 12 , 'T' , 5
union all select 'BB001BB' , 8 , 'M' , 1
union all select 'BB001BB' , 8 , 'R' , 2
union all select 'CC001CC' , 8 , 'N' , 1
union all select 'CC001CC' , 8 , 'O' , 2
union all select 'CC001CC' , 8 , 'P' , 3
union all select 'CC001CC' , 8 , 'Q' , 4
union all select 'CC001CC' , 8 , 'R' , 5
union all select 'CC001CC' , 8 , 'S' , 6
union all select 'CC001CC' , 8 , 'T' , 7
union all select 'CC001CC' , 8 , 'U' , 8
union all select 'CC001CC' , 8 , 'V' , 9
union all select 'CC001CC' , 8 , 'W' , 10
declare #mx int , #min int = 1 , #sql nvarchar(max) = '' , #select1 nvarchar(max) = '',#select2 nvarchar(max) = ''
select #mx = MAX(rank) from #tab
while #min<= #mx
begin
set #select1 = #select1 + '[' + cast(#min as varchar(10))+ '] ,'
set #select2 = #select2 + '[' + cast(#min as varchar(10))+ '] as '+ '[Tennant_' + cast(#min as varchar(10))+ '] ,'
set #min = #min + 1
end
set #select1 = SUBSTRING( #select1 , 1 , LEN(#select1)-1)
set #select2 = SUBSTRING( #select2 , 1 , LEN(#select2)-1)
set #sql = '
SELECT Postcode , Number ,'+#select2+'
FROM #tab
PIVOT
(
max(Occupant)
FOR [Rank] IN ('+#select1+')
)AS pvt '
exec sp_executesql #sql

Stored Procedure is returning duplicate records

I have the following tables 7 tables:
1) Titles
ID Title Author
-------------------------------------------------------------------------
1 The Hidden Language of Computer Hardware and Software Charles Petzold
2 Paths, Dangers, Strategies Nick Bostrom
3 The Smart Girl's Guide to Privacy Violet Blue
4 Introduction to Algorithms Thomas H. Cormen
5 Machine Learning in Action Peter Harrington
...
2) Themes
ID Name
------------------------------------------
1 Science Fiction
2 Biography
3 Painting
...
3) Subjects
ID Name
-----------------------------------
1 Science
2 Technology
3 Music
4 Geography
...
4) Grades
ID Name
------------------------------------
1 Grade 1
2 Grade 2
3 Grade 3
4 Grade 4
5 Grade 5
...
5) TitleThemeAssociation
TitleID ThemeID
------------------------------------------
1 1
1 3
4 2
4 3
...
6) TitleSubjectAssociaton
TitleID SubjectID
---------------------------------
1 1
1 3
2 1
2 3
4 1
4 2
...
7) TitleGradeAssociaton
TitleID GradeID
1 1
1 2
1 3
2 1
2 2
...
I have a stored procudure as below:
CREATE PROCEDURE [dbo].[GetTitlesPageWise]
#PageIndex INT = 1
,#PageSize INT = 10
,#searchText NVARCHAR(250) = ''
,#PageCount INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SET FMTONLY OFF;
select ROW_NUMBER() over
(
ORDER BY [id] ASC
) as RowNumber,
T.Id As [Title ID],
T.Title,
H.Theme,
S.Subject,
G.Grade
into #Results
From Titles T
Outer Apply
(
Select Stuff(( Select ', ' + Name
From Themes H
Join TitleThemeAssociaton TH On H.Id = TH.ThemeId
Where TH.TitleId = T.Id
For Xml Path('')), 1, 2, '') As Theme
From Themes
) H
Outer Apply
(
Select Stuff(( Select ', ' + Name
From Subjects S
Join TitleSubjectAssociation TS On S.Id = TS.SubjectId
Where TS.TitleId = T.Id
For Xml Path('')), 1, 2, '') As Subject
From Subjects
) S
Outer Apply
(
Select Stuff(( Select ', ' + Name
From Grades G
Join TitleGradeAssociation TG On G.Id = TG.GradeId
Where TG.TitleId = T.Id
For Xml Path('')), 1, 2, '') As Grade
From Grades
) G
WHERE
t.title Like #searchText + '%'
AND
(
H.Theme Is Null
Or S.Subject Is Null
Or G.Grade Is Null
)
DECLARE #RecordCount INT
SELECT #RecordCount = COUNT(*) FROM #Results
SET #PageCount = CEILING(CAST(#RecordCount AS DECIMAL(10, 2)) / CAST(#PageSize AS DECIMAL(10, 2)))
PRINT #PageCount
SELECT * FROM #Results
WHERE RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
DROP TABLE #Results
END
I want distinct title ID and output is like as below but the query is giving the duplicate result. If Theme, Subject and Grade all there values are assigned that record should be excluded form the result. In the above case Title ID 1 should be excluded because all three values are present there. I need help to fix the problem.
RowNumber Title ID Title Theme Subject Grade
1 2 Paths, Dangers, Strategies NULL Science , Music Grade 1, Grade 2
2 3 The Smart Girl's Guide to Privacy NULL NULL NULL
3 4 Introduction to Algorithms Biography, Painting Science , Technology NULL
4 5 Machine Learning in Action NULL NULL NULL
.............
SQL DEMO
SELECT
ROW_NUMBER() OVER (ORDER BY [ID]) rn,
[ID],
[Title],
Subject = STUFF ((
SELECT ',' + S.[Name]
FROM TitleSubjectAssociation TS
JOIN Subjects S
ON TS.SubjectId = S.Id
WHERE TS.[TitleID] = T.[ID]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, ''
),
Theme = STUFF ((
SELECT ',' + TH.[Name]
FROM TitleThemeAssociation TA
JOIN Themes TH
ON TA.[ThemeID] = TH.[ID]
WHERE TA.[TitleID] = T.[ID]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, ''
),
Grade = STUFF ((
SELECT ',' + G.[Name]
FROM TitleGradeAssociation TG
JOIN Grades G
ON TG.[GradeID] = G.[ID]
WHERE TG.[TitleID] = T.[ID]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, ''
)
FROM Titles T;
If you need a filter you can try checking how many , are in the results. two , mean three elements.
WHERE len(Subject) - len(replace(Subject,',','')) != 2
AND len(Theme) - len(replace(Theme,',','')) != 2
AND len(Grade) - len(replace(Grade,',','')) != 2
OUTPUT

Count columns with a value x at least once

Suppose I have this table:
A B C
------------
1 0 0
0 0 1
1 0 1
0 0 0
I need the count of columns where 1 occurs (irrespective of the number of times it occurs). So in this example the count would be 2 since it occurs in 2 columns A & C.
How can this be done in SQL server?
Edit: From comments
Number of columns maybe fixed but unknown
A query like below will give you correct results if number of columns are known, for unknown number of columns this query can be made dynamic.
SELECT
MAX(CASE WHEN colA = 1 THEN 1 ELSE 0 END) +
MAX(CASE WHEN colB = 1 THEN 1 ELSE 0 END) +
MAX(CASE WHEN colC = 1 THEN 1 ELSE 0 END) +
--...
MAX(CASE WHEN colZ = 1 THEN 1 ELSE 0 END) as CountOfColumns
FROM tableT
Below is a dynamic query:
declare #q varchar(max)
select
#q= 'select ' +
stuff((
select
'+ MAX(CASE WHEN ' + C.name + ' = 1 THEN 1 ELSE 0 END) '
from
sys.columns C inner join sys.tables T
on C.object_id=T.object_id and T.name='tableT'
for xml path('')),1,1,'')
+ ' as CountOfColumns FROM tableT'
exec( #q)

SQL Server 2012 PIVOT without aggregate

I have the following sample data:
Id Name Category
-----------------------
1 Joe A
2 Joe B
3 Joe D
4 Mary A
5 Mary C
6 Mary D
I would like to show the categories a person belongs to like so:
Name CategoryA CategoryB CategoryC CategoryD
--------------------------------------------------
Joe X X X
Mary X X X
1's and 0's could be used in place of X's and blanks.
This smells like a PIVOT question to me.
There are several ways that you can transform the data. Some use an aggregate function and others don't. But even though you are pivoting a string you can still apply an aggregate.
Aggregate with CASE:
select name,
max(case when category = 'A' then 'X' else '' end) CategoryA,
max(case when category = 'B' then 'X' else '' end) CategoryB,
max(case when category = 'C' then 'X' else '' end) CategoryC,
max(case when category = 'D' then 'X' else '' end) CategoryD
from yourtable
group by name
See SQL Fiddle with Demo
Static Pivot:
You can still use the PIVOT function to transform the data even though the values are strings. If you have a known number of categories, then you can hard-code the query:
select name,
coalesce(A, '') CategoryA,
coalesce(B, '') CategoryB,
coalesce(C, '') CategoryC,
coalesce(C, '') CategoryD
from
(
select name, category, 'X' flag
from yourtable
) d
pivot
(
max(flag)
for category in (A, B, C, D)
) piv
See SQL Fiddle with Demo.
Dynamic Pivot:
If you have an unknown number of categories, then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#colsNull AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(category)
from yourtable
group by category
order by category
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsNull = STUFF((SELECT ', coalesce(' + QUOTENAME(category)+', '''') as '+QUOTENAME('Category'+category)
from yourtable
group by category
order by category
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT name, ' + #colsNull + '
from
(
select name, category, ''X'' flag
from yourtable
) x
pivot
(
max(flag)
for category in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
Multiple Joins:
select c1.name,
case when c1.category is not null then 'X' else '' end as CategoryA,
case when c2.category is not null then 'X' else '' end as CategoryB,
case when c3.category is not null then 'X' else '' end as CategoryC,
case when c4.category is not null then 'X' else '' end as CategoryD
from yourtable c1
left join yourtable c2
on c1.name = c2.name
and c2.category = 'B'
left join yourtable c3
on c1.name = c3.name
and c3.category = 'C'
left join yourtable c4
on c1.name = c4.name
and c4.category = 'D'
where c1.category = 'A'
See SQL Fiddle with Demo
All queries will give the result:
| NAME | CATEGORYA | CATEGORYB | CATEGORYC | CATEGORYD |
--------------------------------------------------------
| Joe | X | X | | X |
| Mary | X | | X | X |

Resources