CTC Annual History as a Pivot Table sql server - sql

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

Related

Select data where sum for last 7 from max-date is greater than x

I have a data set as such:
Date Value Type
2020-06-01 103 B
2020-06-01 100 A
2020-06-01 133 A
2020-06-11 150 A
2020-07-01 1000 A
2020-07-21 104 A
2020-07-25 140 A
2020-07-28 1600 A
2020-08-01 100 A
Like this:
Type ISHIGH
A 1
B 0
Here's the query i tried,
select type, case when sum(value) > 10 then 1 else 0 end as total_usage
from table_a
where (select sum(value) as usage from tableA where date = max(date)-7)
group by type, date
This is clearly not right. What is a simple way to do this?
It is a simply group by except that you need to be able to access max date before grouping:
select type
, max(date) as last_usage_date
, sum(value) as total_usage
, case when sum(case when date >= cutoff_date then value end) >= 1000 then 'y' end as [is high!]
from t
cross apply (
select dateadd(day, -6, max(date))
from t as x
where x.type = t.type
) as ca(cutoff_date)
group by type, cutoff_date
If you want just those two columns then a simpler approach is:
select t.type, case when sum(value) >= 1000 then 'y' end as [is high!]
from t
left join (
select type, dateadd(day, -6, max(date)) as cutoff_date
from t
group by type
) as a on t.type = a.type and t.date >= a.cutoff_date
group by t.type
Find the max date by type. Then used it to find last 7 days and sum() the value.
with
cte as
(
select [type], max([Date]) as MaxDate
from tableA
group by [type]
)
select c.[type], sum(a.Value),
case when SUM(a.Value) > 1000 then 1 else 0 end as ISHIGH
from cte c
inner join tableA a on a.[type] = c.[type]
and a.[Date] >= DATEADD(DAY, -7, c.MaxDate)
group by c.[type]
This can be done through a cumulative total as follows:
;With CTE As (
Select [type], [date],
SUM([value]) Over (Partition by [type] Order by [date] Desc) As Total,
Row_Number() Over (Partition by [type] Order by [date] Desc) As Row_Num
From Tbl)
Select Distinct CTE.[type], Case When C.[type] Is Not Null Then 1 Else 0 End As ISHIGH
From CTE Left Join CTE As C On (CTE.[type]=C.[type]
And DateDiff(dd,CTE.[date],C.[date])<=7
And C.Total>1000)
Where CTE.Row_Num=1
I think you are quite close with you initial attempt to solve this. Just a tiny edit:
select type, case when sum(value) > 1000 then 1 else 0 end as total_usage
from tableA
where date > (select max(date)-7 from tableA)
group by type

Group and expand to new columns using a SQL query?

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')

Minimum and maximum dates within continuous date range grouped by name

I have a data ranges with start and end date for a persons, I want to get the continuous date ranges only per persons:
Input:
NAME | STARTDATE | END DATE
--------------------------------------
MIKE | **2019-05-15** | 2019-05-16
MIKE | 2019-05-17 | **2019-05-18**
MIKE | 2020-05-18 | 2020-05-19
Expected output like:
MIKE | **2019-05-15** | **2019-05-18**
MIKE | 2020-05-18 | 2020-05-19
So basically output is MIN and MAX for each continuous period for the person.
Appreciate any help.
I have tried the below query:
With N AS ( SELECT Name, StartDate, EndDate
, LastStop = MAX(EndDate)
OVER (PARTITION BY Name ORDER BY StartDate, EndDate
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) FROM Table ), B AS ( SELECT Name, StartDate, EndDate
, Block = SUM(CASE WHEN LastStop Is Null Then 1
WHEN LastStop < StartDate Then 1
ELSE 0
END)
OVER (PARTITION BY Name ORDER BY StartDate, LastStop) FROM N ) SELECT Name
, MIN(StartDate) DateFrom
, MAX(EndDate) DateTo FROM B GROUP BY Name, Block ORDER BY Name, Block
But its not considering the continuous period. It's showing the same input.
This is a type of gap-and-islands problem. There is no need to expand the data out by day! That seems very inefficient.
Instead, determine the "islands". This is where there is no overlap -- in your case lag() is sufficient. Then a cumulative sum and aggregation:
select name, min(startdate), max(enddate)
from (select t.*,
sum(case when prev_enddate >= dateadd(day, -1, startdate) then 0 else 1 end) over
(partition by name order by startdate) as grp
from (select t.*,
lag(enddate) over (partition by name order by startdate) as prev_enddate
from t
) t
) t
group by name, grp;
Here is a db<>fiddle.
Here is an example using an ad-hoc tally table
Example or dbFiddle
;with cte as (
Select A.[Name]
,B.D
,Grp = datediff(day,'1900-01-01',D) - dense_rank() over (partition by [Name] Order by D)
From YourTable A
Cross Apply (
Select Top (DateDiff(DAY,StartDate,EndDate)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),StartDate)
From master..spt_values n1,master..spt_values n2
) B
)
Select [Name]
,StartDate= min(D)
,EndDate = max(D)
From cte
Group By [Name],Grp
Returns
Name StartDate EndDate
MIKE 2019-05-15 2019-05-18
MIKE 2020-05-18 2020-05-19
Just to help with the Visualization, the CTE generates the following
This will give you the same result
SELECT subquery.name,min(subquery.startdate),max(subquery.enddate1)
FROM (SELECT NAME,startdate,
CASE WHEN EXISTS(SELECT yt1.startdate
FROM t yt1
WHERE yt1.startdate = DATEADD(day, 1, yt2.enddate)
) THEN null else yt2.enddate END as enddate1
FROM t yt2) as subquery
GROUP by NAME, CAST(MONTH(subquery.startdate) AS VARCHAR(2)) + '-' + CAST(YEAR(subquery.startdate) AS VARCHAR(4))
For the CASE WHEN EXISTS I refered to SQL CASE
For the group by month and year you can see this GROUP BY MONTH AND YEAR
DB_FIDDLE

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;

Get all customer having limited transactions

I have to extract all those customer names having transactions of less than 5000 each per month for 6 consecutive months and then have 3 transactions of 20,000 each on 7th month.
All the transactions for a customer will be stored in different rows.
Example: Considering customer A, Information for the customer will be stored as follows:
Name | TransactionDate | Amount
1. CustomerA | 27-08-2015 | 4500
2. CustomerA | 27-09-2015 | 4500
3. CustomerA | 27-10-2015 | 4500
4. CustomerA | 27-11-2015 | 4500
5. CustomerA | 27-12-2015 | 4500
6. CustomerA | 27-01-2016 | 4500
7. CustomerA | 27-02-2016 | 20000
8. CustomerA | 27-02-2016 | 20000
9. CustomerA | 27-02-2016 | 20000
Until you specify SQL flavor, I think I got a flexible and decent solution for T-SQL:
1) To have simpler queries, I have defined as persisted column to store month number is a convenient way:
create table CustomerTransaction
(
CustomerName VARCHAR(20),
TransactionDate DATE,
Amount NUMERIC(18, 2),
MonthNo AS DATEPART(yyyy, TransactionDate) * 12 + DATEPART(mm, TransactionDate) - 1 PERSISTED
)
If this cannot be used, you can employee date arithmetic (DATEDIFF), or have the exact computation inlined.
First CTE gets transaction data with a row number and a start month number for that group (customer and payment series).
For each category, small amounts and 20K (big amounts), I have selected from previous CTE applying filtering based on amount.
For each series apply the count criteria (6 small payments, followed by 3 big ones).
Join small and big payments based on customer and dates (group month is the smallest group month - 1).
The final query is the following:
declare #SmallAmountsLen INT = 6;
declare #BigAmountsLen INT = 3;
declare #SmallAmountThreshold NUMERIC(18, 2) = 5000
declare #BigAmount NUMERIC(18, 2) = 20000
;with AmountCte AS (
SELECT CustomerName, TransactionDate, Amount, MonthNo, ROW_NUMBER() OVER (PARTITION BY CustomerName ORDER BY TransactionDate) AS RowNo,
MonthNo - ROW_NUMBER() OVER (PARTITION BY CustomerName ORDER BY TransactionDate) AS GroupMonthNo
FROM CustomerTransaction
),
SmallAmountCte AS (
SELECT *
FROM AmountCte
WHERE Amount < #SmallAmountThreshold
),
BigAmountCte AS (
SELECT *
FROM AmountCte
WHERE Amount = #BigAmount
),
SmallGroupCte AS (
select CustomerName, GroupMonthNo
from SmallAmountCte
group by CustomerName, GroupMonthNo
having count(1) = #SmallAmountsLen
),
BigGroupCte AS (
select CustomerName, MonthNo
from BigAmountCte
group by CustomerName, MonthNo
having count(1) = #BigAmountsLen
)
select S.*, B.*
from SmallGroupCte S
join BigGroupCte B on B.CustomerName = S.CustomerName
where B.MonthNo = S.GroupMonthNo + #SmallAmountsLen + 1
[EDIT] Query without need of a computed column
declare #SmallAmountsLen INT = 6;
declare #BigAmountsLen INT = 3;
declare #SmallAmountThreshold NUMERIC(18, 2) = 5000
declare #BigAmount NUMERIC(18, 2) = 20000
;with AmountCte AS (
SELECT CustomerName, TransactionDate, Amount, DATEPART(yyyy, TransactionDate) * 12 + DATEPART(mm, TransactionDate) - 1 AS MonthNo,
ROW_NUMBER() OVER (PARTITION BY CustomerName ORDER BY TransactionDate) AS RowNo,
DATEPART(yyyy, TransactionDate) * 12 + DATEPART(mm, TransactionDate) - 1 - ROW_NUMBER() OVER (PARTITION BY CustomerName ORDER BY TransactionDate) AS GroupMonthNo
FROM CustomerTransaction
),
SmallAmountCte AS (
SELECT *
FROM AmountCte
WHERE Amount < #SmallAmountThreshold
),
BigAmountCte AS (
SELECT *
FROM AmountCte
WHERE Amount = #BigAmount
),
SmallGroupCte AS (
select CustomerName, GroupMonthNo
from SmallAmountCte
group by CustomerName, GroupMonthNo
having count(1) = #SmallAmountsLen
),
BigGroupCte AS (
select CustomerName, MonthNo
from BigAmountCte
group by CustomerName, MonthNo
having count(1) = #BigAmountsLen
)
select S.*, B.*
from SmallGroupCte S
join BigGroupCte B on B.CustomerName = S.CustomerName
where B.MonthNo = S.GroupMonthNo + #SmallAmountsLen + 1
Here is a simpler query to get the result, what I wanted:
WITH CTE
AS
(
SELECT * FROM
(
SELECT DENSE_RANK() OVER (PARTITION BY Name ORDER BY DATEPART(MONTH,TransactionDate)) AS SrNo,
Name,Amount,DatePart(Month,TransactionDate) AS MonthNo,TransactionDate FROM TransactionTable) AS A
WHERE
(Amount <= 5000 AND SrNo < 7) OR (Amount = 20000 AND SrNo = 7)
)
SELECT A.Name AS Account_Number,A.Transaction_Date FROM
(
SELECT Name,Amount,COUNT(SrNo) As Sr,MAX(TransactionDate) AS Transaction_Date FROM CTE
WHERE SrNo = 7 AND Amount = 20000
GROUP BY Name,Amount
HAVING COUNT(srNo) = 3) AS A
INNER JOIN
(
SELECT Name,COUNT(SrNo) AS Ss FROM CTE
WHERE SrNo < 7 AND Amount <= 5000
GROUP BY Name
HAVING COUNT(srNo) = 6) AS B
ON A.Name = B.Name