How to get data by datetime logic in sql - sql-server

I have date(dt col name) column in est.
If i execute query between '7:30pm'EST and '11:59' Est timezone.i should get previous day information and if i execute after 12:00 i need to get today's information.
I have tried this:
Case when getdate() between '2021-03-09 7:30:00' and '2021-03-09 11:59:00'
Then dt=getdate() -1
else dt=getdate()

If I understand your question correctly, then you want to retrieve different rows depending on the time when the query is executed.
Using a case expression can work, but then you have to move the comparison with the case expression result outside the case expression.
So instead of:
... where case when <condition> then (Column = Value1) else (Column = Value2) end
do this:
... where Column = (case when <condition> then Value1 else Value2 end)
where the parentheses are added here for clarity.
Sample data
create table MyTable
(
dt date,
data nvarchar(20)
);
insert into MyTable (dt, data) values
('2021-03-08', 'yesterday''s data'),
('2021-03-09', 'today''s data');
Solution
Applying the construction from the start of my answer. Unit testing both options of the case expression will require a separate variable (or you could just wait hours and hours...).
Requesting data on today's date in period [07:30, 11:59]
declare #referenceDate smalldatetime = '2021-03-09 10:20';
--> using date variable instead of 'getdate()' for demo purposes !
select mt.dt,
mt.data
from MyTable mt
where mt.dt = case
when #referenceDate between '2021-03-09 07:30:00' and '2021-03-09 11:59:00'
then dateadd(day, -1, convert(date, #referenceDate))
else convert(date, #referenceDate)
end;
--> returns data from yesterday
Requesting data on today's date outside period [07:30, 11:59]
declare #referenceDate smalldatetime = '2021-03-09 17:40';
--> using date variable instead of 'getdate()' for demo purposes !
select mt.dt,
mt.data
from MyTable mt
where mt.dt = case
when #referenceDate between '2021-03-09 07:30:00' and '2021-03-09 11:59:00'
then dateadd(day, -1, convert(date, #referenceDate))
else convert(date, #referenceDate)
end;
--> returns data from today
Requesting data on any date depending on period [07:30, 12:00]
select mt.dt,
mt.data
from MyTable mt
where mt.dt = case
when convert(time, getdate()) >= '07:30:00'
and convert(time, getdate()) < '12:00:00'
then dateadd(day, -1, convert(date, getdate()))
else convert(date, getdate())
end;
--> returns data from today OR yesterday depeding on time of execution (in/outside [07:30, 12:00])
Your question also mentions the EST timezone. If you want the current execution time to be interpreted as EST, then have a look a this question and this documentation page. Whether you should store data in EST instead of UTC in the first place is a whole other question... You will end up with something like this.
-- converting the query reference time to EST
declare #referenceDate smalldatetime = (select getdate() at time zone 'Eastern Standard Time');
select mt.dt,
mt.data
from MyTable mt
where mt.dt = case
when convert(time, #referenceDate) >= '07:30:00'
and convert(time, #referenceDate) < '12:00:00'
then dateadd(day, -1, convert(date, #referenceDate))
else convert(date, #referenceDate)
end;
Fiddle to see everything in action.

Related

Evaluating datetime into timewindows

Im trying to establish for any given datetime a tag that is purely dependent on the time part.
However because the time part is cyclic I cant make it work with simple greater lower than conditions.
I tried a lot of casting and shift one time to 24hour mark to kinda break the cycle However it just gets more and more complicated and still doesnt work.
Im using SQL-Server, here is the situation:
DECLARE #tagtable TABLE (tag varchar(10),[start] time,[end] time);
DECLARE #datetimestable TABLE ([timestamp] datetime)
Insert Into #tagtable (tag, [start], [end])
values ('tag1','04:00:00.0000000','11:59:59.9999999'),
('tag2','12:00:00.0000000','19:59:59.9999999'),
('tag3','20:00:00.0000000','03:59:59.9999999');
Insert Into #datetimestable ([timestamp])
values ('2022-07-24T23:05:23.120'),
('2022-07-27T13:24:40.650'),
('2022-07-26T09:00:00.000');
tagtable:
tag
start
end
tag1
04:00:00.0000000
11:59:59.9999999
tag2
12:00:00.0000000
19:59:59.9999999
tag3
20:00:00.0000000
03:59:59.9999999
for given datetimes e.g. 2022-07-24 23:05:23.120, 2022-07-27 13:24:40.650, 2022-07-26 09:00:00.000
the desired result would be:
date
tag
2022-07-25
tag3
2022-07-27
tag2
2022-07-26
tag1
As I wrote i tried to twist this with casts and adding and datediffs
SELECT
If(Datepart(Hour, a.[datetime]) > 19,
Cast(Dateadd(Day,1,a.[datetime]) as Date),
Cast(a.[datetime] as Date)
) as [date],
b.[tag]
FROM #datetimestable a
INNER JOIN #tagtable b
ON SomethingWith(a.[datetime])
between SomethingWith(b.[start]) and SomethingWith(b.[end])
The only tricky bit here is that your tag time ranges can go over midnight, so you need to check that your time is either between start and end, or if it spans midnight its between start and 23:59:59 or between 00:00:00 and end.
The only other piece is splitting your timestamp column into date and time using a CTE, to save having to repeat the cast.
;WITH splitTimes AS
(
SELECT CAST(timestamp AS DATE) as D,
CAST(timestamp AS TIME) AS T
FROM #datetimestable
)
SELECT
DATEADD(
day,
CASE WHEN b.[end]<b.start THEN 1 ELSE 0 END,
a.D) as timestamp,
b.[tag]
FROM [splitTimes] a
INNER JOIN #tagtable b
ON a.T between b.[start] and b.[end]
OR (b.[end]<b.start AND (a.T BETWEEN b.[start] AND '23:59:59.99999'
OR a.T BETWEEN '00:00:00' AND b.[end]))
Live example: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=506aef05b5a761afaf1f67a6d729446c
Since they're all 8-hour shifts, we can essentially ignore the end (though, generally, trying to say an end time is some specific precision of milliseconds will lead to a bad time if you ever use a different data type (see the first section here) - so if the shift length will change, just put the beginning of the next shift and use >= start AND < end instead of BETWEEN).
;WITH d AS
(
SELECT datetime = [timestamp],
date = CONVERT(datetime, CONVERT(date, [timestamp]))
FROM dbo.datetimestable
)
SELECT date = DATEADD(DAY,
CASE WHEN t.start > t.[end] THEN 1 ELSE 0 END,
CONVERT(date, date)),
t.tag
FROM d
INNER JOIN dbo.tagtable AS t
ON d.datetime >= DATEADD(HOUR, DATEPART(HOUR, t.start), d.date)
AND d.datetime < DATEADD(HOUR, 8, DATEADD(HOUR,
DATEPART(HOUR, t.start), d.date));
Example db<>fiddle
Here's a completely different approach that defines the intervals in terms of starts and durations rather than starts and ends.
This allows the creation of tags that can span multiple days, which might seem like an odd capability to have here, but there might be a use for it if we add some more conditions down the line. For example, say we want to be able say "anything from 6pm friday to 9am monday gets the 'out of hours' tag". Then we could add a day of week predicate to the tag definition, and still use the duration-based interval.
I have defined the duration granularity in terms of hours, but of course this can easily be changed
create table #tags
(
tag varchar(10),
startTimeInclusive time,
durationHours int
);
insert #tags
values ('tag1','04:00:00', 8),
('tag2','12:00:00', 8),
('tag3','20:00:00', 8);
create table #dateTimes (dt datetime)
insert #dateTimes
values ('2022-07-24T23:05:23.120'),
('2022-07-27T13:24:40.650'),
('2022-07-26T09:00:00.000');
select dt.dt,
t.tag
from #datetimes dt
join #tags t on cast(dt.dt as time) >= t.startTimeInclusive
and dt.dt < dateadd
(
hour,
t.durationHours,
cast(cast(dt.dt as date) as datetime) -- strip the time from dt
+ cast(t.startTimeInclusive as datetime) -- add back the time from t
);
Maybe I am looking at this to simple, but,
can't you just take the first tag with an hour greater then your hour in table datetimestable.
With an order by desc it should always give you the correct tag.
This will work well as long as you have no gaps in your tagtable
select case when datepart(hour, tag.tagStart) > 19 then dateadd(day, 1, convert(date, dt.timestamp))
else convert(date, dt.timestamp)
end as [date],
tag.tag
from datetimestable dt
outer apply ( select top 1
tt.tag,
tt.tagStart
from tagtable tt
where datepart(Hour, dt.timestamp) > datepart(hour, tt.tagStart)
order by tt.tagStart desc
) tag
It returns the correct result in this DBFiddle
The result is
date
tag
2022-07-25
tag3
2022-07-27
tag2
2022-07-26
tag1
EDIT
If it is possible that there are gaps in the table,
then I think the most easy and solid solution would be to split that row that passes midnight into 2 rows, and then your query can be very simple
See this DBFiddle
select case when datepart(hour, tag.tagStart) > 19 then dateadd(day, 1, convert(date, dt.timestamp))
else convert(date, dt.timestamp)
end as [date],
tag.tag
from datetimestable dt
outer apply ( select tt.tag,
tt.tagStart
from tagtable tt
where datepart(Hour, dt.timestamp) >= datepart(hour, tt.tagStart)
and datepart(Hour, dt.timestamp) <= datepart(hour, tt.tagEnd)
) tag

SQL Server query runs - but no data is returned

Although the following query runs, no data is being returned. The query splits columns into JudgmentMonth, Consumer and Commercial. My knowledge of SQL Server is not great but I'm guessing no date parameters are given hence no data is being returned. I'm not sure where date is to be manually entered after >= and <=
WITH dset AS
(
SELECT
COUNT(category) AS Volumes,
MONTH(creation_date) AS JudgmentMonth,
transaction_type,
REPLACE(record_type, 3, 2) AS RecordType
FROM
table
WHERE
transaction_type = 'jg'
AND category = 'CCJ'
AND CAST(creation_date AS DATE) >= CONVERT(VARCHAR(12), GETDATE(), 101)
AND CAST(creation_date AS DATE) <= CONVERT(VARCHAR(12), GETDATE(), 101)
GROUP BY
MONTH(creation_date), transaction_type, REPLACE(record_type, 3, 2)
)
SELECT
x.JudgmentMonth,
MAX(CASE x.RecordType WHEN '1' THEN x.Volumes END) CONSUMER,
MAX(CASE x.RecordType WHEN '2' THEN x.Volumes END) COMMERCIAL
FROM
(SELECT
r.JudgmentMonth, r.RecordType, r.Volumes
FROM
dset r) x
GROUP BY
x.JudgmentMonth
You are giving it dates: you give it getdate() twice!
creation_date >= getdate and creation_date <= getdate
is the same as
creation_date = getdate()
So unless you have rows with a creation_date exactly matching the current date/time, you won't get much at all.
Converting your column creation_date to a date is going to make your query non-SARGable. Assuming that what you want is data for tthe current date, I would suggest:
...
WHERE ...
AND creation_date >= CONVERT(date, GETDATE())
AND creation_date < CONVERT(date, DATEADD(DAY, 1, GETDATE()))
GROUP BY...
This will create a SARGable query, and thus perform far faster than converting both sides of the expression.

Daily report with stored procedure

I have this sp to make a report which needs to be daily. How can I implement the day part? It is OK like I wrote or that is some easy way?!
ALTER PROCEDURE [dbo].[pr_Report]
#YearOfRegistration INT
AS
SELECT
peCountryID,
peCountryName as coName,
ISNULL(SUM(CASE WHEN peIsSubmittedFL = 1 THEN 1 ELSE 0 END ),0) AS rdValue1,
ISNULL(SUM(CASE WHEN peIsSubmittedFL = 0 THEN 1 ELSE 0 END ),0) AS rdValue2,
COUNT(*) AS Total
FROM
vPerson
WHERE
#YearOfRegistration = 0
OR peYearOfRegistration = #YearOfRegistration
AND (DATEPART(dd, peSubmitDate) = DATEPART(dd, GETDATE())
AND DATEPART(MM, peSubmitDate) = DATEPART(MM, GETDATE())
AND DATEPART(yy, peSubmitDate) = DATEPART(YY, GETDATE()))
GROUP BY
peCountryofResidencyID, peCountryOfResidencyName
The logic is correct, but it is a really bad way to do it. Wherever possible to you should avoid calling functions on your data, especially in the where clause, because it means that any indexes on the underlying columns can not be used.
Your predicate would be better written as:
WHERE peSubmitDate >= CAST(GETDATE() AS DATE)
AND peSubmitDate < DATEADD(DAY, 1, CAST(GETDATE() AS DATE));
This way indexes can be used, and your query is sargable
As it happens, converting DATETIME to DATE (and vice versa) is actually an exception to the rule of not using functions, so you can shorten this to:
WHERE CONVERT(DATE, peSubmitDate) = CONVERT(DATE, GETDATE())
Another point is that although it might look better to use OR to accommodate both your options (of filtering by year or returning all records), you will find that having two separate queries will perform better. so your final SP might be:
ALTER PROC [dbo].[pr_Report] #YearOfRegistration INT
AS
BEGIN
IF (#YearOfRegistration = 0)
BEGIN
SELECT peCountryID,
peCountryName as coName,
ISNULL(SUM(CASE WHEN peIsSubmittedFL = 1 THEN 1 ELSE 0 END ),0) AS rdValue1,
ISNULL(SUM(CASE WHEN peIsSubmittedFL = 0 THEN 1 ELSE 0 END ),0) AS rdValue2,
COUNT(*) AS Total
FROM vPerson
WHERE CONVERT(DATE, peSubmitDate) = CONVERT(DATE, GETDATE())
GROUP BY peCountryofResidencyID,peCountryOfResidencyName
END
ELSE
BEGIN
SELECT peCountryID,
peCountryName as coName,
ISNULL(SUM(CASE WHEN peIsSubmittedFL = 1 THEN 1 ELSE 0 END ),0) AS rdValue1,
ISNULL(SUM(CASE WHEN peIsSubmittedFL = 0 THEN 1 ELSE 0 END ),0) AS rdValue2,
COUNT(*) AS Total
FROM vPerson
WHERE CONVERT(DATE, peSubmitDate) = CONVERT(DATE, GETDATE())
AND peYearOfRegistration = #YearOfRegistration
GROUP BY peCountryofResidencyID,peCountryOfResidencyName;
END
END
getdate returns a datetime, so if you want to compare just the date and not the time of day, you could use cast. If peSubmitDate is of datatype Date , use this comparison:
peSubmitDate = cast(GetDate() as date)
If it is a datatime then use it like this:
cast(peSubmitDate as date) = cast(GetDate() as date)
The latter one gives worse performance, so only use this if is a datetime

Modify Date Function Based on Where Clause SQL Server

Currently my case statement is based on the first day of the current month.
SELECT
[TicketNbr] AS 'Ticket Nbr'
-- 1st day of this month
, ( CASE WHEN [date_entered] >= DATEADD(day,-1, GETDATE()) THEN 1 ELSE 0 END) AS '1=Opened Within Last 24HR 0=No'
FROM [v_rpt_Service] WITH(NOLOCK)
WHERE ([date_entered] >= '2017-04-01T11:24:00.000' AND [date_entered] < '2017-05-01T11:24:00.000')
GROUP BY [TicketNbr]
But I want to modify it so it will adjust so it will based on the date range
in the where clause. (And that can be any date ranges.)
How do I do this without variables?
Sounds like you just need variables
declare #startDate datetime = '2017-04-01 11:24:00'
declare #endDate datetime = '2017-05-01 11:24:00'
SELECT
[TicketNbr] AS 'Ticket Nbr'
-- 1st day of this month
, ( CASE WHEN [date_entered] between #startDate and #endDate THEN 1 ELSE 0 END) AS '1=Opened Within Date Range 0= No'
FROM [v_rpt_Service] WITH(NOLOCK)
WHERE ([date_entered] >= startDate AND [date_entered] < #endDate)
GROUP BY [TicketNbr]

SQL : If first day of the new month then run report for previous month

I have some reports which run showing data MTD
Here is the code that is not working how I would like
StartDate = select dateadd(s,0,dateadd(mm, datediff(m,0,getdate()),0))
EndDate = getdate()
Our data replication happens at the end of each day.
So on the First day of each month I don't want a blank report to run.
what I would like to happen.
Only If its the first day of the month then the StartDate must be beginning of last month and EndDate to be end of last month. Else use
StartDate = select dateadd(s,0,dateadd(mm, datediff(m,0,getdate()),0)) and
EndDate = getdate()
Not exactly sure about your main query. This is how you could get first and last day of last month depending on the given date being 1st of current month.
Please note, else part of each case expression setting the current date for both first and last date. You can set them as null if needed.
DECLARE #Today DATETIME = GETDATE()
DECLARE #FirsDay DATETIME = CASE WHEN DATEPART(DAY, #Today) = 1
THEN DATEADD(MONTH, -1, #Today) --first day of last month
ELSE #Today END --current date for other dates
DECLARE #LastDay DATETIME = CASE WHEN DATEPART(DAY, #Today) = 1
THEN DATEADD(DAY, -1, #Today) --last date of last month
ELSE #Today END --current date for other dates
Thanks all.
so the rabbit hole got a bit deeper
Example : What about when the 1st falls on a Saturday ?
I need it to run using the last trading day if its the first day of the new month.
what I ended up using was a function that uses our working hours table
ALTER FUNCTION [data].[Last_Trade_Day] (#Date date)
returns date as
begin
declare #OrigDate date = isnull(#Date, getdate())
return (
select max(convert(date, wh_starttime))
from Embrace.fact.Working_Hours
where convert(date, wh_starttime) < #OrigDate
)
end
The code in the report now look's like :
declare #EndDate date = Embrace.data.Last_Trade_Day(isnull(#Date, getdate()))
declare #StartDate date = dateadd(mm, 0, dateadd(mm, datediff(m, 0, #EndDate), 0))
You can try to use something like this:
-- Create demo data
CREATE TABLE #dates(get_date_simulation datetime)
INSERT INTO #dates(get_date_simulation)
VALUES (N'2015-07-01 13:46:47.063'), -- fallback to 2015-06-01
(N'2015-07-02 13:46:47.063') -- use this date normal
-- Your part
SELECT get_date_simulation,
CASE
WHEN DATEPART(day,get_date_simulation) = 1
THEN DATEADD(month,-1,DATEADD(day,(DATEPART(day,get_date_simulation)-1)*-1,get_date_simulation))
ELSE DATEADD(day,(DATEPART(day,get_date_simulation)-1)*-1, get_date_simulation)
END as start_date,
CASE
WHEN DATEPART(day,get_date_simulation) = 1
THEN DATEADD(second,-1,CONVERT(datetime,CONVERT(date,get_date_simulation)))
ELSE get_date_simulation
END as end_date
FROM #dates
-- Cleanup
DROP TABLE #dates
Which results into this:
get_date_simulation start_date end_date
----------------------- ----------------------- -----------------------
2015-07-01 13:46:47.063 2015-06-01 13:46:47.063 2015-06-30 23:59:59.000
2015-07-02 13:46:47.063 2015-07-01 13:46:47.063 2015-07-02 13:46:47.063
To me it seems as simple as subtracting 1 day from the current date to get the start date
SELECT StartDate =
DATEADD(s,0,DATEADD(mm,DATEDIFF(m,0,GETDATE() - 1),0))
Then you just get the end date using the current date without the time
SELECT EndDate =
CONVERT(DATE, GETDATE()
Then your query is WHERE date >= StartDate and < EndDate

Resources