Calculating weekly Hires, Rehires, and Terminations from monthly snapshot - sql

We have a table that contains a snapshot of every employees data at the end of each month until the month they leave the company. This table also has the snapshot of each employee for the current day which is replaced each day until the end of the month.
What we're trying to do is select weekly statistics for Hires, Rehires, and Terms for each department. However since we only capture data by month and not by week, I'm having trouble breaking this down by week without getting duplicates.
I'm able to pull monthly statistics similar to this. Is there a method to group by each week in a month if there is only an entry for a month?
select
Max(AsOfDate) as AsOfDate,
Sector,
Department,
sum(case
when DatePart(Year, TermDate) = DatePart(Year, AsOfDate) and DatePart(Month, TermDate) = DatePart(Month, AsOfDate) then 1
else 0
end) as Terms,
sum(case
when DatePart(Year, HireDate) = DatePart(Year, AsOfDate) and DatePart(Month, HireDate) = DatePart(Month, AsOfDate) then 1
else 0
end) as Hires,
sum(case
when DatePart(Year, RehireDate) = DatePart(Year, AsOfDate) and DatePart(Month, RehireDate) = DatePart(Month, AsOfDate) then 1
else 0
end) as Rehires
from Employee_History
group by Year(AsOfDate), datepart(Month, AsOfDate), Department
Example data if today was 2022-03-17
AsOfDate
EmployeeID
Department
Title
HireDate
RehireDate
TermDate
2022-01-31
EMP22
HR
Admin
2021-01-12
null
2022-01-17
2022-01-31
EMP45
IT
Programmer
2022-01-10
null
null
2022-02-28
EMP45
IT
Programmer
2022-01-10
null
null
2022-03-17
EMP45
IT
Programmer
2022-01-10
null
null
2022-01-31
EMP03
IT
Manager
2018-08-17
2022-01-24
null
2022-02-28
EMP03
IT
Manager
2018-08-17
2022-01-24
null
2022-03-17
EMP03
IT
Manager
2018-08-17
2022-01-24
null
Desired output for January 2022 for example
AsOfDate
Department
Hires
Rehires
Terms
2022-01-01
HR
0
0
0
2022-01-08
HR
0
0
0
2022-01-15
HR
0
0
0
2022-01-22
HR
0
0
1
2022-01-29
HR
0
0
0
2022-01-01
IT
0
0
0
2022-01-08
IT
0
0
0
2022-01-15
IT
1
0
0
2022-01-22
IT
0
0
0
2022-01-29
IT
0
1
0

What you need is a mapping table for week <-> end of the Month thing containing:
create table weekmap(asOfDate DATE PRIMARY KEY, weekDayStart DATE, weekDayEnd DATE)
One problem is that your snapshot table contains "current date" if month isn't finished. I would advice to change that so it always has end of month to simplify stuff. Alternatively, create new column for that.
Populate it with whatever logic your weeks should be, some use ISO WEEK, some use day from start of new year etc.
Then you join your snapshot against this table (and you need to handle case where asOfDate isn't end of the month):
select w.asOfDate, w.weekDayStart, t.Department
, SUM(case when HireDate between weekdaystart and weekdayend then 1 else 0 end) AS hires
, SUM(case when ReHireDate between weekdaystart and weekdayend then 1 else 0 end) AS rehires
, SUM(case when TermDate between weekdaystart and weekdayend then 1 else 0 end) AS term
from snapshottable t
inner join weekmap w
ON w.asOfDate = t.asOfDateFixedEndOfMonth
group by w.asOfDate, w.weekDayStart, t.Department
There will be some loss of data if a guy is hired and fired twice in one month, but then you probably have a bigger problem

Related

day of week function for week day aggregates

I currently have a query that reads from a table and aggregates based on category. It gives me what I need but I"m trying to add another column that looks at all records for that category/employee combo for the days of this past week. SO if the job with this query runs on Wednesday Night, it needs to get a total of all category/employee records for Monday and Tuesday Night as well.
The query:
SELECT employee,
sum(case when category = 'Shoes' and date_of_report >= current_date - 1 days then daily_total else 0 end) as Shoes_DAILY,
sum(case when category = 'Shoes' and date_of_report >= ( current date - ( dayofweek(current date) - 1 ) days ) then sum(daily_total) else 0 end) as dailyTotalWeek
from shoeTotals
where date_of_report >= current_date
group by employee;
So the third column there is what's messing me up saying function use not valid. here's what I want:
The source table has these records for this past week:
employee | daily_total | date_of_report
--------------------------------------------------
123 14 2019-08-26
123 1 2019-08-27
123 56 2019-08-28
123 6 2019-08-29
123 8 2019-08-30 * today
My desired output would get (based on employee and category) the total for today (8) and then the sum of all the employees' records for that category on each preceding weekday. Running on Monday night would only count that days records, friday night would count monday through friday's as shown above.
employee | shoes_daily | dailyTotalWeek
--------------------------------------------------
123 8 85
What am I doing wrong with the dayofweek function?
You cannot nest aggregation functions. I think you simply want:
select employee,
sum(case when category = 'Shoes' and date_of_report >= current_date - 1 days
then daily_total else 0
end) as Shoes_DAILY,
sum(case when category = 'Shoes' and date_of_report >= ( current date - ( dayofweek(current date) - 1 ) days )
then daily_total else 0
end) as dailyTotalWeek
from shoeTotals
where date_of_report >= current date - ( dayofweek(current date) - 1 ) days
group by employee;

DB2/SQL aggregates with preceeding weekdays

I have a query that currently gets daily records against a weekly number from a prepopulated table:
SELECT Employee,
sum(case when category = 'Shirts' then daily_total else 0 end) as Shirts_DAILY,
sum(case when category = 'Shirts' then weekly_quota else 0 end) as Shirts_QUOTA, -- this is a static column, this number is the same for every record
sum(case when category = 'Shoes' then daily_total else 0 end) as Shoes_DAILY,
sum(case when category = 'Shoes' then weekly_quota else 0 end) as Shoes_QUOTA, -- this is a static column, this number is the same for every record
CURRENT_DATE as DATE_OF_REPORT
from SalesNumbers
where date_of_report >= current_date
group by Employee;
This runs in a script nightly and returns records like this:
Employee | shirts_DAILY | shirts_QUOTA | Shoes_DAILY | Shoes_QUOTA | DATE_OF_REPORT
--------------------------------------------------------------------------------------------------------
123 15 75 14 85 2019-08-30
That's the record from last Friday Night's report. I'm trying to figure out a way to add a column for each category that would take the sum of daily totals (shirts_DAILY, shoes_DAILY) for each category on preceding weekdays (running sunday through saturday as a week) and divide by that category's quota (shirts_QUOTA, shoes_QUOTA).
For example, here are records from sunday through thursday
Employee | shirts_DAILY | shirts_QUOTA | Shoes_DAILY | Shoes_QUOTA | DATE_OF_REPORT
--------------------------------------------------------------------------------------------------------
123 15 75 16 85 2019-08-25
123 4 75 2 85 2019-08-26
123 8 75 6 85 2019-08-27
123 2 75 8 85 2019-08-28
123 15 75 14 85 2019-08-29
With my new change, I would want Friday night's record to take the sum of sunday through thursday's daily records and divide by the quota (including friday's daily in the sum)
Friday night's record with new column:
Employee | shirts_DAILY | shirts_QUOTA | shirtsPercent | Shoes_DAILY | Shoes_QUOTA | shoesPercent | DATE_OF_REPORT
-----------------------------------------------------------------------------------------------------------------------------------------------
123 2 75 61.3 7 85 62.4 2019-08-30
So friday's run added 15,4,8,2,15,2 for the shirts for 46/75 and 7,14,8,6,2,16 for shoes for 53/85. So the daily sum of each for the preceding week, including present day daily totals, if that makes sense.
What is the best way for me to achieve this?
SELECT Employee,
sum(case when category = 'Shirts' and date_of_report >= current date then
daily_total else 0 end) as Shirts_DAILY,
sum(case when category = 'Shirts' and date_of_report >= current date then
weekly_quota else 0 end) as Shirts_QUOTA,
( sum(case when category = 'Shirts' then
daily_total else 0 end) * 100 ) /
( sum(case when category = 'Shirts' and date_of_report >= current date then
weekly_quota else 0 end) ) as Shirts_PERCENT,
CURRENT_DATE as DATE_OF_REPORT
from SalesNumbers
where date_of_report >= ( current date - ( dayofweek(current date) - 1 ) days )
group by Employee

How to achieve the bucket values in SQL?

I have schedule table like this (table name = testSch)
ID Amount scheduleDate
1 7230.00 2018-07-13
1 7272.00 2018-07-27
1 7314.00 2018-08-10
1 7356.00 2018-08-24
1 7398.00 2018-09-07
1 7441.00 2018-09-21
1 7439.00 2018-10-08
1 7526.00 2018-10-22
1 7570.00 2018-11-05
1 7613.00 2018-11-19
1 5756.00 2018-12-03
I need to sum the Amount field based on specific bucket values as shown below
Principal_7To30_Days
Principal_1To3_Months
Principal_3To6_Months
Principal_6To12_Months
Principal_1To3_Years
by giving an input date
And my input date is 2018-07-09 and below is my query;
;with cteSchedule as (
select *,DATEDIFF(DAY,'20180709',scheduleDate) as datedifference,
DATEDIFF(MONTH,'20180709',scheduleDate) as monthdifference from testSch)
select ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference <7),0) AS Principal_0To7_Days,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=7 and cteSchedule.datedifference<30),0)
AS Principal_7To30_Days,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=30 and cteSchedule.datedifference<90),0) AS Principal_1To3_Months,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=90 and cteSchedule.datedifference<180),0) AS Principal_3To6_Months,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=180 and cteSchedule.datedifference<365),0) AS Principal_6To12_Months
And below is my output
Principal_0To7_Days Principal_7To30_Days Principal_1To3_Months Principal_3To6_Months Principal_6To12_Months
7230.00 7272.00 29509.00 35904.00 0.00
But the correct output should be
Principal_0To7_Days Principal_7To30_Days Principal_1To3_Months Principal_3To6_Months Principal_6To12_Months
7230.00 7272.00 36948.00 28465.00 0.00
So the problem is i'm getting wrong values for Principal_1To3_Months and Principal_3To6_Months, When I asked my client how do they calculate this in their legacy system, they replied that they calculate using +-months by adding number of months and not days. So if today is 2018-07-09 + 3 months we will get 2018-10-09.
So I used the month difference in my cte query as below
DATEDIFF(MONTH,'20180709',scheduleDate) as monthdifference
And use this in my overall query as below
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.monthdifference>=1 and cteSchedule.monthdifference<=3),0) AS Principal_1To3_Months
But this time also I get the same values as mentioned in my very first output.
Can someone please point out where is my mistake and how to achieve this values as mentioned in correct output
I wouldn't use DATEDIFF to calculation day or month difference days, because there is some month have 31 days, others month have 30 days.
Therefore, the calculated difference days are not accurate.
I would use DATEADD instead of DATEDIFF to do the condition.
;with cteSchedule as (
select *,'20180709' compareDay
from testSch
)
SELECT Sum(CASE
WHEN t.scheduleDate < DATEADD(day, 7, compareDay)
THEN t.amount
ELSE 0
END) AS Principal_0To7_Days,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(day, 7, compareDay) AND t.scheduleDate < DATEADD(day, 30, compareDay)
THEN t.amount
ELSE 0
END) AS Principal_7To30_Days,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(month,1,compareDay) AND t.scheduleDate < DATEADD(month,3,compareDay)
THEN t.amount
ELSE 0
END) AS Principal_1To3_Months,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(month,3,compareDay) AND t.scheduleDate < DATEADD(month,6,compareDay)
THEN t.amount
ELSE 0
END) AS Principal_3To6_Months,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(month,6,compareDay) AND t.scheduleDate < DATEADD(month,12,compareDay)
THEN t.amount
ELSE 0
END) AS Principal_6To12_Months
from cteSchedule t
SQLFiddle
[Results]:
| Principal_0To7_Days | Principal_7To30_Days | Principal_1To3_Months | Principal_3To6_Months | Principal_6To12_Months |
|---------------------|----------------------|-----------------------|-----------------------|------------------------|
| 7230 | 7272 | 36948 | 28465 | 0 |
Note
You can use CASE WHEN with SUM
Aggregate function instead of select subquery, the performance will be better.

SQL update with hard-coded column names

I need help creating a SQL update for the following example (with sample data):
Table 1: DETAILTRAN
Structure: VENDOR CHAR(10), EMPLOYEE CHAR(10), WEEK INT
VENDOR EMPLOYEE WEEK
VEN01 EMP01 1
VEN01 EMP01 1
VEN02 EMP03 1
VEN03 EMP02 1
VEN01 EMP01 2
VEN01 EMP01 2
VEN01 EMP03 2
VEN03 EMP02 2
VEN02 EMP01 3
VEN02 EMP01 3
VEN02 EMP03 3
VEN03 EMP03 3
Table 2: SUMMARTRAN (that needs to be updated)
Structure: WEEK01 INT, WEEK02 INT, WEEK03 INT, VENDOR CHAR(10), EMPLOYEE CHAR(10)
The results of SQL Update this table (SUMMARTRAN) should look like this:
WEEK01 WEEK02 WEEK03 VENDOR EMPLOYEE
2 3 0 VEN01
1 0 3 VEN02
1 1 1 VEN03
2 2 2 EMP01
1 1 0 EMP02
1 1 2 EMP03
You can approach this using grouping sets and conditional aggregation:
select vendor, employee,
sum(case when week = 1 then 1 else 0 end) as week01,
sum(case when week = 2 then 1 else 0 end) as week02,
sum(case when week = 3 then 1 else 0 end) as week03
from DETAILTRAN
group by grouping sets ((vendor), (employee));
You can incorporate this into an insert, statement for summartran:
insert into summertran(vendor, employee, week01, week02, week03)
select vendor, employee,
sum(case when week = 1 then 1 else 0 end) as week01,
sum(case when week = 2 then 1 else 0 end) as week02,
sum(case when week = 3 then 1 else 0 end) as week03
from DETAILTRAN
group by grouping sets ((vendor), (employee));
Try this
with VENDOR as
(
select isnull(count(case when WEEK = 1 then WEEK end),0) WEEK01,
isnull(count(case when WEEK = 2 then WEEK end),0) WEEK02 ,
isnull(count(case when WEEK = 3 then WEEK end),0) WEEK03 ,
VENDOR
from DETAILTRAN
group by VENDOR
),EMPLOYEE as
(
select isnull(count(case when WEEK = 1 then WEEK end),0) WEEK01,
isnull(count(case when WEEK = 2 then WEEK end),0) WEEK02 ,
isnull(count(case when WEEK = 3 then WEEK end),0) WEEK03 ,
EMPLOYEE
from DETAILTRAN
group by EMPLOYEE
)
select WEEK01, WEEK02, WEEK03, VENDOR, EMPLOYEE = '' from VENDOR
union all
select WEEK01, WEEK02, WEEK03, VENDOR='', EMPLOYEE from EMPLOYEE
SQL FIDDLE DEMO
To update the result to SUMMARTRAN table use this
with VENDOR as
(
select isnull(count(case when WEEK = 1 then WEEK end),0) WEEK01,
isnull(count(case when WEEK = 2 then WEEK end),0) WEEK02 ,
isnull(count(case when WEEK = 3 then WEEK end),0) WEEK03 ,
VENDOR
from DETAILTRAN
group by VENDOR
),EMPLOYEE as
(
select isnull(count(case when WEEK = 1 then WEEK end),0) WEEK01,
isnull(count(case when WEEK = 2 then WEEK end),0) WEEK02 ,
isnull(count(case when WEEK = 3 then WEEK end),0) WEEK03 ,
EMPLOYEE
from DETAILTRAN
group by EMPLOYEE
)
insert into SUMMARTRAN (WEEK01, WEEK02, WEEK03, VENDOR, EMPLOYEE)
select WEEK01, WEEK02, WEEK03, VENDOR, EMPLOYEE = '' from VENDOR
union all
select WEEK01, WEEK02, WEEK03, VENDOR='', EMPLOYEE from EMPLOYEE

SQL - How to count records for each status in one line per day?

I have a table Sales
Sales
--------
id
FormUpdated
TrackingStatus
There are several status e.g. Complete, Incomplete, SaveforLater, ViewRates etc.
I want to have my results in this form for the last 8 days(including today).
Expected Result:
Date Part of FormUpdated, Day of Week, Counts of ViewRates, Counts of Sales(complete), Counts of SaveForLater
--------------------------------------
2015-05-19 Tuesday 3 1 21
2015-05-18 Monday 12 5 10
2015-05-17 Sunday 6 1 8
2015-05-16 Saturday 5 3 7
2015-05-15 Friday 67 5 32
2015-05-14 Thursday 17 0 5
2015-05-13 Wednesday 22 0 9
2015-05-12 Tuesday 19 2 6
Here is my sql query:
select datename(dw, FormUpdated), count(ID), TrackingStatus
from Sales
where FormUpdated <= GETDATE()
AND FormUpdated >= GetDate() - 8
group by datename(dw, FormUpdated), TrackingStatus
order by datename(dw, FormUpdated) desc
I do not know how to make the next step.
Update
I forgot to mention, I only need the Date part of the FormUpdated, not all parts.
You can use SUM(CASE WHEN TrackingStatus = 'SomeTrackingStatus' THEN 1 ELSE 0 END)) to get the status count for each tracking status in individual column. Something like this. SQL Fiddle
select
CONVERT(DATE,FormUpdated) FormUpdated,
DATENAME(dw, CONVERT(DATE,FormUpdated)),
SUM(CASE WHEN TrackingStatus = 'ViewRates' THEN 1 ELSE 0 END) c_ViewRates,
SUM(CASE WHEN TrackingStatus = 'Complete' THEN 1 ELSE 0 END) c_Complete,
SUM(CASE WHEN TrackingStatus = 'SaveforLater' THEN 1 ELSE 0 END) c_SaveforLater
from Sales
where FormUpdated <= GETDATE()
AND FormUpdated >= DATEADD(D,-8,GetDate())
group by CONVERT(DATE,FormUpdated)
order by CONVERT(DATE,FormUpdated) desc
You can also use a PIVOT to achieve this result - you'll just need to complete the list of TrackingStatus names in both the SELECT and the FOR, and no GROUP BY required:
WITH DatesOnly AS
(
SELECT Id, CAST(FormUpdated AS DATE) AS DateOnly, DATENAME(dw, FormUpdated) AS DayOfWeek, TrackingStatus
FROM Sales
)
SELECT DateOnly, DayOfWeek,
-- List of Pivoted Columns
[Complete],[Incomplete], [ViewRates], [SaveforLater]
FROM DatesOnly
PIVOT
(
COUNT(Id)
-- List of Pivoted columns
FOR TrackingStatus IN([Complete],[Incomplete], [ViewRates], [SaveforLater])
) pvt
WHERE DateOnly <= GETDATE() AND DateOnly >= GetDate() - 8
ORDER BY DateOnly DESC
SqlFiddle
Also, I think your ORDER BY is wrong - it should just be the Date, not day of week.