The table:
hDate Holiday
17/12/2011 National Day
01/01/2012 New Year
....
From the table, i want to find the total number of holidays between two dates:
A query like:
select count(hdate)
from table1
where hdate between '" & start_date & "' and '" & end_date & "'
User input:
start_date = '16/12/2011'
end_date = '15/01/2012'
and also I want to find the friday between 2 dates.
For finding fridays, how to create a query?
Expected output:
Holiday Friday
2 5
[2] - 2 days holiday from table1, [5] - 5 days friday
How to do this?
This counts the fridays between 2 dates:
declare #from datetime= '2012-01-26'
declare #to datetime = '2012-01-28'
select datediff(day, -3, #to)/7-datediff(day, -2, #from)/7
The holidays are easy to find, it seems like you have that part covered already.
I sort of answered this earlier. But didn't get any credit:
How to calculate the number of "Tuesdays" between two dates in TSQL?
That select help you:
DECLARE #FROMDATE DATE = '2009-01-07'
DECLARE #TODATE DATE = '2012-01-26'
SELECT COUNT(*) holidays,(select COUNT(*) from table1 where DATEPART(DW, hdate) = 5
AND DT BETWEEN #FROMDATE AND #TODATE ) fridays FROM table1
WHERE hdate BETWEEN #FROMDATE AND #TODATE
See:
Why should I consider using an auxiliary calendar table?
A calendar table can make it much
easier to develop solutions around any
business model which involves dates.
Last I checked, this encompasses
pretty much any business model you can
think of, to some degree. Constant
problems that end up requiring
verbose, complicated and inefficient
methods include the following
questions:
How many business days between x and y?
What are all of the dates between the second Tuesday of March and the first Friday in April?
On what date should I expect this shipment to arrive?
What were the dates of all the Fridays in this quarter?
...
For the holiday, your SQL looks fine, you just seem to have trouble plugging the parameters into the SQL. If you specify which programming language you are using, we might be able to help here. If you use .NET, you should use Parameterized Queries instead of string substitution.
For the fridays, see this question:
Get number of weekdays (Sundays, Mondays, Tuesdays) between two dates SQL
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate ='16/12/2011'
SET #EndDate = '15/01/2012'
SELECT
(DATEDIFF(dd, #StartDate, #EndDate) + 1)
-(DATEDIFF(wk, #StartDate, #EndDate) * 2)
-(CASE WHEN DATENAME(dw, #StartDate) = 'Friday' THEN 1 ELSE 0 END)
select count(Holiday) as holiday
from Table1
where date between start_date AND end_date
We solving the problem with an extra time table. This looks like this
ID | Date | Holiday | Year | CalendarWeek | DayName
1 | 17/12/2011 | 1 | 2011 | 50 | Monday
2 | 18/12/2011 | 0 | 2011 | 50 | Thursday
3 | 19/12/2011 | 0 | 2011 | 50 | Wendsday
With this table you could resolve your question like this
select
(select count(d.DayName) from date_table as d
where d.DayName = 'Friday' and date >= start_date and date <= end_date ),
(select sum(d.Holiday) from date_table as d
where date >= start_date and date <= end_date )
This should also be SQL Server 2000 compatible. And this for SQL Server 2005 and above:
with tmp(id) as
(
select id from from date_table where date >= start_date and date <= end_date
)
select
(select count(d.DayName) from date_table inner join tmp on tmp.id = id
where DayName = 'Friday' ),
(select sum(d.Holiday) from date_table inner join tmp on tmp.id = id )
Related
I am trying to write a query which will work out the difference between two dates but without counting specific days.
select ((DATEPART(weekday,(DATEDIFF(DAY,StartDate,EndDate)))) NOT IN (1,5)) FROM MyTable
In this example I'm trying to remove Monday and Friday from difference days (StartDate EndDate)
Ideally you need calendar table, to do these kinds of calculations easier. Below, I am generating the dates for the range and then counting the days accordingly.
DECLARE #startDate DATE = '2020-07-01'
DECLARE #EndDate DATE = '2020-07-13'
;with AllDates AS
(
SELECT #StartDate AS DateOf
UNION ALL
SELECT DateAdd(day,1,DateOf)
FROM AllDates
WHERE DateOf<#EndDate
)
select SUM(CASE WHEN DATENAME(weekday,dateof) in ('Monday','Friday') THEN 0 ELSE 1 END) as SumOfDays
FROM AllDates
+-----------+
| SumOfDays |
+-----------+
| 9 |
+-----------+
i need a sql function which has to return 5th previous business date except saturday sunday and public holiday,
Ex: i should get last thursday (04-01-2018) if i won't have any public holiday inbetween im able to achieve that by,
SELECT DATEADD(DAY, CASE DATENAME(WEEKDAY, GETDATE())
WHEN 'Sunday' THEN -2
WHEN 'Monday' THEN -3
ELSE -1 END, DATEDIFF(DAY, 5, GETDATE()))
but how to omit public holiday from this,
Can anyone help me please
If you don't want (or you can't) create a calendar "tally" table (with columns identifying holidays and week-end days) you can try a query like following one.
sample data for holidays table
CREATE TABLE HOL_TAB (DAT DATETIME);
INSERT INTO HOL_TAB VALUES ('2018-01-05');
INSERT INTO HOL_TAB VALUES ('2018-01-04');
The query use a CTE to "create" on the fly a small tally calendar table (I used 12 as limit, but you can change it).
The last SELECT use a join with holiday table to exclude those days and then ROW_NUMBER() to extract "first" five days.
To keep query similar to the one you made I used DATENAME, but I suggest to avoid its use and use instead other methods).
WITH CAL_TAB AS (
SELECT DATEADD(dd, 0, CAST(GETDATE() AS DATE) ) AS DAT
, 1 AS COUN
UNION ALL
SELECT DATEADD(dd, -1- CASE DATENAME (WEEKDAY, DATEADD(dd,-1,B.DAT) ) WHEN 'Sunday' THEN 2 WHEN 'Saturday' THEN 1 ELSE 0 END, B.DAT ) AS DAT
, B.COUN+1 AS COUN
FROM CAL_TAB B
WHERE B.COUN<12 /* 12 is just to limit number of days */
)
SELECT DAT, WD
FROM (SELECT C.DAT, C.COUN, DATENAME(WEEKDAY, C.DAT) AS WD, ROW_NUMBER() OVER (ORDER BY COUN) AS RN
FROM CAL_TAB C
WHERE NOT EXISTS(SELECT DAT FROM HOL_TAB D WHERE D.DAT=C.DAT)
) E WHERE RN<=5;
Output:
DAT WD
1 10.01.2018 00:00:00 Wednesday
2 09.01.2018 00:00:00 Tuesday
3 08.01.2018 00:00:00 Monday
4 03.01.2018 00:00:00 Wednesday
5 02.01.2018 00:00:00 Tuesday
try This Logic
WITH CTE
AS
(
SELECT
MyDate = DATEADD(DD,-5,GETDATE())
)
SELECT
MyDate = CASE WHEN DATENAME(WEEKDAY, MyDate) IN ('Sunday','Saturday')
THEN NULL
WHEN MHL.Holiday IS NOT NULL
THEN NULL
ELSE CTE.MyDate END
FROM CTE
LEFT JOIN MyHoliDayList MHL
ON CTE.MyDate = MHL.Holiday
Try this Method:
DECLARE #Holiday TABLE(HoliDay DATE)
INSERT INTO #Holiday VALUES ('2018-01-02')
INSERT INTO #Holiday VALUES ('2018-01-05')
DECLARE #WithOutHoliDay DATETIME = (SELECT DATEADD(DAY, CASE DATENAME(WEEKDAY, GETDATE())
WHEN 'Sunday' THEN -2
WHEN 'Monday' THEN -3
ELSE -1 END, DATEDIFF(DAY, 5, GETDATE())))
SELECT DATEADD(DAY,0-
(SELECT COUNT(1)
FROM #Holiday
WHERE HoliDay BETWEEN #WithOutHoliDay AND GETDATE()),#WithOutHoliDay)
This should give, what exactly you need...
In a calendar control, we can see some dates from the previous month and next month also. Sample image below
(ie Apr-2016: Starts from Mar-28 and ends in May-08
Mar-2016: Starts from Apr Feb-29 and ends in Apr-10)
Here, i need to generate a list of all the dates in a calendar control for a particular year month. My week start is Monday.
Here is the tsql script i have tried so far.
DECLARE #V_DATE DATE = GETDATE()
;WITH CTE_DATE AS (
SELECT DATEADD(dd,-(DAY(#V_DATE)-1),#V_DATE) CDATE
UNION ALL
SELECT DATEADD(dd,1,CDATE)
FROM CTE_DATE
WHERE DATEADD(dd,1,CDATE) <= DATEADD(dd,-(DAY(DATEADD(mm,1,CDATE))),DATEADD(mm,1,CDATE))
)
SELECT * FROM CTE_DATE
Result Is:
2016-04-01
2016-04-02
.
.
2016-04-29
2016-04-30
It will list all the days from a inputted year month, but i need to include the
missing dates from the previous month as well as next month.
Expected result for Apr-2016
2016-03-28
2016-03-29
.
2016-04-15
.
2016-05-07
2016-05-08
Expected result for May-2016
2016-04-25
2016-04-26
.
2016-05-15
.
2016-06-04
2016-06-05
Note:- The calendar control is always showing 42 days.
since your week is starts on Monday,you can take referece to date 0 '1900-01-01' which is a Monday. Adding 41 days would gives you your end date
select
date_fr = dateadd(day, datediff(day, 0, '2016-05-01') / 7 * 7, 0),
date_to = dateadd(day, datediff(day, 0, '2016-05-01') / 7 * 7, 41)
the following gives you date 1900-01-01 and Monday
select convert(datetime, 0), datename(weekday, 0)
Have you considered creating a dates table in your database. You would have columns for dates and a column for week number. Linking to this table you could find the week number for your start and end dates, you could then re-link to the table to find the first date of your start week and the last date of your end week. This would probably be more efficient than calculations at each step each time, it is a simple link.
I have create done script for this. This is working as per my expectation, may be helpful for future reference. (Thanks #Squirrel for the logic).
DECLARE #V_ST_DATE DATE = GETDATE()
SET #V_ST_DATE = DATEADD(DAY,-(DAY(#V_ST_DATE)-1),#V_ST_DATE)
SET #V_ST_DATE = DATEADD(WEEK,DATEDIFF(WEEK, 0, #V_ST_DATE) ,0) +
(CASE WHEN DATEADD(WEEK,DATEDIFF(WEEK, 0, #V_ST_DATE) ,0) > #V_ST_DATE THEN -7 ELSE 0 END)
;WITH CTE_DATE AS (
SELECT #V_ST_DATE CDATE,0 TDAYS
UNION ALL
SELECT DATEADD(DAY,1,CDATE) , DATEDIFF(DAY,#V_ST_DATE, DATEADD(DAY,1,CDATE))
FROM CTE_DATE
WHERE DATEDIFF(DAY,#V_ST_DATE, DATEADD(DAY,1,CDATE)) < 42
)
SELECT * FROM CTE_DATE
This question already has answers here:
Count work days between two dates
(24 answers)
Closed 6 years ago.
I need to count all orders that have a datediff <= 1. I use the COALESCE function, because sometimes the Date1 can be null.
SELECT COUNT(*)
FROM Orders
WHERE DATEDIFF(dd,COALESCE(Date1, Date2),Date0) <= 1
Is there a way I can count only the business days (Mon-Fri)?
Thanks.
You could change your count to a conditional sum, using DATEPART to ignore the Saturdays and Sundays (which should have a dw of 7 & 1 respectively).
SELECT SUM(CASE WHEN DATEPART(dw, COALESCE(Date1, Date2)) BETWEEN 2 AND 6
THEN 1 ELSE 0 END)
FROM Orders
WHERE DATEDIFF(dd,COALESCE(Date1, Date2),Date0) <= 1
You can use a Calendar table and store all business days in it.
Or, you can use the following simple formula to calculate number of business days between two dates. The formula assumes that Saturday and Sunday are not business days. It assumes that two given dates are business days.
All it does, is calculates the normal difference in days and then subtracts 2 (non-business) days from this result for each beginning of the week.
Example 1
DECLARE #Date0 date = '2016-04-07'; -- Thursday
DECLARE #Date1 date = '2016-04-08'; -- Friday
SELECT
DATEDIFF(day, #Date0, #Date1) -
DATEDIFF(week, #Date0, #Date1) * 2 AS BusinessDays;
Result
BusinessDays
1
Example 2
DECLARE #Date0 date = '2016-04-08'; -- Friday
DECLARE #Date1 date = '2016-04-11'; -- Monday
SELECT
DATEDIFF(day, #Date0, #Date1) -
DATEDIFF(week, #Date0, #Date1) * 2 AS BusinessDays;
Result
BusinessDays
1
Example 3
DECLARE #Date0 date = '2016-04-08'; -- Friday
DECLARE #Date1 date = '2016-04-18'; -- Monday
SELECT
DATEDIFF(day, #Date0, #Date1) -
DATEDIFF(week, #Date0, #Date1) * 2 AS BusinessDays;
Result
BusinessDays
6
Your query would look like this:
SELECT COUNT(*)
FROM Orders
WHERE
DATEDIFF(day, COALESCE(Date1, Date2), Date0) -
DATEDIFF(week, COALESCE(Date1, Date2), Date0) * 2 <= 1
;
I'm attempting to calculate days of therapy by month from an oracle database. The (vastly simplified) data is as follows:
Therapies
+-----------+-----------+----------+
| Rx Number | StartDate | StopDate |
|-----------+-----------+----------|
| 1 | 12-29-14 | 1-10-15 |
| 2 | 1-2-15 | 1-14-15 |
| 3 | 1-29-15 | 2-15-15 |
+-----------+-----------+----------+
For the purposes of this example, all times are assumed to be midnight. The total days of therapy in this table is (10-1 + 32-29) + (14-2) + (15-1 + 32-29) = 41. The total days of therapy in January in this table is (10-1) + (14-2) + (32-29) = 24.
If I wanted to calculate days of therapy for the month of January , my best effort is the following query:
SELECT SUM(stopdate - startdate)
FROM therapies
WHERE startdate > to_date('01-JAN-15')
AND stopdate < to_date ('01-FEB-15');
However, rx's 1 and 3 are not captured at all. I could try the following instead:
SELECT SUM(stopdate - startdate)
FROM therapies
WHERE stopdate > to_date('01-JAN-15')
AND startdate < to_date ('01-FEB-15');
But that would include the full duration of the first and third therapies, not just the portion in January. To make the matter more complex, I need these monthly summaries over a period of two years. So my questions are:
How do I include overhanging therapies such that only the portion within the target time period is included, and
How do I automatically generate these monthly summaries over a two year period?
How do I include overhanging therapies such that only the portion
within the target time period is included?
select sum(
greatest(least(stopdate, date '2015-01-31' + 1)
- greatest(startdate, date '2015-01-01'), 0)) suma
from therapies
How do I automatically generate these monthly summaries over a two
year period?
with period as (select date '2014-01-01' d1, date '2015-12-31' d2 from dual),
months as (select trunc(add_months(d1, level-1), 'Month') dt
from period connect by add_months(d1, level-1)<d2)
select to_char(dt, 'yyyy-mm') mth,
sum(greatest(least(stopdate, add_months(dt, 1)) - greatest(startdate, dt), 0)) suma
from therapies, months
group by to_char(dt, 'yyyy-mm') order by mth
Above queries produced desired output. Please insert your dates in proper places to change analyzed periods.
In second SQL inner subquery months gives 24 dates, one for each month. The rest is only maneuvering
with functions greatest(),least() and some math.
Use a case statement to set the start date and stop date. Like the below:
select sum(
Stopdate -
(case Startdate
when startdate < to_date(#YourBeginingDate) then To_date(#YourBeginingDate)
else startdate
end)
FROM therapies
WHERE stopdate > to_date(#YourBeginingDate)
AND StartDate < to_date(#YourEndingDate)
I would do something like the following:
WITH t1 AS (
SELECT 1 AS rx, DATE'2014-12-29' AS start_date
, DATE'2015-01-10' AS stop_date
FROM dual
UNION ALL
SELECT 2, DATE'2015-01-02', DATE'2015-01-14'
FROM dual
UNION ALL
SELECT 3, DATE'2015-01-29', DATE'2015-02-15'
FROM dual
)
SELECT TRUNC(rx_dt, 'MONTH') AS rx_month, SUM(rx_cnt) AS rx_day_cnt
FROM (
SELECT rx_dt, COUNT(*) AS rx_cnt
FROM (
SELECT rx, start_date + LEVEL - 1 AS rx_dt
FROM t1
CONNECT BY start_date + LEVEL - 1 < stop_date
AND PRIOR rx = rx
AND PRIOR DBMS_RANDOM.VALUE IS NOT NULL
) GROUP BY rx_dt
) GROUP BY TRUNC(rx_dt, 'MONTH')
ORDER BY rx_month
Results:
12/1/2014 12:00:00 AM 2
1/1/2015 12:00:00 AM 24
2/1/2015 12:00:00 AM 15
See SQL Fiddle here.
What I am doing is using LEVEL and CONNECT BY to get all the days of therapy based on start_date and stop_date (not inclusive). I then GROUP BY the therapy date (rx_dt) to handle the overlapping therapies. Then I GROUP BY the month of the therapy using the TRUNC() function.
This should work just fine over a two-year period (or more); just add that filter before the last GROUP BY:
WHERE rx_dt >= DATE'2014-01-01'
AND rx_dt < DATE'2016-01-01'
GROUP BY TRUNC(rx_dt, 'MONTH')
Note that if your primary key is composite, you should include all the columns in the CONNECT BY clause:
CONNECT BY start_date + LEVEL - 1 < stop_date
AND PRIOR rx = rx
AND PRIOR patient_id = patient_id
--etc.
This is a bit tricky, as you need to capture days from sessions that:
Begin before the month and end after the month
Begin before the month and end during the month
Begin during the month and end after the month
Begin during the month and end during the month
To get those sessions, you can use a WHERE statement like this (the # symbol means that those are variables being passed in):
*examples are in TSQL, PLSQL might have somewhat different syntax
WHERE startdate < #endDate AND stopdate > #startDate
That should capture all four of those scenarios that I listed.
Then you only need to capture days that occurred during the month. I do this with a query that replaces the startdate/enddate with the date range limits if they exceed the range, like this:
SELECT
CASE WHEN enddate > #endDate then #endDate ELSE enddate END -
CASE WHEN startdate < #startDate THEN #startDate ELSE startdate END
So your whole query should look like this:
SELECT
SUM(
CASE WHEN enddate > #endDate then #endDate ELSE enddate END -
CASE WHEN startdate < #startDate THEN #startDate ELSE startdate END
)
FROM therapies
WHERE startdate < #endDate AND stopdate > #startDate
If you want to run that for two years, toss that code in a function that accepts #startDate and #endDate parameters, then call it from a query that gives you two years worth of months, like this:
WITH dateCTE AS (
SELECT
GETDATE() AS StartDate,
DATEADD(Month, 1, GETDATE()) AS EndDate
UNION ALL
SELECT
DATEADD(MONTH, -1, StartDate),
DATEADD(MONTH, -1, EndDate)
FROM dateCTE
WHERE StartDate > DATEADD(YEAR, -2, GETDATE())
)
SELECT
StartDate,
EndDate,
SomeFunction(StartDate, EndDate)
FROM dateCTE