Count business days in month excluding holidays - sql

I have the below table.
TableA
DaysInMonth CalDate CalendarMonth MonthEndInd CalDateMonth WorkDay HolidayInd
31 3/26/2018 MAR 2018 N 3/31/2018 1 N
31 3/25/2018 MAR 2018 N 3/31/2018 0 N
How can I calculate the number of WorkDays in a month?
I'm not sure where to start, so I don't have any work to show.
Expected output
DaysInMonth CalDate CalendarMonth MonthEndInd CalDateMonth WorkDay HolidayInd WorkDaysInMonth
31 3/26/2018 MAR 2018 N 3/31/2018 1 N
31 3/25/2018 MAR 2018 N 3/31/2018 0 N
SQL I have the below but how can I add this to my query
SELECT COUNT(*),C.CALENDAR_MONTH FROM HUM.CALENDAR C WHERE 1=1 AND C.WORKDAY = 1 GROUP BY C.CALENDAR_MONTH

You can use an analytic function:
SELECT c.*,
SUM( WorkDay ) OVER ( PARTITION BY CalendarMonth ) AS WorkDaysInMonth
FROM Calendar C;
SQLFIDDLE

In much the same way as SUM you can also use COUNT if you want to limit it to just working days;
SELECT c.*,
COUNT(*) OVER(PARTITION BY calendarmonth ORDER BY calendarmonth) WDIM
FROM calendar
WHERE workday = 1
Depends how you want to use it I guess.

Related

Creating FISCAL_WEEK column in SQL Server Calendar table

We have a requirement. We need to create a fiscal_week column in a SQL Server table.
Table will have data as Normal_date column as fiscal_date and fiscal_year column which will have year part of the date.
Logic for FISCAL_WEEK is like this:
FIRST FISCAL WEEK WILL START FROM 1 JAN OF EVERY YEAR AND IT WILL BE TILL FIRST FRIDAY.
SECOND WEEK STARTS FROM SATURDAY AND IT WILL RUN TILL NEXT FRIDAY.
THIS WILL GO ON TILL THE END OF YEAR (31 JAN)
We will have data something as below table.
Table With Fields as per Requirement
How would I create query for this ? We will have data from 2010 till 2035 years in the table.
Thanks,
Mahesh
You can use an ad-hoc tally table to create the dates and then the window function sum() over() to calculate the Fiscal_Week
Declare #Date1 date = '2020-01-01'
Declare #Date2 date = '2035-12-31'
Select Fiscal_Date = D
,Fiscal_Year = DatePart(YEAR,D)
,Day_Name = DateName(WEEKDAY,D)
,Fiscal_Week = case when DateName(WEEKDAY,#Date1)='Saturday' then 0 else 1 end
+sum(case when DateName(WEEKDAY,D)='Saturday' then 1 else 0 end) over (partition by DatePart(YEAR,D) order by D)
From ( Select Top (DateDiff(DAY,#Date1,#Date2)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#Date1)
From master..spt_values n1,master..spt_values n2,master..spt_values n3
) A
Results
Fiscal_Date Fiscal_Year Day_Name Fiscal_Week
2020-01-01 2020 Wednesday 1
2020-01-02 2020 Thursday 1
2020-01-03 2020 Friday 1
2020-01-04 2020 Saturday 2
2020-01-05 2020 Sunday 2
2020-01-06 2020 Monday 2
...
Other way I found would be as below.
SET DATEFIRST 6
GO
SELECT FISCAL_DATE, DATEPART(WEEK,Fiscal_Date) FISCAL_WEEK FROM ERP.CUSTOM_FISCAL_CALENDER

check whether values lies between or not in SQL

These are my 2 tables
table 2:
date_val
yr_num
yr_wk_num
day_wk_num
yr_wk_nm
day
mo_num
20200808
2020
32
6
202032
Saturday
08
20200809
2020
32
7
202032
Sunday
08
20200810
2020
33
1
202033
Monday
08
20200811
2020
33
2
202033
Tuesday
08
20200812
2020
33
3
202033
Wednesday
08
table1:
sku_id
dateval
sales
ab124
20210603
10
ab124
20210502
20
ab123
20210606
30
Need to check sales is with in + or - 30% of 2 month avg sales
with CTE
as
(
select * from table1 where dateval >= dateadd(mm, -2, dateval)
)
select dateval, sum(sales) as [Total Sales], avg(sales) as [Average Sales] from CTE group by dateval order by 1
I tried below also...
with CTE
as
(
select * from table1 t1 left join table2 t2 on t1.dateval = t2.date_val where t2.date_val >= dateadd(mm, -2, t1.dateval)
)
select dateval,sum(sales) as [Total Sales], avg(sales) as [Average Sales] from CTE group by dateval order by 1
here am doing filtering within table1 but i need to use table 2 to get filtered for past two months and get avg sales.
Next, i need to do +30% to that result avg and -30% result avg and check whether my sales is withn avg sales( avg30% above or below) or not if yes '1' if not '0'
For Ex:
Historic 2 month avg sales 100.
(+30% of avg sales is 130)
(-30% of avg sales is 70)
if sales is 120. i need to check 120 lies between 70 to 130.
please suggest me how to achieve

SQL question - how to output using iterative date logic in SQL Server

I have the following sample table (provided with single ID for simplicity - need to perform the same logic across all IDs)
ID Visit_date
-----------------
ABC 8/7/2019
ABC 9/10/2019
ABC 9/12/2019
ABC 10/1/2019
ABC 10/1/2019
ABC 10/8/2019
ABC 10/15/2019
ABC 10/17/2019
ABC 10/24/2019
Here is what I need to get the sample output
Mark the first visit as 1 in the "new_visit" column
Compare the subsequent dates with the 1st date until it exceeds 21 days condition. Example Sep 10 is compared to Aug 7 and it doesn’t fall within 21 days of Aug 7, therefore this is considered as another new_visit, so mark new_visit as 1
Then we compare Sep 10 with the subsequent dates with 21 days criteria and mark all of them as follow_up of Sep 10 visit. Eg. Sep 12, Oct 1 are within 21 days of Sep 10; hence they are considered as follow up visits, so mark "follow_up" as 1
When the subsequent date exceeds 21 days criteria of the previous new visit (e.g. Oct 8 compared to Sep 10) then Oct 8 will be considered a new visit & mark "New_visit" as 1 and the subsequent dates will be compared against Oct 8
Sample Output :
Dates New_Visit Follow_up
-----------------------------
8/7/2019 1
9/10/2019 1
9/12/2019 1
10/1/2019 1
10/1/2019 1
10/8/2019 1
10/15/2019 1
10/17/2019 1
10/24/2019 1
You need a recursive query for this.
You would enumerate the rows, then walk through the dataset by ascending date, while keeping track of the first visit date of each group; when the interval since the last first visit exceeds 21 days, the date of the first visit resets, and a new group starts.
with
data as (
select t.*, row_number() over(partition by id order by date) rn
from mtytable t
),
cte as (
select id, visit_date, visit_date first_visit_date
from data
where rn = 1
union all
select c.id, d.visit_date, case when d.visit_date > datead(day, 21, c.first_visit_date) then d.visit_date else c.first_visit_date end
from cte c
inner join data d on d.id = c.id and d.rn = c.rn + 1
)
select
id,
date,
case when visit_date = first_visit_date then 1 else 0 end as is_new
case when visit_date = first_visit_date then 0 else 1 end as is_follow_up
from cte
If a patient may have more than 100 visits, then you need to add option (maxrecursion 0) at the very end of the query.
You need a recursive CTE to handle this. This is the idea, although the exact syntax might vary by database:
with recursive t as (
select id, date,
row_number() over (partition by id order by date) as seqnum
from yourtable
),
recursive cte as (
select id, date, visit_start as date, 1 as is_new_visit
from t
where id = 1
union all
select cte.id, t.date,
(case when t.date < visit_start + interval '21 day'
then cte.visit_start else t.date
end) as visit_start,
(case when t.date < cte.visit_start + interval '21 say'
then 0 else 1
end) as is_new_visit
from cte join
t
on t.id = cte.id and t.seqnum = cte.seqnum + 1
)
select *
from cte
where is_new_visit = 1;

Generate a calendar table with hours of the day using SQL Server 2012

Problem Statement:
I work for a fire department and I am working on statistical analysis of my data. One issue is to generate the number of calls for service for every hour of every day for a calendar year. I need a table that can be joined to the fire incidents that has every day of the year and every hour of each day. What I am hoping for is the following (using military time)
1 January 2017 00:00:00
1 January 2017 00:00:00
1 January 2017 01:00:00
1 January 2017 02:00:00
1 January 2017 03:00:00
1 January 2017 04:00:00
1 January 2017 05:00:00
1 January 2017 06:00:00
1 January 2017 07:00:00
1 January 2017 08:00:00
etc until the end of the year
31 December 2017 21:00:00
31 December 2017 22:00:00
31 December 2017 23:00:00
end of year
this table will allow me to join to the fire incidents table and I will be able to statistically calculate the number of incidents for each hour of the day and for each day of the year. A calculated table is necessary because the fire incidents table has gaps in it. For example; On january 1st at 0100 hours and 0200 hours and 0300 hours no emergency calls came in. Therefore I cannot make a calculation using the fire incidents table because there is no data for when no calls come in. The fire incidents table with gaps looks like:
TimeInterval, IncidentAddress
1 january 2017 00:00:00, 123 Elm Street
1 January 2017 04:00:00, 456 Oak Street
1 January 2017 05:00:00, 789 Maple Street
(Notice there are no fire calls for the hours of 0100, 0200 and 0300. Those are the gaps.)
Because there are gaps in the data where zeros should be the calculated averages necessary for a Poisson distribution are missing. The averages are incorrect.
Desired Output:
My goal is to have a calendar with an hours of day table to join to my fire incidents so my results set returns. Here is a rough draft of a query that returns every row from the calendar table and rows from the fire incidents table if there is matching value.
SELECT
TimeInterval
, COUNT(Incidents) AS [CountOfIncidents] /*this should probably be a COALESCE statement*/
FROM CalendarTable /*all rows from the calendar with hours and rows with data from FireIncidents*/
LEFT OUTER JOIN FireIncidents ON CalendarTable.timeInterval = FireIncidents.TimeInterval
GROUP BY TimeInterval
Query would return what I am hoping to achieve:
TimeInterval, CountOfIncidents
1 january 2017 00:00:00, 5
1 January 2017 01:00:00, 0
1 January 2017 02:00:00, 0
1 January 2017 03:00:00, 0
1 January 2017 04:00:00, 2
1 January 2017 05:00:00, 1
(Notice the hours of 0100, 0200 and 0300 have zero number of calls. This is what I want! Now I can create a histogram showing how many hours had zero calls. Or I can calculate an average value that takes into account zero calls for parts of the day.)
What I have tried:
I have tried the following but I cannot figure out how to create a table out of this and how to make it a finished product as you can see below in the Question paragraph.
DECLARE #DayOfYearNumber INT
DECLARE #HourNumber INT
SET #DayOfYearNumber = 1
SET #HourNumber = 0
PRINT 'Year' + ', ' + 'CalendarDayOfYear' + ', ' + 'HourOfDay'
WHILE #DayOfYearNumber < 366
BEGIN
SET #HourNumber = 0
WHILE #HourNumber < 24
BEGIN PRINT '2017' + ', ' + CONVERT(VARCHAR, #DayOfYearNumber) + ' ' + CONVERT(VARCHAR, #HourNumber)
SET #HourNumber = #HourNumber + 1
END
SET #DayOfYearNumber = #DayOfYearNumber + 1
END
Question:
How do I generate a calendar table in SQL Server 2012 that will have every day of the year and every hour of each day. My example again
1 January 2017 00:00:00
1 January 2017 01:00:00
1 January 2017 02:00:00
1 January 2017 03:00:00
1 January 2017 04:00:00
1 January 2017 05:00:00
1 January 2017 06:00:00
1 January 2017 07:00:00
1 January 2017 08:00:00
etc until the end of the year
31 December 2017 21:00:00
31 December 2017 22:00:00
31 December 2017 23:00:00
end of year
A simple method uses recursion:
with d as (
select cast('2017-01-01' as datetime) as dte
union all
select dateadd(hour, 1, dte)
from d
where dateadd(hour, 1, dte) < '2018-01-01'
)
select d.*
from d
option (maxrecursion 0);
Although recursion is surprisingly fast, if you are going to be needing this multiple times, you might want to consider have a numbers table around or storing this in a temporary or permanent table.
A alternative method to using a rCTE is a Tally Table, as it's not RBAR:
DECLARE #TopDate date = '20550101';
WITH N AS(
SELECT *
FROM (VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) V(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS I
FROM N N1
CROSS JOIN N N2
CROSS JOIN N N3
CROSS JOIN N N4
CROSS JOIN N N5
CROSS JOIN N N6)
SELECT DATEADD(HOUR, I, '20170101') AS DateValue
FROM Tally
WHERE DATEADD(HOUR, I, '20170101') < #TopDate;
You could achieve it using single query. All you need is tally(number) table:
WITH tally(n) AS (
SELECT ROW_NUMBER() OVER(ORDER BY 1/0)-1
FROM master..spt_values s1, master..spt_values s2, master..spt_values s3
)
-- INSERT INTO calendar(col_name)
SELECT DATEADD(HOUR,n,'20170101') AS d
FROM tally
WHERE DATEADD(HOUR,n,'20170101') <= '20180101'
Rextester Demo

Incremental and decremental count based on a date

Name Start_date end_date
aaa 01/02/2017 05/03/2017
bbb 03/05/2017 07/07/2017
ccc 02/01/2017 10/09/2017
I want to write a query that calculates the number of people who exist in the DB in a certain month/year.
Answer:
Jan 2017 1
Feb 2017 2
Mar 2017 3
Apr 2017 3
May 2017 2 (one person - aaa ,ended in May 2017)
Jun 2017 2
Jul 2017 1 (bbb ended in July 2017)
How do I write a PSQL query to get the desired output?
Thanks.
First, get the max and min dates in order to declare the dates range.
Second, with etc select all the month in the range.
Third, sum the number of the records in each dates.
Like:
declare #date date
declare #toDate date
select #date = min(Start_date),
#toDate = max(end_date)
from table_name
;With dt As
(
Select #date As [TheDate]
Union All
Select DateAdd(month, 1, TheDate) From dt Where [TheDate] < #toDate
)
select month(dt.TheDate),
year(dt.TheDate),
sum(case when table_name.Name is not null then 1 else 0 end)
from dt
left join table_name
on table_name.Start_date >= dt.TheDate
and table_name.end_date < dateadd(day,-1,dateAdd(month,1,dt.TheDate))