SQL case statement closest to current date - sql

Need help create a case statement to find the closest date from date table. My data: https://imgur.com/hkBu4SA
I basically want to set:
Y flag if it's closest to today's date from a.FROM_EFFDT and is not null.
F if to_effdate is null
else N
WHEN a.FROM_EFFDT < GETDATE() AND (to_effdate) IS NOT NULL THEN 'Y'
WHEN to_effdate IS NULL THEN 'F'
ELSE 'N'

You can use window functions:
(case when row_number() over (order by abs(datediff(day, getdate(), to_effdate)) = 1
then 'Y'
when to_effdate is null then 'F'
else 'N'
end)

You may be able to accomplish it with something like this. Though this isn't bulletproof, you could get duplicates if the closest date is tied.
create table Dates (from_effdt datetime, to_effdt datetime, flag varchar(1))
insert Dates (from_effdt, to_effdt, flag)
values
('2019-03-16', null, '') ,
('2018-06-14', '2019-03-16', '') ,
('2018-05-14', '2018-06-14', '') ,
('2018-01-01', '2018-05-14', '')
select * from Dates
UPDATE Dates
SET flag =
CASE
WHEN from_effdt = (
select top 1 from_effdt
from Dates
order by ABS ( DATEDIFF(day, from_effdt, getdate()) )
)
THEN 'Y'
ELSE
'N'
END
*update, not sure why I created it as an update. This select should do.
SELECT from_effdt, to_effdt,
CASE
WHEN from_effdt = (
select top 1 from_effdt
from Dates
order by ABS ( DATEDIFF(day, from_effdt, getdate()) )
)
THEN 'Y'
ELSE
'N'
END [numberOfDaysAway]
FROM Dates

You can simply do this:
CASE
WHEN from_effdt = (
select from_effdt
from Dates
where abs(datediff(second, from_effdt, getdate()))
= (select min(
abs(datediff(second, from_effdt, getdate()))
)
from Dates)
)
THEN 'Y'
ELSE
'N'
END

ROW_NUMBER() Over (Partition by id order by to_effdt desc)
,id
,from_effdt
,to_effdt
, CASE WHEN (ROW_NUMBER() Over (Partition by id order by to_effdt desc) = 1) THEN ('Y')
WHEN (to_effdt IS NULL) THEN ('F') ELSE ('N') End as flag
from a

Related

Selecting count of consecutives dates before and after a specified date based on start/end

I'm trying to determine the number of records with consecutive dates (previous record ends on the same date as the start date of the next record) before and after a specified date, and ignore any consecutive records as soon as there is a break in the chain.
If I have the following data:
-- declare vars
DECLARE #dateToCheck date = '2020-09-20'
DECLARE #numRecsBefore int = 0
DECLARE #numRecsAfter int = 0
DECLARE #tempID int
-- temp table
CREATE TABLE #dates
(
[idx] INT IDENTITY(1,1),
[startDate] DATETIME ,
[endDate] DATETIME,
[prevEndDate] DATETIME
)
-- insert temp table
INSERT INTO #dates
( [startDate], [endDate] )
VALUES ( '2020-09-01', '2020-09-04' ),
( '2020-09-04', '2020-09-10' ),
( '2020-09-10', '2020-09-16' ),
( '2020-09-17', '2020-09-19' ),
( '2020-09-19', '2020-09-20' ),
--
( '2020-09-20', '2020-09-23' ),
( '2020-09-25', '2020-09-26' ),
( '2020-09-27', '2020-09-28' ),
( '2020-09-28', '2020-09-30' ),
( '2020-10-01', '2020-09-05' )
-- update with previous records endDate
DECLARE #maxRows int = (SELECT MAX(idx) FROM #dates)
DECLARE #intCount int = 0
WHILE #intCount <= #maxRows
BEGIN
UPDATE #dates SET prevEndDate = (SELECT endDate FROM #dates WHERE idx = (#intCount - 1) ) WHERE idx=#intCount
SET #intCount = #intCount + 1
END
-- clear any breaks in the chain?
-- number of consecutive records before this date
SET #numRecsBefore = (SELECT COUNT(idx) FROM #dates WHERE startDate = prevEndDate AND endDate <= #dateToCheck)
-- number of consecutive records after this date
SET #numRecsAfter = (SELECT COUNT(idx) FROM #dates WHERE startDate = prevEndDate AND endDate >= #dateToCheck)
-- return & clean up
SELECT * FROM #dates
SELECT #numRecsBefore AS numBefore, #numRecsAfter AS numAfter
DROP TABLE #dates
With the specified date being '2020-09-20, I would expect #numRecsBefore = 2 and #numRecsAfter = 1. That is not what I am getting, as its counting all the consecutive records.
There has to be a better way to do this. I know the loop isn't optimal, but I couldn't get LAG() or LEAD() to work. I've spend all morning trying different methods and searching, but everything I find doesn't deal with two dates, or breaks in the chain.
This reads like a gaps-and-island problem. Islands represents rows whose date ranges are adjacent, and you want to count how many records preceed of follow a current date in the same island.
You could do:
select
max(case when #dateToCheck > startdate and #dateToCheck <= enddate then numRecsBefore end) as numRecsBefore,
max(case when #dateToCheck >= startdate and #dateToCheck < enddate then numRecsAfter end) as numRecsAfter
from (
select d.*,
count(*) over(partition by grp order by startdate) as numRecsBefore,
count(*) over(partition by grp order by startdate desc) as numRecsAfter
from (
select d.*,
sum(case when startdate = lag_enddate then 0 else 1 end) over(order by startdate) as grp
from (
select d.*,
lag(enddate) over(order by startdate) as lag_enddate
from #dates d
) d
) d
) d
This uses lag() and a cumulative sum() to define the islands. The a window count gives the number and preceding and following records on the same island. The final step is conditional aggrgation; extra care needs to be taken on the inequalities to take in account various possibilites (typically, the date you search for might not always match a range bound).
Demo on DB Fiddle
I think this is what you are after, however, this does not give the results in your query; I suspect that is because they aren't the expected results? One of the conditional aggregated may also want to be a >= or <=, but I don't know which:
WITH CTE AS(
SELECT startDate,
endDate,
CASE startDate WHEN LAG(endDate) OVER (ORDER BY startDate ASC) THEN 1 END AS IsSame
FROM #dates d)
SELECT COUNT(CASE WHEN startDate < #dateToCheck THEN IsSame END) AS numBefore,
COUNT(CASE WHEN startDate > #dateToCheck THEN IsSame END) AS numAfter
FROM CTE;

How to calculate average of variables in SQL Server

I am trying to calculate date ranges between rows and then get an average.
declare #date0 date = (
select top 1 my_date
from someTable
order by my_date desc)
declare #date1 date = (
select my_date
from someTable
order by my_date desc
offset 1 rows
fetch next 1 row only)
declare #date2 date = (
select my_date
from someTable
order by my_date desc
offset 2 rows
fetch next 1 row only)
declare #date3 date = (
select my_date
from someTable
order by my_date desc
offset 3 rows
fetch next 1 row only)
select
[Range 1] = dateDiff(day, #date1, #date0),
[Range 2] = dateDiff(day, #date2, #date1),
[Range 3] = dateDiff(day, #date3, #date2),
[Avg Range] = avg(
nullIf(#date0, 0),
nullIf(#date1, 0),
nullIf(#date2, 0),
nullIf(#date3, 0)
)
The range calculations work fine, but a bit clumsy.
However, I'm not sure how to handle the average. It looks like the function is supposed to run against a table and not an array, but I was having issues inserting the variables into a temp table column.
How can I get the average of these ranges (not including range = 0)?
Use UNION ALL to return a row for each case:
select avg(t.[Range]) [Avg Range]
from (
select dateDiff(day, #date1, #date0) [Range]
union all
select dateDiff(day, #date2, #date1)
union all
select dateDiff(day, #date3, #date2)
) t
where t.[Range] <> 0
AVG is an aggregate function, designed to be used with GROUP BY or windows. You can simply do the math in your query:
select
[Range 1] = dateDiff(day, #date1, #date0),
[Range 2] = dateDiff(day, #date2, #date1),
[Range 3] = dateDiff(day, #date3, #date2),
[Avg Range] = (
nullIf(#date0, 0) +
nullIf(#date1, 0) +
nullIf(#date2, 0) +
nullIf(#date3, 0)
) /
(
CASE WHEN #date0 IS NULL THEN 0 ELSE 1 END +
CASE WHEN #date1 IS NULL THEN 0 ELSE 1 END +
CASE WHEN #date2 IS NULL THEN 0 ELSE 1 END +
CASE WHEN #date3 IS NULL THEN 0 ELSE 1 END
)
No reason to use four different queries:
with dates as (
select
row_number() over (order by my_date desc) rn,
datediff(days, lag(my_date) over (order by my_date desc), my_date) diff
from T
)
select avg(nullif(diff, 0)) from dates where rn <= 3;
or
with dates as (
select
datediff(days, lag(my_date) over (order by my_date desc), my_date) diff
from T
order by my_date desc
fetch next three rows only
)
select avg(nullif(diff, 0)) from dates;
Using distinct would also let you easily get the top three dates and not have to mess around with nullif().
with dates as (
select distinct
datediff(days, lag(my_date) over (order by my_date desc), my_date) diff
from T
order by my_date desc
fetch next three rows only
)
select avg(diff) from dates;

SQL: Cast Not working in SubString

We have a string field for date of birth and now we have to convert it in order to perform the calculations required. However when we are using CAST or CONVERT to convert to perform the calculations it is not working.
select distinct(ptr.RecordID)
from dbo.PatientRecord as ptr
where
ptr.CHName like 'Access2Loc%'
AND ptr.RecordID
in(
select
(
case when
(DATEDIFF(hour, convert(date,DOB,110), GETDATE())/8766)>18
then PatientID
else NULL
end
) as RecordID
from
PatientView
where ISDATE(DOB) = 1
)
SQL Server considers this a "feature". It is hard to explain, but the where is not necessarily executed before the select.
In SQL Server 2012+, use try_convert() (or try_cast():
where ptr.CHName like 'Access2Loc%' and
ptr.RecordID in (select (case when DATEDIFF(hour, try_convert(date, DOB, 110), GETDATE()) / 8766 > 18
then PatientID
end) as RecordID
from PatientView
where ISDATE(DOB) = 1
)
In more ancient versions, you can use a case expression for much the same effect.
-- this will guarantee that ##t1 contains only valid dob rows
if object_id('tempdb..##t1') is not null
drop table #t1
select * into ##t1 from patientview where isdate(dob)=1
select distinct(ptr.RecordID)
from dbo.PatientRecord as ptr
where
ptr.CHName like 'Access2Loc%'
AND ptr.RecordID
in(
select
(
case when
(DATEDIFF(hour, convert(date,DOB,110), GETDATE())/8766)>18
then PatientID
else NULL
end
) as RecordID
from
##t1
)
drop table ##t1

ADJUST/EDIT SQL QUERY ASSISTANCE

I have the below query which calculates from the first requested date to first completed date how much time elapsed.
However in the example below there is a requested date of 2017-02-02 (backdated) which was created on the 2017-02-06.
I want to edit the query to select the requested date and calculate how much time elapsed until completed from the FIRST CREATED DATE
So right now it returns 1 when it should be 0
Reasoning, because first created date is 2017-02-03 and it was requested and completed on the same day 2017-02-03
Any help is appreciated greatly!
CREATE TABLE #temp
(Identifier VARCHAR(40) NOT NULL,
Created_Date DATETIME NOT NULL,
Requested_Date DATETIME NOT NULL,
Completed_Date DATETIME NULL,
SN_Type VARCHAR(20) NOT NULL,
SN_Status VARCHAR(20) NOT NULL
);
INSERT INTO #temp
VALUES
('11111',
'20170203',
'20170203',
'20170203',
'Re-Activattion',
'COMP'
);
INSERT INTO #temp
VALUES
('11111',
'20170206',
'20170202',
NULL,
'Re-Activattion',
'N-CO'
);
SELECT *
FROM #temp;
-- calculate/identify Order start and Order End records
WITH cte
AS (
-- 1st Order start record i.e. earliest record in the table for a given "Identifier"
SELECT Identifier,
MIN(Created_Date) AS Created_Date,
CONVERT(VARCHAR(30), 'Created') AS RecordType,
1 AS OrderNumber
FROM #temp
GROUP BY Identifier
UNION ALL
-- All records with "COMP" status are treated as order completed events. Add 2 weeks to the completed date to create a "dummy" Order End Date
SELECT Identifier,
DATEADD(WEEK, 2, Created_Date) AS Created_Date,
'Completed' AS RecordType,
ROW_NUMBER() OVER(PARTITION BY Identifier ORDER BY Created_Date) AS OrderNumber
FROM #temp
WHERE SN_STATUS = 'COMP'
UNION ALL
-- Set the start period of the next order to be right after (3 ms) the previous Order End Date
SELECT Identifier,
DATEADD(ms, 3, DATEADD(WEEK, 2, Created_Date)) AS Created_Date,
'Created' AS RecordType,
ROW_NUMBER() OVER(PARTITION BY Identifier ORDER BY Created_Date) + 1 AS OrderNumber
FROM #temp
WHERE SN_STATUS = 'COMP'),
-- Combine Start / End records into one record
OrderGroups
AS (
SELECT Identifier,
OrderNumber,
MIN(Created_Date) AS OrderRangeStartDate,
MAX(Created_Date) AS OrderRangeEndDate
FROM cte
GROUP BY Identifier,
OrderNumber)
SELECT a.Identifier,
a.OrderNumber,
OrderRangeStartDate,
OrderRangeEndDate,
CASE
WHEN SUM(CASE
WHEN SN_STATUS = 'COMP'
AND SN_TYPE = 'Re-Activattion'
THEN 1
ELSE 0
END) > 0
THEN STR(DATEDIFF(day, MIN(CASE
WHEN SN_TYPE = 'Re-Activattion'
THEN Requested_Date
ELSE NULL
END), MIN(CASE
WHEN(SN_TYPE = 'Re-Activattion'
AND SN_STATUS = 'COMP')
THEN Completed_Date
ELSE NULL
END)))
WHEN SUM(CASE
WHEN SN_TYPE = 'Re-Activattion'
THEN 1
ELSE 0
END) > 0
THEN 'NOT COMP'
ELSE 'NO RE-ACT'
END AS RE_ACT_COMPLETION_TIME,
SUM(CASE
WHEN SN_STATUS = 'N-CO'
THEN 1
ELSE 0
END) AS [RE-AN NCO #]
FROM OrderGroups AS a
INNER JOIN #Temp AS b ON a.Identifier = b.Identifier
AND a.OrderRangeStartDate <= b.Created_Date
AND b.Created_Date <= a.OrderRangeEndDate
GROUP BY a.Identifier,
a.OrderNumber,
OrderRangeStartDate,
OrderRangeEndDate;
I want to edit the query to select the requested date and calculate
how much time elapsed until completed from the FIRST CREATED DATE
Change this:
,MIN(case
when SN_TYPE = 'Re-Activattion'
then Requested_Date
else null
end
To this:
,MIN(case
when SN_TYPE = 'Re-Activattion'
then Created_Date
else null
end

Iterate value dynamically

I'm using the below query to calculate a budget value dynamically means iterating upto selected date value.
SUM(case when Name = 'Budget' then Value + ((Value/#TotaldaysinMonth) *
#DaysPastinMonth) end) as [Budget]
Here variable #DaysPastinMonth should be dynamic. Means if I select a date as 03/31/2017. Then the query should run upto the previous month value. Another example is if I select August, then I need to run query from Jan-Aug.
For Jan
SUM(case when Name = 'Budget' then Value + ((Value/#TotaldaysinMonth) *
#DaysPastinJanMonth) end) as [Budget]
For Feb
SUM(case when Name = 'Budget' then Value + ((Value/#TotaldaysinMonth) *
#DaysPastinFebMonth) end) as [Budget]
For Mar
SUM(case when Name = 'Budget' then Value + ((Value/#TotaldaysinMonth) *
#DaysPastinMarMonth) end) as [Budget]
Also I have created variables for all the 12 months which holds DaysPastinMonth.
Can anyone suggest how this can be achieved using case statement.
You are thinking about this in loop when you could do it with set based operations.
----------------------------------------------------------
--Create a table of dates for testing
----------------------------------------------------------
if object_id('tempdb..#dates') is not null
drop table #dates
create table #dates(d date
,RN bigint)
declare #sdate datetime='2017-01-01 00:00'
declare #edate datetime='2017-7-31 00:00'
insert into #dates
select
DATEADD(d,number,#sdate)
,row_number() over (order by (select null)) as RN
from
master..spt_values
where
type='P'
and number<=datediff(d,#sdate,#edate)
declare #numOfDays int = (select count(*) from #dates)
----------------------------------------------------------
--Populate Test Data
----------------------------------------------------------
if object_id('tempdb..#testTable') is not null
drop table #testTable
create table #testTable([Name] varchar(64),
[Value] decimal (16,4),
DT datetime)
insert into #testTable ([Name],[Value],DT)
select
'Budget'
,r.randomNumber
,d.d
from
#dates d
inner join
(SELECT TOP (select #numOfDays)
randomNumber,
row_number() over (order by (select null)) as RN
FROM (
SELECT CAST(ABS(CAST(NEWID() AS binary(6)) %100000) + RAND() AS DECIMAL (16,4)) + 1 randomNumber
FROM sysobjects) sample
GROUP BY randomNumber
ORDER BY randomNumber DESC) r on r.RN = d.RN
union all
select
'Not The Budget'
,r.randomNumber
,d.d
from
#dates d
inner join
(SELECT TOP (select #numOfDays)
randomNumber,
row_number() over (order by (select null)) as RN
FROM (
SELECT CAST(ABS(CAST(NEWID() AS binary(6)) %100000) + RAND() AS DECIMAL (16,4)) + 1 randomNumber
FROM sysobjects) sample
GROUP BY randomNumber
ORDER BY randomNumber DESC) r on r.RN = d.RN
----------------------------------------------------------
--Instead of making your variables "dynamic" which
--would likely consist of some loop, just pass in the
--month you care about and let SQL do the work
----------------------------------------------------------
declare #month datetime = '2016-03-31'
select
DT
,[Value]
,[Name]
,sum(case when [Name] = 'Budget'
then [Value] +
(([Value] / (DATEDIFF(day,DATEADD(month, DATEDIFF(month, 0, #month), 0),#month)))
*
(DATEDIFF(DAY,DATEADD(MONTH, DATEDIFF(MONTH, 0, #month)-1, 0),DATEADD(MONTH, DATEDIFF(MONTH, -1, #month)-1, -1)))) end) as Budget
from
#testTable
where
DT >= DATEADD(yy, DATEDIFF(yy, 0, #month), 0) --this is Jan 1 of the year associated with your vairable
group by
DT
,[Name]
,[Value]