SQL Server: finding change between selected month and previous month's sales - sql

I have the following table for example. I would like to calculate the changes increase or decrease from previous month. This will show a percentage of change from previous month.
Location Month Sales
A Jan 1753
B Jan 32130
C Jan 71353
D Jan 74885
E Jan 50241
F Jan 66393
A Feb 80633
B Feb 67918
C Feb 73330
D Feb 33269
E Feb 78915
F Feb 98817
A Mar 80633
B Mar 67918
C Mar 73330
D Mar 33269
E Mar 78915
F Mar 98817
I wan to create a table like following. I searched stack overflow but was not able to get table.
Location Selected Current_Month Prvisous_Month Change
A Feb 80633 1753 4500%
B Feb 67918 32130 111%
C Feb 73330 71353 3%
D Feb 33269 74885 -56%
E Feb 78915 50241 57%
F Feb 98817 66393 49%

If you can't change the datatype of the "Month" column, for whatever reason, then this solution may work
DECLARE #Table TABLE ([Location] CHAR(1), [Month] NVARCHAR(3), Sales INT )
INSERT INTO #Table
([Location], [Month], Sales)
VALUES
('A',N'Jan',1753),
('B',N'Jan',32130),
('C',N'Jan',71353),
('D',N'Jan',74885),
('E',N'Jan',50241),
('F',N'Jan',66393),
('A',N'Feb',80633),
('B',N'Feb',67918),
('C',N'Feb',73330),
('D',N'Feb',33269),
('E',N'Feb',78915),
('F',N'Feb',98817),
('A',N'Mar',80633),
('B',N'Mar',67918),
('C',N'Mar',73330),
('D',N'Mar',33269),
('E',N'Mar',78915),
('F',N'Mar',98817)
DECLARE #Selection NVARCHAR(3) = N'Feb' -- Enter Selected Month here
;WITH cteX
AS(
SELECT
T.[Location]
, T.[Month]
, MonthNum = MONTH([T].[Month] + ' 1 1900') --Use some dummy date here
, T.Sales
FROM #Table T
)
SELECT
T.[Location]
, Selected = T.Month
, CurrentMonth = T.Sales
, PreviousMonth = T1.Sales
, Change = CAST((T.Sales - T1.Sales) / (T1.Sales * 1.0) * 100.0 AS DECIMAL)
FROM cteX T
INNER JOIN
cteX T1 ON T1.MonthNum = T.MonthNum - 1
AND T1.[Location] = T.[Location]
WHERE
T.[Month] = #Selection
Output

Something like this should be a good start. As Cool_Br33ze noted, you should rethink the date column of this table.
SELECT
*,
referenceTimePeriod / NULLIF(comparisonTimePeriod, 0) -- avoid DIV0 errors
FROM (
SELECT Location, Sales
FROM myTable
WHERE month = 'jan'
) AS referenceTimePeriod
FULL JOIN(
SELECT Location, Sales
FROM myTable
WHERE month = 'feb'
) AS comparisonTimePeriod ON referenceTimePeriod.Location = comparisonTimePeriod .Location

SELECT A.Location, 'Feb' AS Selected, A.Sales AS Current_Month
, B.Sales AS Prvisous_Month, (A.Sales - B.Sales)/ B.Sales AS Change
FROM YourTable A JOIN YourTable B
ON A.Month = B.Month + 1 -- You will have to represent Months by numbers
WHERE A.Month = 2 -- Selected month

Assuming that you change the Month attribute to date then you can use a LAG window function easily like this
SELECT location,
month,
sales,
lag(sales) over (order by month) previous,
(sales/lag(sales) over (order by month) - 1) * 100 as change
FROM your_table
WHERE month = 'feb'
The major issue now in your task is the correct ordering of Month which would be much more easier with date, or numbers.
EDIT: You can use the ordering solution of Cool_Br33ze for current data:
SELECT location,
month,
sales,
lag(sales) over (order by MONTH([T].[Month] + ' 1 1900')) previous, -- taken from Cool_Br33ze solution
(sales/lag(sales) over (order by MONTH([T].[Month] + ' 1 1900')) - 1) * 100 as change
FROM your_table
WHERE month = 'feb'
However, the best option is to change the data type of Month ...

Related

Calculating YoY revenue when not all accounts existed last year

I am trying to calculate YoY revenue in Snowflake. I have 3 columns: account_id, activity_date, and arr. I've tried using a CTE to calculate, but the problem I have is that the account id's from today, might not all be the same from a year ago. I am trying to calculate this for each and every day and account. Here's what I have:
WITH CTE AS(SELECT account_id, account_name, activity_date, arr
FROM arr_base)
SELECT c1.*, c2.arr AS yoy_arr
FROM CTE c1
LEFT JOIN CTE c2 ON c1.account_id = c2.account_id AND c2.activity_date = dateadd(year, -1, c1.activity_date)
This approach ends up excluding some records, because not all accounts match. So the yoy_arr value is smaller than it should be. Any suggestions?
Edited to add sample data, current results and desired results:
Sample Data:
Account_ID
Activity_Date
ARR
A
Jan. 31, 2021
50
B
Jan. 31, 2021
40
A
Jan. 31, 2020
40
B
Jan. 31, 2020
35
C
Jan. 31, 2020
30
D
Jan. 31, 2020
30
Current Results:
Account_ID
Activity_Date
ARR
YOY_ARR
A
Jan. 31, 2021
50
40
B
Jan. 31, 2021
40
35
Desired Results:
Account_ID
Activity_Date
ARR
YOY_ARR
A
Jan. 31, 2021
50
40
B
Jan. 31, 2021
40
35
NULL
Jan. 31, 2021
NULL
60
This is an common Business Intelligence Challenge:
The solution to prevent having to do these calculations is usually to create a fact table.
In the fact table, one would persist every date for every account and be able to do quick yoy comparisons and there would be no missing values to content with.
you table would have records like
accountid, year, arr
C, 2019, 0
C, 2020, 30
C, 2021, 0
To give you a direct solution vs recommended strategy see the code here
create or replace table arr_base(account_id varchar, account_name varchar, activity_date date, arr number(32,4));
insert into arr_base (account_id, account_name, activity_date, arr)
values
('A','ACCOUNT A','2021-01-31',50)
,('B','ACCOUNT B','2021-01-31',40)
,('A','ACCOUNT A','2020-01-31',40)
,('B','ACCOUNT B','2020-01-31', 35)
,('C','ACCOUNT C','2020-01-31', 30)
,('D','ACCOUNT D','2020-01-31', 30)
;
set start_date = '2018-01-01'::timestamp_ltz;
set end_date = current_date();
set years = (select datediff(years,$start_date, $end_date) +1 );
with cte_accounts as (select distinct account_id, account_name from arr_base where activity_date between $start_date and $end_date )
,cte_dates as (select distinct year(activity_Date) as activity_year, activity_Date from arr_base where activity_date between $start_date and $end_date)
,cte_years as ( select seq4() + year($start_date) as txn_year from table(generator(rowcount=> $years)))
,cte_arr as (select account_id, account_name, year(activity_date) as activity_year, SUM( arr) as arr from arr_base where activity_date between $start_date and $end_date group by account_id, account_name, year(activity_date))
-- cartesian product
select
dim.activity_year
,dim.account_id
,dim.account_name
,f1.arr as arr_current_year
,f2.arr as arr_next_year
,case when f2.arr is not null and f1.arr is not null then ((f2.arr - f1.arr) / f1.arr) else null end as yoy_arr
from
(
select
a.account_id
,a.account_name
,d.activity_year
from
cte_accounts a cross join
cte_dates d
) as dim left outer join
cte_arr as f1 on dim.account_id = f1.account_id and dim.activity_year = f1.activity_year left outer join
cte_arr as f2 on dim.account_id = f2.account_id and (dim.activity_year+1) = f2.activity_year
order by dim.activity_year
,dim.account_id
;
See Results Below:
https://gist.github.com/umjohndacosta/784d5fa7a41a5e50066d925c92696c22
Alternative approach adding onto John's excellent answer:-)
select
*
from
(select
account_name
, year(ACTIVITY_DATE) year_arr
, to_char(ACTIVITY_DATE,'DD-MON') arr_date,arr
from arr_base )
pivot
( sum(arr) for year_arr in ('2021','2020' ))
create or replace table arr_base(account_id varchar, account_name varchar, activity_date date, arr number(32,4));
insert into arr_base (account_id, account_name, activity_date, arr)
values
('A','ACCOUNT A','2021-01-31',50)
,('B','ACCOUNT B','2021-01-31',40)
,('A','ACCOUNT A','2020-01-31',40)
,('B','ACCOUNT B','2020-01-31', 35)
,('C','ACCOUNT C','2020-01-31', 30)
,('D','ACCOUNT D','2020-01-31', 30)
;
select p.* from (select account_name, year(ACTIVITY_DATE) year_arr , to_char(ACTIVITY_DATE,'DD-MON') arr_date,arr from arr_base )
pivot(sum(arr) for year_arr in ('2021', '2020' )) as p

Calculate YTD, Previous YTD in the same query

How could I calculate to calculate YTD and prior YTD in the same query?
I try to calculate weight and weight ytd. I didn't undestand how to add the calculation of prior ytd in the same query.
YTD: Sum Weight from the first of January to today
prior YTD: Sum
Weight from the first of January of last year to today minus 1 year
SELECT SUM(CASE
WHEN convert(date,s.Delivery_Year+'-'+ s.Delivery_month+'-'+ s.Delivery_day) BETWEEN dateadd(yy, DATEDIFF(yy, 0, GETDATE()), 0) AND GETDATE() THEN s.Weight
ELSE 0
END) AS WeightYTD ,
sum(Weight) AS weight ,
[Sales_Organization] ,
[Market_Grp] ,
[Delivery_Year] ,
[Delivery_month] ,
Delivery_day
FROM Fact_sales s
GROUP BY ,
[Sales_Organization] ,
[Market_Grp] ,
[Delivery_Year] ,
[Delivery_month] ,
Delivery_day
It's still quite unclear as to what results/behaviour you actually want.
One possible interpretation is that the "year to date" is relative to the date in the table, and not relative to "today" / GETDATE().
So...
for 14th March 2018 : YTD = 1st Jan 2018 to 14th March 2018
for 20th March 2019 : YTD = 1st Jan 2019 to 20th March 2019
That being the case, I would do the following to get the YTD column...
WITH
grouped_by_date AS
(
SELECT
[Sales_Organization],
[Market_Grp],
[Delivery_Year],
[Delivery_Month],
[Delivery_Day],
SUM([Weight]) AS Weight
FROM
Fact_sales s
GROUP BY
[Sales_Organization],
[Market_Grp],
[Delivery_Year],
[Delivery_Month],
[Delivery_Day]
),
cumulative_sum_for_ytd AS
(
SELECT
*,
SUM([Weight]) OVER (PARTITION BY [Delivery_Year]
ORDER BY [Delivery_Month], [Delivery_Day]
)
AS Weight_YTD
FROM
grouped_by_date
),
There isn't a LAG() function in SQL Server 2008, but there are "hacky tricks" that can simulate it...
hack_to_do_lag AS
(
SELECT
*,
CASE
WHEN [Delivery_Year]%2=1
THEN MAX(CASE WHEN [Delivery_Year]%2=0 THEN [Weight_YTD] END) OVER (PARTITION BY ([Delivery_Year]+0)/2)
ELSE MAX(CASE WHEN [Delivery_Year]%2=1 THEN [Weight_YTD] END) OVER (PARTITION BY ([Delivery_Year]+1)/2)
END
AS Weight_PreviousYTD
FROM
cumulative_sum_for_ytd
)
SELECT
*
FROM
hack_to_do_lag

Count number of days each employee take vacation in a month SQL Server

I have this table:
Vacationtbl:
ID Start End
-------------------------
01 04/10/17 04/12/17
01 04/27/17 05/02/17
02 04/13/17 04/15/17
02 04/17/17 04/20/17
03 06/14/17 06/22/17
Employeetbl:
ID Fname Lname
------------------
01 John AAA
02 Jeny BBB
03 Jeby CCC
I like to count the number of days each employee take vacation in April.
My query:
SELECT
SUM(DATEDIFF(DAY, Start, End) + 1) AS Days
FROM
Vacationtbl
GROUP BY
ID
01 returns 9 (not correct)
02 returns 7 (correct)
How do I fix the query so that it counts until the end of month and stops at end of month. For example, April has 30 days. On second row, Employee 01 should counts 4/27/17 until 4/30/17. And 05/02/17 is for May.
Thanks
The Tally/Calendar table is the way to go. However, you can use an ad-hoc tally table.
Example
Select Year = Year(D)
,Month = Month(D)
,ID
,Days = count(*)
From Vacationtbl A
Cross Apply (
Select Top (DateDiff(DAY,[Start],[End])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),[Start])
From master..spt_values
) B
-- YOUR OPTIONAL WHERE STATEMENT HERE --
Group By ID,Year(D),Month(D)
Order By 1,2,3
Returns
Year Month ID Days
2017 4 01 7
2017 4 02 7
2017 5 01 2
EDIT - To Show All ID even if Zero Days
Select ID
,Year = Year(D)
,Month = Month(D)
,Days = sum(case when D between [Start] and [End] then 1 else 0 end)
From (
Select Top (DateDiff(DAY,'05/01/2017','05/31/2017')+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),'05/01/2017')
From master..spt_values
) D
Cross Join Vacationtbl B
Group By ID,Year(D),Month(D)
Order By 1,2,3
Returns
ID Year Month Days
1 2017 5 2
2 2017 5 0
dbFiddle if it Helps
EDIT - 2 Corrects for Overlaps (Gaps and Islands)
--Create Some Sample Data
----------------------------------------------------------------------
Declare #Vacationtbl Table ([ID] varchar(50),[Start] date,[End] date)
Insert Into #Vacationtbl Values
(01,'04/10/17','04/12/17')
,(01,'04/27/17','05/02/17')
,(02,'04/13/17','04/15/17')
,(02,'04/17/17','04/20/17')
,(02,'04/16/17','04/17/17') -- << Overlap
,(03,'05/16/17','05/17/17')
-- The Actual Query
----------------------------------------------------------------------
Select ID
,Year = Year(D)
,Month = Month(D)
,Days = sum(case when D between [Start] and [End] then 1 else 0 end)
From (Select Top (DateDiff(DAY,'04/01/2017','04/30/2017')+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),'04/01/2017') From master..spt_values ) D
Cross Join (
Select ID,[Start] = min(D),[End] = max(D)
From (
Select E.*,Grp = Dense_Rank() over (Order By D) - Row_Number() over (Partition By ID Order By D)
From (
Select Distinct A.ID,D
From #Vacationtbl A
Cross Apply (Select Top (DateDiff(DAY,A.[Start],A.[End])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),A.[Start]) From master..spt_values ) B
) E
) G
Group By ID,Grp
) B
Group By ID,Year(D),Month(D)
Order By 1,2,3
Returns
ID Year Month Days
1 2017 4 7
2 2017 4 8
3 2017 4 0
Without a dates table, you could use
select Id
,sum(case when [end]>'20170430' and [start]<'20170401' then datediff(day,'20170401','20170430')+1
when [end]>'20170430' then datediff(day,[start],'20170430')+1
when [start]<'20170401' then datediff(day,'20170401',[end])+1
else datediff(day,[start],[end])+1
end) as VacationDays
from Vacationtbl
where [start] <= '20170430' and [end] >= '20170401'
group by Id
There are 3 conditions here
Start is before this month and the end is after this month. In this case you subtract the end and start dates of the month.
End is after month end and start is in the month, in this case subtract month end date from the start.
Start is before this month but the end is in the month. In this case subtract month start date and the end date.
Edit: Based on the OP's comments that the future dates have to be included,
/*This recursive cte generates the month start and end dates with in a given time frame
For Eg: all the month start and end dates for 2017
Change the start and end period as needed*/
with dates (month_start_date,month_end_date) as
(select cast('2017-01-01' as date),cast(eomonth('2017-01-01') as date)
union all
select dateadd(month,1,month_start_date),eomonth(dateadd(month,1,month_start_date)) from dates
where month_start_date < '2017-12-01'
)
--End recursive cte
--Query logic is the same as above
select v.Id
,year(d.month_start_date) as yr,month(d.month_start_date) as mth
,sum(case when v.[end]>d.month_end_date and v.[start]<d.month_start_date then datediff(day,d.month_start_date,d.month_end_date)+1
when v.[end]>d.month_end_date then datediff(day,v.[start],d.month_end_date)+1
when v.[start]<d.month_start_date then datediff(day,d.month_start_date,v.[end])+1
else datediff(day,v.[start],v.[end])+1
end) as VacationDays
from dates d
join Vacationtbl v on v.[start] <= d.month_end_date and v.[end] >= d.month_start_date
group by v.id,year(d.month_start_date),month(d.month_start_date)
Assuming you want only one month and you want to count all days, you can do this with arithmetic. A separate calendar table is not necessary. The advantage is performance.
I think this would be easier if SQL Server supported least() and greatest(), but case will do:
select id,
sum(1 + datediff(day, news, newe)) as vacation_days_april
from vactiontbl v cross apply
(values (case when [start] < '2017-04-01' then cast('2017-04-01' as date) else [start] end),
(case when [end] >= '2017-05-01' then cast('2017-04-30' as date) else [end] end)
) v(news, newe)
where news <= newe
group by id;
You can readily extend this to any month:
with m as (
select cast('2017-04-01' as date) as month_start,
cast('2017-04-30' as date) as month_end
)
select id,
sum(1 + datediff(day, news, newe)) as vacation_days_aprile
from m cross join
vactiontbl v cross apply
(values (case when [start] < m.month_start then m.month_start else [start] end),
(case when [end] >= m.month_end then m.month_end else [end] end)
) v(news, newe)
where news <= newe
group by id;
You can even use a similar idea to extend to multiple months, with a different row for each user and each month.
You can use a Calendar or dates table for this sort of thing.
For only 152kb in memory, you can have 30 years of dates in a table with this:
/* dates table */
declare #fromdate date = '20000101';
declare #years int = 30;
/* 30 years, 19 used data pages ~152kb in memory, ~264kb on disk */
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
select top (datediff(day, #fromdate,dateadd(year,#years,#fromdate)))
[Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,#fromdate))
into dbo.Dates
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by [Date];
create unique clustered index ix_dbo_Dates_date
on dbo.Dates([Date]);
Without taking the actual step of creating a table, you can use it inside a common table expression with just this:
declare #fromdate date = '20170401';
declare #thrudate date = '20170430';
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top (datediff(day, #fromdate, #thrudate)+1)
[Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,#fromdate))
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by [Date]
)
select [Date]
from dates;
Use either like so:
select
v.Id
, count(*) as VacationDays
from Vacationtbl v
inner join Dates d
on d.Date >= v.[Start]
and d.Date <= v.[End]
where d.Date >= '20170401'
and d.Date <= '20170430'
group by v.Id
rextester demo (table): http://rextester.com/PLW73242
rextester demo (cte): http://rextester.com/BCY62752
returns:
+----+--------------+
| Id | VacationDays |
+----+--------------+
| 01 | 7 |
| 02 | 7 |
+----+--------------+
Number and Calendar table reference:
Generate a set or sequence without loops - 2 - Aaron Bertrand
The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
Creating a Date Table/Dimension in sql Server 2008 - David Stein
Calendar Tables - Why You Need One - David Stein
Creating a date dimension or calendar table in sql Server - Aaron Bertrand
Try this,
declare #Vacationtbl table(ID int,Startdate date,Enddate date)
insert into #Vacationtbl VALUES
(1 ,'04/10/17','04/12/17')
,(1 ,'04/27/17','05/02/17')
,(2 ,'04/13/17','04/15/17')
,(2 ,'04/17/17','04/20/17')
-- somehow convert your input into first day of month
Declare #firstDayofGivenMonth date='2017-04-01'
Declare #LasttDayofGivenMonth date=dateadd(day,-1,dateadd(month,datediff(month,0,#firstDayofGivenMonth)+1,0))
;with CTE as
(
select *
,case when Startdate<#firstDayofGivenMonth then #firstDayofGivenMonth else Startdate end NewStDT
,case when Enddate>#LasttDayofGivenMonth then #LasttDayofGivenMonth else Enddate end NewEDT
from #Vacationtbl
)
SELECT
SUM(DATEDIFF(DAY, NewStDT, NewEDT) + 1) AS Days
FROM
CTE
GROUP BY
ID

How use the operator IN with a subquery that returns two columns

Hello masters I need your help.
Having the table:
DataCollection
==================
PK Code
smallint RestaurantCode
smallint Year
tinyint Month
money Amount
money AccumulativeMonthsAmount
...
I need the AccumulateAmount for the LastMonth on every Restaurant.
First, I get the last Month per Restaurant for the 'current year'(for this case):
SELECT RestaurantCode, MAX(Month) as Month FROM DataCollection
WHERE (Year >= 2012 AND YEAR <= 2012) GROUP BY RestaurantCode
Now I want to use that as subquery, to get the Last - AccumulativeMonthsAmount :
SELECT AccumulativeMonthsAmount FROM DataCollection
WHERE (RestaurantCode, Month)
IN (SELECT RestaurantCode, MAX(Month) as Month FROM DataCollection
WHERE (Year >= 2012 AND YEAR <= 2012) GROUP BY RestaurantCode)
But the operator IN, don't work, How I should do it?
Sample Data sorted by Year and Month:
RestCode Amount Accumulative Year Month
123 343.3 345453.65 2012 12
123 124.7 345329.00 2012 11
...
122 312.2 764545.00 2012 12
122 123.4 764233.00 2012 11
...
999 500.98 2500.98 2012 6
999 100.59 2000.00 2012 5
...
I wanna to get the Accumulative for the last month of every restaurant:
RestCode Accumulative Month
123 345453.65 12
122 764545.00 12
99 2500.98 6
...
SELECT dc.AccumulativeMonthsAmount
FROM dbo.DataCollection AS dc
INNER JOIN
(
SELECT RestaurantCode, MAX(Month)
FROM dbo.PAL_Entries_Relatives
WHERE [Year] = 2012
GROUP BY RestaurantCode
) AS r(rc, m)
ON dc.RestaurantCode = r.rc
AND dc.[Month] = r.m;
With the changed requirements:
;WITH x AS
(
SELECT RestCode, Accumulative, [Month],
rn = ROW_NUMBER() OVER (PARTITION BY RestCode ORDER BY [Month] DESC)
FROM dbo.DataCollection -- or is it dbo.PAL_Entries_Relatives?
)
SELECT RestCode, Accumulative, [Month]
FROM x
WHERE rn = 1
ORDER BY [Month] DESC, RestCode DESC;
That syntax is not allowed in SQL Server. You can do something similar with EXISTS:
SELECT AccumulativeMonthsAmount
FROM DataCollection dc
WHERE exists (select 1
from PAL_Entries_Relatives er
where (Year >= 2012 AND YEAR <= 2012)
group by RestaurantCode
having er.RestaurantCode = dc.RestaurantCode and
max(er.month) = dc.Month
)
SELECT AccumulativeMonthsAmount
FROM DataCollection
INNER JOIN PAL_Entries_Relatives
ON DataCollection.RestaurantCode = PAL_Entries_Relatives.RestaurantCode
WHERE (Year >= 2012 AND YEAR <= 2012)
GROUP BY DataCollection.RestaurantCode
HAVING AccumulativeMonthsAmount.Month = MAX(PAL_Entries_Relatives.Month)

SQL Case select

maybe someone can help me
i have an SQL datebase that is used for logging employees leave. we have some many different shifts, that on our employees table we have 7 fields that represent the days they work. these are a bit data type, 1 for working that day, and 0 for not working.
a second table has all the employees leave. containing employee id, leave date and reason.
i can easily query the employees table and get how many people are to work on any given day of the week, and i can easily query the leave table to see how many people are off on a given date.
what i looking to do is based on the day of the week in the leave table, count how many people are supposed to be in on that day.
the code im trying to make work is
select TBL_Leave.Leave_Date AS 'Date',
datepart(weekday,TBL_Leave.Leave_Date) - 1 AS 'Day Of Week',
count(TBL_Leave.Leave_Date) AS 'Total Off',
case
when datepart(weekday,TBL_Leave.Leave_Date) - 1 = 5 then select SUM(convert(int,Mon)) from TBL_Employees)
else 'Flase'
end
from TBL_Leave
where Leave_Date between '2010-01-01' AND '2010-12-31'
group by TBL_Leave.Leave_Date
but sure enough, it dont work.
im trying to count the number of people working from one table based the the day of the week from a field in another.
any help anyone can give will be great
cheers
Paul
i have this query to get how many people are off on any date
select TBL_Leave.Leave_Date AS 'Date',
datepart(weekday,TBL_Leave.Leave_Date) - 1 AS 'Day Of Week',
count(TBL_Leave.Leave_Date) AS 'Total Off'
from TBL_Leave
where Leave_Date between '2010-01-01' AND '2010-12-31'
group by TBL_Leave.Leave_Date
and this to see how many people are in on any day
select SUM(convert(int,Mon)) as 'Monday',
SUM(convert(int,Tue)) AS 'Tuesday',
SUM(convert(int,Wed)) AS 'Wednesday',
SUM(convert(int,Thu)) AS 'Thursday',
SUM(convert(int,Fri)) AS 'Friday',
SUM(convert(int,Sat)) AS 'Saturday',
SUM(convert(int,Sun)) AS 'Sunday'
from TBL_Employees
where planned = 1
IMHO you should set a view in place as a helper for queries like these:
create view V_EmployeeWorkingDays as
select EmployeeID,
case ShortDayName
when 'Mon' then 1 when 'Tue' then 2 when 'Wed' then 3
when 'Thu' then 4 when 'Fri' then 5 when 'Sat' then 6
when 'Sun' then 7 end as weekday,
IsWorking
from TBL_Employees
unpivot (IstWorking for ShortDayName in (Mon,Tue,Wed,Thu,Fri,Sat,Sun)) p;
Secondly you need the calendar dates within your range. You could use a function like this:
create function F_DateValues(#FromDate datetime, #ToDate datetime)
returns table as
return (
select dateadd(day,Nr-1,#FromDate) as Date
from (select row_number() over (rand()) as Nr
from (values (1),(1),(1),(1)) a
cross join (values (1),(1),(1),(1)) b
cross join (values (1),(1),(1),(1)) c
cross join (values (1),(1),(1),(1)) c) n
where Nr > datediff(day,#FromDate,#ToDate)
);
Now you can put this alltogether:
select d.Date,
isnull(w.CountWorkingPlanned,0)-isnull(l.CountLeaves,0) as CountWorking
from F_DateValue('20101118','20101128') d
left join (select LeaveDate, count(*) as CountLeaves
from TBL_LeaveDate group by LeaveDate) l
on l.LeaveDate = d.Date
left join (select weekday, count(*) as CountWorkingPlanned
from V_EmployeeWorkingDays where IsWorking=1 group by weekday) w
on w.weekday = datepart(weekday,d.Date);
This should be working (not tested - so please don't kill me for typos ;) ).
You should redesign the table layout. As you have a field for each weekday, that means that you have data in the field names. Data belongs inside the table, so you should put that data as rows in a separate table.
Then it's easy to get the data. Example:
select count(*)
from Employees e
left join Leave l on l.EmployeeId = e.EmployeeId and LeaveDate = #Today
left join Workdays w on w.EmployeeId = e.EmployeeId and w.WeekDay = datepart(weekday, #Today)
where l..EmployeeId is null and w.EmployeeId is null
SELECT count(id) FROM employee WHERE monday = true
Seems easy enough unless I still don't get what you need...
Here's a query that might work for you.
The query uses derived queries to get the leave and work counts. I included an UNPIVOT operation on the TBL_Employee data to make it easier to get the employee data. You can avoid this with design changes that have been suggested.
SELECT Leave.Leave_Date, WorkCount, LeaveCount,
WorkCount-LeaveCount AS CountDifference
FROM
(
-- Get Leave counts by date
SELECT Leave_Date, UPPER(LEFT(DATENAME(dw, Leave_Date), 3)) AS WorkDay,
COUNT(*) as LeaveCount
FROM TBL_Leave
WHERE Leave_Date between '2010-01-01' AND '2010-12-31'
GROUP BY Leave_Date, UPPER(LEFT(DATENAME(dw, Leave_Date), 3))
) AS Leave
LEFT OUTER JOIN
(
-- Get Work counts by day of week
SELECT WorkDay, COUNT(*) WorkCount
FROM
(
SELECT EmpID, Mon, Tue, Wed, Thu, Fri, Sat, Sun
FROM TBL_Employees
) p
UNPIVOT
(IsWorking FOR WorkDay IN
(Mon, Tue, Wed, Thu, Fri, Sat, Sun)
)AS unpvt
WHERE unpvt.IsWorking = 1
GROUP BY WorkDay
) AS Work ON Leave.WorkDay = Work.WorkDay -- Join on day of week
thanks for everyones help on this, i have picked up a few tips. i managed to get this sorted last night and when i look at it, i think i made it sound more complicated than it is. here is what i came up with.
create table TEMP_planned (id int null, mon int null, tue int null, wed int null, thur int null, fri int null, sat int null, sun int null)
insert into TEMP_planned (id, mon, tue, wed, thur, fri, sat, sun)
values(1,
(select SUM(convert(int,Mon)) from TBL_Employees where Planned = 1),
(select SUM(convert(int,Tue)) from TBL_Employees where Planned = 1),
(select SUM(convert(int,Wed)) from TBL_Employees where Planned = 1),
(select SUM(convert(int,Thu)) from TBL_Employees where Planned = 1),
(select SUM(convert(int,Fri)) from TBL_Employees where Planned = 1),
(select SUM(convert(int,Sat)) from TBL_Employees where Planned = 1),
(select SUM(convert(int,Sun)) from TBL_Employees where Planned = 1))
select TBL_Leave.Leave_Date,
'Planned' = case DATEPART(dw,TBL_Leave.Leave_Date) - 1
when 1 then (select mon from TEMP_Planned where ID = 1)
when 2 then (select tue from TEMP_Planned where ID = 1)
when 3 then (select wed from TEMP_Planned where ID = 1)
when 4 then (select thur from TEMP_Planned where ID = 1)
when 5 then (select fri from TEMP_Planned where ID = 1)
when 6 then (select sat from TEMP_Planned where ID = 1)
when 7 then (select sun from TEMP_Planned where ID = 1)
end,
COUNT(tbl_leave.Leave_Date) as 'Total Staff Off'
from TBL_Leave
group by TBL_Leave.Leave_Date
drop table temp_planned