Get the date for custom day but current month in sql - sql

I am working on some reporting module, where I need to implement the logic which gets a date as below cases -
My table :-
Id
Day
1
8
2
14
3
22
4
29
Now I have to write a query to get result as below -
Case 1- If current date (GETDATE()) is 2022-9-5 00:00:00.000
result
2022-9-8 00:00:00.000
2022-9-14 00:00:00.000
2022-9-22 00:00:00.000
2022-9-29 00:00:00.000
Case 2- If current date (GETDATE()) is 2022-9-16 00:00:00.000
result
2022-10-8 00:00:00.000
2022-10-14 00:00:00.000
2022-9-22 00:00:00.000
2022-9-29 00:00:00.000
Note : The query should work with any month / year.

select dateadd(day, day, eomonth(getdate(), case when day < datepart(day, getdate()) then 0 else -1 end)) as result
from t
result
2022-10-08 00:00:00.000
2022-10-14 00:00:00.000
2022-09-22 00:00:00.000
2022-09-29 00:00:00.000
Fiddle

Some DATEADD willhep, as you first need to know, the first daty of the next month and then you can add the das from your table
The moth seledcted will be determined if the day of the Selct run is smaller than the day in the table
CREATE TABLE table1
([Id] int, [Day] int)
;
INSERT INTO table1
([Id], [Day])
VALUES
(1, 8),
(2, 14),
(3, 22),
(4, 29)
;
4 rows affected
SELECT getdate()
(No column name)
2022-09-20 18:55:21.917
SELECT
DATEADD(DAY, [Day] -1,DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())+
(CASE WHEN DAY(GETDATE()) < [Day] THEN 0 ELSE 1 END), 0))
FROM table1
(No column name)
2022-10-08 00:00:00.000
2022-10-14 00:00:00.000
2022-09-22 00:00:00.000
2022-09-29 00:00:00.000
fiddle

An IF ELSE is probably what you need
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/if-else-transact-sql?view=sql-server-ver16
So if the day is greater than the day in the other table, add one month to the date.

Related

SSMS 2018 - Find Gaps in Dates and Flag the Gaps

I have reviewed many posts about how to find gaps in dates and believe that I am close to figuring it out but need just a little extra help. Per my query I am pulling distinct days with a record count for each distinct day. I have added a "Gap_Days" column which should return a zero if no gap from previous date OR the number of days since the previous date. As you can see all of my Gap_Days are zero when in fact I am missing 10/24 and 10/25. Therefore on 10/26 there should be a gap of 2 since the previous date is 10/23.
Thanks in advance for pointing out what I am probably looking right at.
SELECT DISTINCT Run_Date, COUNT(Run_Date) AS Daily_Count,
Gap_Days = Coalesce(DateDiff(Day,Lag(Run_Date) Over (partition by Run_Date order by Run_Date DESC), Run_Date)-1,0)
FROM tblUnitsOfWork
WHERE (Run_Date >= '2022-10-01')
GROUP BY Run_Date
ORDER BY Run_Date DESC;
Run_Date Daily_Count Gap_Days
2022-10-29 00:00:00.000 8431 0
2022-10-28 00:00:00.000 8204 0
2022-10-27 00:00:00.000 8705 0
2022-10-26 00:00:00.000 7885 0
2022-10-23 00:00:00.000 7485 0
2022-10-22 00:00:00.000 8699 0
2022-10-21 00:00:00.000 9212 0
2022-10-20 00:00:00.000 9220 0
First let's set up some demo data:
DECLARE #table TABLE (ID INT IDENTITY, date DATE)
DECLARE #dt DATE
WHILE (SELECT COUNT(*) FROM #table) < 30
BEGIN
SET #dt = DATEADD(DAY,(ROUND(((50 - 1 -1) * RAND() + 1), 0) - 1)-25,CURRENT_TIMESTAMP)
IF NOT EXISTS (SELECT 1 FROM #table WHERE date = #dt) INSERT INTO #table (date) SELECT #dt
END
ID date
--------
1 2022-11-10
2 2022-11-15
3 2022-10-20
...
28 2022-10-14
29 2022-11-13
30 2022-11-21
This gives us a table variable with 30 random dates in a 50 day window. Now let's look for missing dates:
SELECT *, CASE WHEN ROW_NUMBER() OVER (ORDER BY date) > 1 AND LAG(date,1) OVER (ORDER BY date) <> DATEADD(DAY,-1,date) THEN 'GAP! ' + CAST(DATEDIFF(DAY,LAG(date,1) OVER (ORDER BY date),date)-1 AS NVARCHAR) + ' DAYS MISSING!' END
FROM #table
ORDER BY date
All we're doing here is ignoring the first date (since it's expected there wouldn't be one before then) and from then on comparing the last date (using lag ordered by date) to the current date. If it is not a day before the case statement will produce a message with how many days were missing.
ID date MissingDatesFlag
----------------------------
1 2022-10-08 NULL
4 2022-10-09 NULL
25 2022-10-10 NULL
28 2022-10-11 NULL
22 2022-10-15 GAP! 4 DAYS MISSING!
2 2022-10-18 GAP! 3 DAYS MISSING!
12 2022-10-19 NULL
24 2022-10-20 NULL
....
15 2022-11-18 GAP! 3 DAYS MISSING!
29 2022-11-21 GAP! 3 DAYS MISSING!
20 2022-11-22 NULL
Since the demo data is randomly selected your results may vary, but they should be similar.

Query based on day-of-week/time range

I'm on SQL Server 2014. I have a table with columns like this:
id (int, PK, identity)
effectiveDate (datetime)
expirationDate (datetime)
1
2022-07-06 18:00:00.000
2022-07-06 23:00:00.000
2
2022-07-08 22:00:00.000
2022-07-09 02:00:00.000
I need to select rows where the current date/time (GETDATE()) lands within the day-of-week/time range represented by these datetimes, beginning on the effectiveDate. So think of row 1 as the range Wednesday 18:00 -> Wednesday 23:00 and row 2 as Friday 22:00 -> Saturday 02:00. (Keep in mind the day-of-week/time range can span multiple days, as in the 2nd row.)
Examples:
GETDATE() = 2022-07-06 19:30:00.000 (i.e Wednesday at 19:30)
Selects row 1
GETDATE() = 2022-07-30 01:00:00.000 (i.e. Saturday at 01:00)
Selects row 2
GETDATE() = 2022-06-30 19:00:00.000 (i.e. Wednesday at 19:00 which matches row 1 on the day-of-week/time range, but is before the effective date)
Selects nothing
I'm not quite sure how to attack this. Maybe we could date adjust each row's effectiveDate and expirationDate as many weeks forward as needed to place the effectiveDate before GETDATE() (assuming the effectiveDate <= GETDATE()). Any thoughts?
The DATEPART with weekday is the key to checking the weekday range. There are two cases. For example, Mon to Wed is different than Wed to Mon. Mon to Wed is easy with a test of Mon<=day<= Wed. For Wed to Mon, it becomes day < Mon or day > Wed. However, this is the same as NOT Mon<=day<=Wed. The query might not be exactly what you need, but should be a good start.
with TestData as (
select *
from (
values
(1, CAST('2022-07-06 18:00:00.000' as datetime), CAST('2022-07-06 23:00:00.000' as datetime)),
(2, CAST('2022-07-08 22:00:00.000' as datetime), CAST('2022-07-09 02:00:00.000' as datetime)),
(3, CAST('2022-07-09 22:00:00.000' as datetime), CAST('2022-07-11 10:00:00.000' as datetime))
) t (id, effectiveDate, expirationDate)
), TestSamples as (
select *
from (
values
(CAST('2022-07-06 19:30:00.000' as datetime)),
(CAST('2022-07-30 01:00:00.000' as datetime)),
(CAST('2022-07-31 01:00:00.000' as datetime)),
(CAST('2022-06-30 19:00:00.000' as datetime)),
(CAST('2022-08-04 19:00:00.000' as datetime))
) t (testDate)
), WeekDayRange as (
select id, effectiveDate, expirationDate,
datename(weekday, effectiveDate) as WDNAME1, datename(weekday, expirationDate) as WDNAME2,
datepart(weekday, effectiveDate) as WD1, datepart(weekday, expirationDate) as WD2
from TestData
) select t.testDate, datename(weekday, t.testDate) as [WDNAME], datepart(weekday, t.testDate) as [WD], r.*
from TestSamples t
left join WeekDayRange r
on (r.WD1 <= r.WD2 and datepart(weekday, t.testDate) BETWEEN r.WD1 and r.WD2)
or (r.WD1 > r.WD2 and not datepart(weekday, t.testDate) BETWEEN r.WD2 and r.WD1)
where t.testDate > r.effectiveDate
testDate WDNAME WD id effectiveDate expirationDate WDNAME1 WDNAME2 WD1 WD2
----------------------- --------- ----------- ----------- ----------------------- ----------------------- --------- --------- ----------- -----------
2022-07-06 19:30:00.000 Wednesday 4 1 2022-07-06 18:00:00.000 2022-07-06 23:00:00.000 Wednesday Wednesday 4 4
2022-07-30 01:00:00.000 Saturday 7 2 2022-07-08 22:00:00.000 2022-07-09 02:00:00.000 Friday Saturday 6 7
2022-07-31 01:00:00.000 Sunday 1 3 2022-07-09 22:00:00.000 2022-07-11 10:00:00.000 Saturday Monday 7 2
I think I got this working with a bit of math:
DECLARE #myGetDate DATETIME;
SET #myGetDate = '2022-07-30 01:00:00.000';
WITH AdjustedWeeklyDates AS (
SELECT
ID,
DATEADD(WEEK, DATEDIFF(day, effectiveDate, #myGetDate) / 7, effectiveDate) AS adjustedEffectiveDate,
DATEADD(WEEK, DATEDIFF(day, effectiveDate, #myGetDate) / 7, expirationDate) AS adjustedExpirationDate
FROM
dbo.myTable
WHERE
effectiveDate <= #myGetDate
)
SELECT
mt.ID, mt.effectiveDate, mt.expirationDate, awd.adjustedEffectiveDate, awd.adjustedExpirationDate
FROM dbo.myTable mt
INNER JOIN AdjustedWeeklyDates awd ON mt.ID = awd.ID
WHERE
awd.adjustedEffectiveDate <= #myGetDate
AND awd.adjustedExpirationDate > #myGetDate
To explain:
DATEDIFF(day, effectiveDate, #myGetDate) returns the number of days between the effectiveDate and the current date. So for example, say it was 20 days ago.
/ 7 gets the number of weeks as an int, since the DATEDIFF returns an int. This also results in the floor of the quotient. So, with our example 20 days / 7, the quotient is about 2.86, but this will result in an even 2
DATEADD adds the number of weeks to bring us up to or before the current date/time. We add the same number of weeks to the expiration date, resulting in the same range as the original effective/expiration dates, which may or may not extend around the current date.
Finally the check for effectiveDate <= #myGetDate guarantees the current date is after or equal to the effective date.

Selecting only 'month-day time' from Datetime field in SQL (server 2012)

I'm looking at changing a specific date to a different date in a case statement, however, I only want it to apply to a day, month and time.
For example, I want to get the case statement to change any date which falls on 31/12 # 23:59:00 to 01/01 # 00:00:00 but unless I write the case to include each year for the next 40 years to cover myself, I've not been able to resolve this.
I am writing this from the UK with date format being dd/mm/yyyy (above example is 31st December and 1st January).
The format of the datetime field in the database is 'datetime': 2019-07-01 13:14:47).
I can't tell if you want the return type to be a date or datetime. If a date, you can do:
(case when year(dateadd(minute, 1, datecol)) <> year(datecol)
then datefromparts(year(datecol) + 1, month(datecol), day(datecol))
else cast(datecol as date)
end)
The logic would be similar for a datetime, assuming datecol is already a datetime:
(case when year(dateadd(minute, 1, datecol)) <> year(datecol)
then datefromparts(year(datecol) + 1, month(datecol), day(datecol))
else datecol
end)
If I understand correctly you want to round the dates inside last minute of year into the next year. You can do this:
SELECT datecol, CASE
WHEN MONTH(datecol) = 12 AND DAY(datecol) = 31 AND CAST(datecol AS TIME(3)) >= '23:59:00' THEN CAST(DATEADD(MINUTE, 1, datecol) AS DATE)
ELSE datecol
END
FROM (VALUES
(CAST('2018-12-31 23:58:59.997' AS DATETIME)),
(CAST('2018-12-31 23:59:00.000' AS DATETIME)),
(CAST('2018-12-31 23:59:59.997' AS DATETIME)),
(CAST('2019-01-01 00:00:00.000' AS DATETIME))
) AS v(datecol)
Result:
2018-12-31 23:58:59.997 2018-12-31 23:58:59.997
2018-12-31 23:59:00.000 2019-01-01 00:00:00.000
2018-12-31 23:59:59.997 2019-01-01 00:00:00.000
2019-01-01 00:00:00.000 2019-01-01 00:00:00.000

Creating Modified Rows from Frequency Column in T-SQL

I need some ideas regarding an efficient way of creating rows per each count on a frequency column on SQL. (SQL Server 2016)
The data:
I have a table with the dates people called in sick and how many days they said they were gonna be absent:
BEGIN_DATE DAYS_SICK
2011-01-01 00:00:00.000 3
2011-01-01 00:00:00.000 3
2011-01-01 00:00:00.000 1
2011-01-02 00:00:00.000 2
2011-01-02 00:00:00.000 3
2011-01-04 00:00:00.000 4
2011-01-04 00:00:00.000 4
2011-01-04 00:00:00.000 3
I want to translate this to a table where each row represents a day in the year and I count the number of people that are sick that day.
DATE PEOPLE_SICK
2011-01-01 00:00:00.000 3
2011-01-02 00:00:00.000 4
2011-01-03 00:00:00.000 4
2011-01-04 00:00:00.000 4
2011-01-05 00:00:00.000 3
2011-01-06 00:00:00.000 3
2011-01-07 00:00:00.000 2
So for example:
For 2011-01-01 there were 3 persons that called in sick, 2 called in sick for 3 days and one only for that day. The output is 3.
Now on 2011-01-02 another 2 (different) persons called in sick but there were 2 persons from the day before that said they were gonna miss that day too so the output is 4.
No person called in sick on 2011-01-03 but there were 2 persons from 2 days ago that said they were gonna miss that day plus 2 persons from the day before. The output is 4.
Etc...
I am currently doing this by iterating through each of the rows in the input and then looping over the frequencies, adding or updating rows on the new table as necessary but it takes an obscene amount of time.
Is there any other way of doing this more efficiently?
This doesn't deal with weekends at all but can get you started. Also if there were a query that ran often I would build a DATE DIM table and use it instead of the Dates CTE. Where I got the DATE DIM code from.
CREATE TABLE #test (ID int IDENTITY(1,1), BEGIN_DATE datetime, DAYS_SICK int);
DECLARE #StartDate datetime = '2011-01-01'
, #CutoffDate datetime = '2011-01-10';
INSERT INTO #test (BEGIN_DATE, DAYS_SICK)
VALUES
('2011-01-01 00:00:00.000', 3),
('2011-01-01 00:00:00.000', 3),
('2011-01-01 00:00:00.000', 1),
('2011-01-02 00:00:00.000', 2),
('2011-01-02 00:00:00.000', 3),
('2011-01-04 00:00:00.000', 4),
('2011-01-04 00:00:00.000', 4),
('2011-01-04 00:00:00.000', 3);
WITH Dates
AS (SELECT d
FROM (
SELECT d = DATEADD(DAY, rn - 1, #StartDate)
FROM (SELECT TOP (DATEDIFF(DAY, #StartDate, #CutoffDate)) rn = ROW_NUMBER() OVER (
ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
ORDER BY s1.[object_id]
) AS x
) AS y
)
,SickRanges
AS (
SELECT BEGIN_DATE
,DATEADD(DAY, DAYS_SICK - 1, BEGIN_DATE) END_DATE
FROM #test
)
SELECT d.d [DATE]
,count(1) PEOPLE_SICK
FROM SickRanges sr
JOIN Dates d ON d.d BETWEEN sr.BEGIN_DATE AND sr.END_DATE
GROUP BY d.d
ORDER BY d.d
DROP TABLE #test

How to count between no of days between two dates month wise in SQL Server?

I have Two Date in SQL sever which overlap in two month i want to find how many days over lap in each month.
For example:
Start date is : 26-Sep-2012
End Date is : 10-Oct-2012
Sept- 5 days
October - 10 days
along with the list of date of each month.
declare #start datetime;
declare #end datetime;
set #start = '20120926';
set #end = '20121010';
SELECT (DAY(DATEADD (m, 1, DATEADD(d, 1 - DAY(#start), #start))-1)
- DAY(#start) + 1) AS DaysLeftStart,
DAY(#end) AS DaysEnd
Fiddle: http://sqlfiddle.com/#!3/d41d8/4441/0
DECLARE #start DATETIME, #end DATETIME;
SELECT #start = '20120926', #end = '20121010';
;WITH c(d) AS
(
SELECT TOP (DATEDIFF(DAY, #start, #end)+1)
DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY name)-1, #start)
FROM sys.all_columns
)
SELECT
[date] = DATEADD(MONTH, DATEDIFF(MONTH, 0, d), 0),
[days] = COUNT(*)
FROM c GROUP BY DATEDIFF(MONTH, 0, d)
UNION ALL SELECT d, NULL FROM c;
Results:
date days
----------------------- -----
2012-09-01 00:00:00.000 5
2012-10-01 00:00:00.000 10
2012-09-26 00:00:00.000 NULL
2012-09-27 00:00:00.000 NULL
2012-09-28 00:00:00.000 NULL
2012-09-29 00:00:00.000 NULL
2012-09-30 00:00:00.000 NULL
2012-10-01 00:00:00.000 NULL
2012-10-02 00:00:00.000 NULL
2012-10-03 00:00:00.000 NULL
2012-10-04 00:00:00.000 NULL
2012-10-05 00:00:00.000 NULL
2012-10-06 00:00:00.000 NULL
2012-10-07 00:00:00.000 NULL
2012-10-08 00:00:00.000 NULL
2012-10-09 00:00:00.000 NULL
2012-10-10 00:00:00.000 NULL
Here you can find some detail about creating a calendar table. You can use it to perform such query:
SELECT CalendarMonth, count(*) day_num
FROM dbo.Calendar
WHERE CalendarDate between #start_date and #end_date;