Group and expand to new columns using a SQL query? - sql

Having this data:
Name
Date
John
2021-03-01 10:00
Paul
2021-03-01 11:00
Paul
2021-03-01 14:20
John
2021-03-01 15:00
Paul
2021-03-01 17:00
How can I obtain this result (Dates ordered ASC)
Name
Date1
Date2
Date2
John
2021-03-01 10:00
2021-03-01 15:00
NULL
Paul
2021-03-01 11:00
2021-03-01 14:20
2021-03-01 17:00
Thank you.

If you want to make your query dynamic that means no matter how many dates you have for any given name this query will generate that number of columns automatically try below query:
Schema:
create table mytable (Name varchar(50),[Date] Datetime);
insert into mytable values('John' , '2021-03-01 10:00');
insert into mytable values('Paul' , '2021-03-01 11:00');
insert into mytable values('Paul' , '2021-03-01 14:20');
insert into mytable values('John' , '2021-03-01 15:00');
insert into mytable values('Paul' , '2021-03-01 17:00');
Query:
declare #cols as varchar(max), #colsForSelect as varchar(max), #query as varchar(max);
select #colsForSelect=string_agg(concat(quotename(rn),' ', datename),',' )from(
select distinct concat('Date',rn) datename,rn from
(SELECT row_number()over(partition by name order by [date])rn from mytable)t)a
select #cols =string_agg(quotename(rn),',') from (
select distinct rn from
(SELECT row_number()over(partition by name order by [date])rn from mytable)t)a
set #query = 'Select Name, ' + #colsForSelect + ' from
(
SELECT *,row_number()over(partition by name order by [date])rn
from mytable
) x
pivot
(
max([date])
for rn in (' + #cols + ')
) p
group by Name,' + #cols
execute(#query);
Output:
Name
Date1
Date2
Date3
John
2021-03-01 10:00:00.000
2021-03-01 15:00:00.000
null
Paul
2021-03-01 11:00:00.000
2021-03-01 14:20:00.000
2021-03-01 17:00:00.000
db<>fiddle here

Based on Larnu's help, This worked:
WITH RNs AS(
SELECT [Name],
[DateTime],
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY (SELECT NULL)) AS RN
FROM dbo.Punch
WHERE Date = '2016-04-18'
)
SELECT Name,
MAX(CASE RN WHEN 1 THEN [DateTime] END) AS Result1,
MAX(CASE RN WHEN 2 THEN [DateTime] END) AS Result2,
MAX(CASE RN WHEN 3 THEN [DateTime] END) AS Result3,
MAX(CASE RN WHEN 4 THEN [DateTime] END) AS Result4
FROM RNs R
GROUP BY Name

I have tried with Stuff function instead of sting_agg which was introduced in 2017 server. If you are using below 2017 version you can use the below query.
declare #column_name varchar(5000)
declare #col_name varchar(5000)
set #column_name = (select stuff((select ','+'['+cast(rn as varchar(1000))+']' from(select distinct row_number()over(partition by name order by (select null))as rn from mytable)a
for xml path('')), 1,1,''))
set #col_name = (select stuff((select ','+'['+cast(rn as varchar(1000))+']' +' Date'+cast(rn as varchar(1000)) from(select distinct row_number()over(partition by name order by (select null))as rn from mytable)a
for xml path('')), 1,1,''))
exec('select name, '+#col_name +'
from (
select row_number()over(partition by name order by (select null))rn, year([date]) yr, *
from mytable
)a
pivot
(
max([date]) for [rn] in ('+#column_name+' )
)pv')

Related

SQL Group Count

I have a table like this
Date County Location
2020-01-01 abc west
2020-01-02 abc north
2020-02-01 xzy west
2020-02-02 xzy east
2020-02-03 xyz east
Can we group and count so it can become
County jan feb
abc 2
xyz 3
Location jan feb
west 1
north 1
west 1
east 2
Thank you
Try this as the base query and then write a pivot query based on this query result as shown in the demo link.
For your reference FROM - Using PIVOT and UNPIVOT.
Select country
, FORMAT([date], 'MMMM') as Month
, count(*) as Tot
from YourTable
group by country, FORMAT([date], 'MMMM')
Pivot query needed an aggregate function. Here is the complete query.
create table YourTable
([Date] Date
, Country varchar(20)
, Location varchar(20))
insert into YourTable values
('2020-01-01', 'abc', 'west'),
('2020-01-02', 'abc', 'north'),
('2020-02-01', 'xzy', 'west'),
('2020-02-02', 'xzy', 'east'),
('2020-02-03', 'xyz', 'east')
Select * into #temp from(
Select country
, FORMAT([date], 'MMMM') as Month
, count(*) as Tot
from YourTable
group by country, FORMAT([date], 'MMMM')
)a
--Select * from #temp
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.Month)
FROM #temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT country, ' + #cols + ' from
(
select Country
, Month
, Tot
from #temp
) x
pivot
(
max(Tot)
for Month in (' + #cols + ')
) p '
execute(#query)
Live db<>fiddle demo.
Use conditional aggregation along with grouping sets:
select county, location,
sum(case when date >= '2020-01-01' and date < '2020-02-01' then 1 end) as jan,
sum(case when date >= '2020-02-01' and date < '2020-03-01' then 1 end) as feb
from t
group by grouping sets ( (country), (location) );

Select sum with other table in SQL

How do I select sum with other table if I have data like below:
Table Member
MemberID Name DateJoin
M0001 John 01/01/2015
M0002 Willy 03/20/2016
M0003 Teddy 02/01/2017
etc....
Table Transaction
MemberID TransDate Total
M0002 02/01/2015 100000
M0002 02/28/2015 222000
M0001 01/01/2016 150000
M0001 01/26/2017 160000
M0002 01/25/2017 160000
M0003 02/01/2017 9000
I want the result as a sum of how many times the member transaction in shop in years 2015-2017
The result I want it's:
MemberID 2015 2016 2017
M0001 0 1 1
M0002 2 0 1
M0003 0 0 1
How many members will appear in Result although don't have transaction too.
try dynamic sql .
--load in #temp table
select MemberID , datepart (yyyy ,TransDate ) as TransDate ,COUNT(*)as cnt into #temp from [Transaction]
group by MemberID , datepart (yyyy ,TransDate )
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.TransDate)
FROM #temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT MemberID, ' + #cols + ' from
(
select MemberID
, cnt
, TransDate
from #temp
) x
pivot
(
max(cnt)
for TransDate in (' + #cols + ')
) p '
execute(#query)
drop #temp -- cleanup of #temp table
CREATE TABLE #Table1
([MemberID] varchar(5), [Name] varchar(5), [DateJoin] datetime)
;
INSERT INTO #Table1
([MemberID], [Name], [DateJoin])
VALUES
('M0001', 'John', '2015-01-01 00:00:00'),
('M0002', 'Willy', '2016-03-20 00:00:00'),
('M0003', 'Teddy', '2017-02-01 00:00:00')
;
CREATE TABLE #Table2
([MemberID] varchar(5), [TransDate] datetime, [Total] int)
;
INSERT INTO #Table2
([MemberID], [TransDate], [Total])
VALUES
('M0002', '2015-02-01 00:00:00', 100000),
('M0002', '2015-02-28 00:00:00', 222000),
('M0001', '2016-01-01 00:00:00', 150000),
('M0001', '2017-01-26 00:00:00', 160000),
('M0002', '2017-01-25 00:00:00', 160000),
('M0003', '2017-02-01 00:00:00', 9000)
;
select MemberID,[2015], [2016], [2017]
from
(
select a.MemberID,a.name,a.DateJoin,year(b.TransDate)[year],b.Total from #Table1 A join
#Table2 B on a.MemberID=b.MemberID
) src
pivot
(
count(total)
for year in ([2015], [2016], [2017])
) piv;
output
MemberID 2015 2016 2017
M0001 0 1 1
M0002 2 0 1
M0003 0 0 1
IN 2000
SELECT MEMBERID, COUNT(CASE WHEN YEAR=2015 THEN YEAR END ) AS [2015],
COUNT(CASE WHEN YEAR=2016 THEN YEAR END ) AS [2016],
COUNT(CASE WHEN YEAR=2017 THEN YEAR END ) AS [2017]
FROM (
SELECT A.MEMBERID,A.NAME,A.DATEJOIN,YEAR(B.TRANSDATE)[YEAR],B.TOTAL FROM #TABLE1 A JOIN
#TABLE2 B ON A.MEMBERID=B.MEMBERID)A
GROUP BY MEMBERID
It seems there is no information you need from table member. So select from table transaction alone and count conditionally.
select
memberid,
count(case when year(transdate) = 2015 then 1 end) as [2015],
count(case when year(transdate) = 2016 then 1 end) as [2016],
count(case when year(transdate) = 2017 then 1 end) as [2017]
from transaction
group by memberid
order by memberid;
If you want to include members that don't have any transaction, then you do need a join (an outer join that is):
select
m.memberid,
count(case when year(t.transdate) = 2015 then 1 end) as [2015],
count(case when year(t.transdate) = 2016 then 1 end) as [2016],
count(case when year(t.transdate) = 2017 then 1 end) as [2017]
from member m
left join transaction t on t.memberid = m.memberid
group by m.memberid
order by m.memberid;

CTC Annual History as a Pivot Table sql server

My table looks like below
select * from Salary_hist
EMPNAME CTC DateAdded
===========================
Ram 300000 2015-01-01
Ram 30000 2014-01-01
Ram 3000 2013-01-02
Ram 2700 2013-01-01
Ram 300 2012-01-01
Ram 30 2011-01-01
I am trying to make the output to show History increase for all employee based on Max Salary for a particular year
Output like
EMPNAME 2015 2014 2013 2012 2011
============================================
Ram 300000 30000 3000 300 30
Tried two method using rank and Alias but seems missing some thing.
select
Y.EMPNAME,
year(Y.DateAdded),
year(y.DateAdded)-2,
year(y.DateAdded)-3,
year(y.DateAdded)-4,
year(y.DateAdded)-5
from (
select X.EMPNAME,
max(case when rn = 1 then CTC end) as dateadded,
max(case when rn = 2 then CTC end) as dateadded1,
max(case when rn = 3 then CTC end) as dateadded2,
max(case when rn = 4 then CTC end) as dateadded3,
max(case when rn = 5 then CTC end) as dateadded4
from
(
select row_number() over (partition by empname order by dateadded desc) as rn,
empname,
CTC,
Dateadded
from Salary_hist
) X group by EMPNAME,dateadded)
y
And
select EMPNAME,
max(case when DatePart(Year, dateadded) = DatePart(Year, GETDATE()) then CTC end) as dateadded,
max(case when DatePart(Year, dateadded) = DatePart(Year, GETDATE()-1) then CTC end) as dateadded-1,
max(case when DatePart(Year, dateadded) = DatePart(Year, GETDATE()-2) then CTC end) as dateadded-2,
max(case when DatePart(Year, dateadded) = DatePart(Year, GETDATE()-3) then CTC end) as dateadded-3,
max(case when DatePart(Year, dateadded) = DatePart(Year, GETDATE()-4 ) then CTC end) as dateadded-4,
max(case when DatePart(Year, dateadded) = DatePart(Year, GETDATE()-5) then CTC end) as dateadded-5
from Salary_hist
group by EMPNAME, dateadded)
You can do this in a much simple way using MAX with PARTITION BY and taking DISTINCT on it. You doesn't need a Sub-query here.
1. STATIC PIVOT
You can use this when the value of year is known in advance.
SELECT EMPNAME,[2015],[2014],[2013],[2012],[2011]
FROM
(
SELECT DISTINCT EMPNAME,MAX(CTC) OVER(PARTITION BY EMPNAME,YEAR(DATEADDED))MAXCTC,
YEAR(DATEADDED)PERIOD
FROM #TEMP
)S
PIVOT
(
MIN(MAXCTC)
FOR PERIOD IN([2015],[2014],[2013],[2012],[2011])
)P
Click here to view result
2. Dynamic pivot
You can use this if the values of year is unknown in advance.
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + YEAR + ']', '[' + YEAR + ']')
FROM (SELECT DISTINCT CAST(YEAR(DATEADDED)AS VARCHAR(4)) [YEAR] FROM #TEMP) PV
ORDER BY CAST([YEAR] AS INT) DESC
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT * FROM
(
SELECT DISTINCT EMPNAME,MAX(CTC) OVER(PARTITION BY EMPNAME,YEAR(DATEADDED))MAXCTC,
YEAR(DATEADDED)PERIOD
FROM #TEMP
) x
PIVOT
(
MIN(MAXCTC)
FOR PERIOD IN (' + #cols + ')
) p
ORDER BY EMPNAME;'
EXEC SP_EXECUTESQL #query
Click here to view result
With that kind of table structure making a select that returns start salary of the year in case there is no salary increase for that year and otherwise returns the max. salary makes it slightly more complex:
select
empname, [2011], [2012], [2013], [2014], [2015]
from
(
select
empname,
year(dateAdded) as year,
max(CTC) as CTC
from
salary_hist S
group by
empname,
year(dateAdded)
union all
select
S.empname,
Y.Year,
S.CTC
from
(select distinct year(DateAdded) as Year from Salary_hist) Y
outer apply (
select S.empname, S.CTC,
row_number() over
(partition by empname order by DateAdded desc) as RN
from Salary_Hist S
where DateAdded
<= convert(datetime, convert(varchar, Y.Year) + '0101', 112)
) S
where
S.RN = 1
) as S
pivot (
max (CTC) for year in ([2011],[2012],[2013],[2014],[2015])
) as P

SQL query date into new columns with unique ID

I have the following columns, a 5 digit ID, date and a value. The ID repeats only when a new date is present.
ID Date Value
11111 2014-12-31 45
22222 2014-12-31 435
33333 2014-12-31 11
11111 2014-12-30 5
22222 2014-12-30 2245
33333 2014-12-30 86
11111 2014-12-29 43
22222 2014-12-29 4678
33333 2014-12-29 2494
I am trying to create an SQL query that will display the following (dates are column names):
ID 2014-12-31 2014-12-30 2014-12-29
11111 45 5 43
22222 435 2245 4678
33333 11 86 2494
What is the best way of doing this using MS SQL.
Thanks
As pointed out by the comments, you need to PIVOT your data. Here is one way using a Dynamic Crosstab.
Read this article by Jeff Moden for reference: http://www.sqlservercentral.com/articles/Crosstab/65048
CREATE TABLE temp(
ID INT,
[Date] DATE,
Value INT
)
INSERT INTO temp VALUES
(11111, '2014-12-31', 45),
(22222, '2014-12-31', 435),
(33333, '2014-12-31', 11),
(11111, '2014-12-30', 5),
(22222, '2014-12-30', 2245),
(33333, '2014-12-30', 86),
(11111, '2014-12-29', 43),
(22222, '2014-12-29', 4678),
(33333, '2014-12-29', 2494);
DECLARE #sql1 VARCHAR(2000) = ''
DECLARE #sql2 VARCHAR(2000) = ''
DECLARE #sql3 VARCHAR(2000) = ''
SELECT #sql1 =
'SELECT
ID
'
SELECT #sql2 = #sql2 +
' ,MIN(CASE WHEN [Date] = CAST(''' + CONVERT(VARCHAR(10), [Date], 120) + ''' AS Date) THEN Value END) AS ['
+ CONVERT(VARCHAR(10), [Date], 120) + ']'+ CHAR(10)
FROM(
SELECT DISTINCT [Date] FROM temp
)t
ORDER BY [Date] DESC
SELECT #sql3 =
'FROM temp
GROUP BY ID
ORDER BY ID'
PRINT (#sql1 + #sql2 + #sql3)
EXEC (#sql1 + #sql2 + #sql3)
DROP TABLE temp
EDIT:
If you want to use fixed name, you'll want to assign a number for each Date. This can be done using ROW_NUMBER():
SELECT #sql2 = #sql2 +
' ,MIN(CASE WHEN [Date] = CAST(''' + CONVERT(VARCHAR(10), [Date], 120) + ''' AS Date) THEN Value END) AS [Date' + CONVERT(VARCHAR, rn) + ']'+ CHAR(10)
FROM(
SELECT
[Date],
rn = ROW_NUMBER() OVER(ORDER BY [Date])
FROM (
SELECT DISTINCT [Date]FROM temp
)x
)t
ORDER BY [Date] DESC

Convert Rows into Columns in Sql query

I am having tough time to figure out this please help me
I have time in/out sql query
I have a table looks like below. there are four columns display time in/out info such as...
Date Day Day TimeStamp CheckType
10/11/2014 Sat 8:30 am in
10/11/2014 Sat 11:30am out
10/11/2014 Sat 1:30pm in
10/11/2014 Sat out
10/12/2014 Sun 9:00am in
10/12/2014 Sun 11:20pm out
10/12/2014 Sun 5:20pm out
10/13/2014 Mon 8:00am in
10/13/2014 Mon 6:10pm in
so whoever checkin or checkout then the record will display the result in order and if someone is supposed to check out but accidently pressed in button then this will display as it is (in) or if someone forget to check out then that space will show blank
I am trying to convert rows into column and display such information in below
Date Day Time Type Time Type Time Type Time Type etc-----
10/11/2014 Sat 8:30am in 11:30am out 1:30pm in
10/12/2014 Sun 9:00am in 11:20am out 1:20pm in 6:20pm in
10/13/2014 Mon 8:00am in 6:10pm out
10/14/2014 Tus 8:20am in
etc
I have tried to use pivot
select Date, Day, [1],[2],[3],[4],[5],[6],[7],[8],[9],[10] etc---
from
(
select Date, Day, Stamptime, CheckTime, userID
from a table
)
pivot
(
max(StampTime)
for stamptime in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10] etc---)
) as PivotTable
can anyone explain how to convert the rows into columns I have spent many days already.
Here's something close to what you're after, making use of XML to get the variable number of columns. As mentioned in my comment above though, I don't recommend this approach.
SQL Fiddle: http://sqlfiddle.com/#!3/e5b325/2
select [Date]
, [Day]
, (
select [TimeStamp] [Time]
, [CheckType] [Type]
from aTable b
where b.[Date] = a.[Date]
order by [TimeStamp], [CheckType]
for xml path ('')
) CheckInAndOutInfo
from aTable a
group by [Date], [Day]
order by [Date]
Output:
DATE DAY CHECKINANDOUTINFO
2014-10-11 Sat <Type>Out</Type><Time>08:30:00</Time><Type>In</Type><Time>11:30:00</Time><Type>Out</Type><Time>13:30:00</Time><Type>In</Type>
2014-10-12 Sun <Time>09:00:00</Time><Type>In</Type><Time>17:20:00</Time><Type>Out</Type><Time>23:20:00</Time><Type>Out</Type>
2014-10-13 Mon <Time>08:00:00</Time><Type>In</Type><Time>18:10:00</Time><Type>In</Type>
Alternatively, if you can guarantee you'll never have more than a certain number of check-ins/outs per day, you could do the following (this assumes no more than 5 per day):
SQL Fiddle: http://sqlfiddle.com/#!3/e5b325/4
select *
from
(
select [Date], [Day]
, 'T' + CAST(ROW_NUMBER() over (partition by [Date] order by [TimeStamp], [CheckType]) as nvarchar) r
, cast([TimeStamp] as nvarchar) pvtVal
from aTable
where [TimeStamp] is not null
union all
select [Date], [Day]
, 'C' + CAST(ROW_NUMBER() over (partition by [Date] order by [TimeStamp], [CheckType]) as nvarchar) r
, cast([CheckType] as nvarchar) pvtVal
from aTable
where [TimeStamp] is not null
) x
pivot
(
min(pvtVal)
for r in ([T1], [C1], [T2], [C2], [T3], [C3], [T4], [C4], [T5], [C5])
) y
order by [Date]
Output:
DATE DAY T1 C1 T2 C2 T3 C3 T4 C4 T5 C5
2014-10-11 Sat 08:30:00.0000000 In 11:30:00.0000000 Out 13:30:00.0000000 In (null) (null) (null) (null)
2014-10-12 Sun 09:00:00.0000000 In 17:20:00.0000000 Out 23:20:00.0000000 Out (null) (null) (null) (null)
2014-10-13 Mon 08:00:00.0000000 In 18:10:00.0000000 In (null) (null) (null) (null) (null) (null)
...Or if you wanted to use dynamic SQL, you could do this:
SQL Fiddle: http://sqlfiddle.com/#!3/e5b325/6
declare #sql nvarchar(max)
select #sql = coalesce(#sql+',','') + QUOTENAME('T' + CAST(x as nvarchar)) + ',' + QUOTENAME('C' + CAST(x as nvarchar))
from
(
select distinct row_number() over (partition by [Date] order by [Date]) x
from aTable
where [TimeStamp] is not null
) y
order by x
set #sql =
'select *
from
(
select [Date], [Day]
, ''T'' + CAST(ROW_NUMBER() over (partition by [Date] order by [TimeStamp], [CheckType]) as nvarchar) r
, cast([TimeStamp] as nvarchar) pvtVal
from aTable
where [TimeStamp] is not null
union all
select [Date], [Day]
, ''C'' + CAST(ROW_NUMBER() over (partition by [Date] order by [TimeStamp], [CheckType]) as nvarchar) r
, cast([CheckType] as nvarchar) pvtVal
from aTable
where [TimeStamp] is not null
) x
pivot
(
min(pvtVal)
for r in (' + #sql + ')
) y
order by [Date]'
exec (#sql)
Output:
DATE DAY T1 C1 T2 C2 T3 C3
2014-10-11 Sat 08:30:00.0000000 In 11:30:00.0000000 Out 13:30:00.0000000 In
2014-10-12 Sun 09:00:00.0000000 In 17:20:00.0000000 Out 23:20:00.0000000 Out
2014-10-13 Mon 08:00:00.0000000 In 18:10:00.0000000 In (null) (null)