MSSQL Date Analysis - sql

I need to write a query where it looks at a plethora of dates and determines if that date was 3 or more years ago, 2 or more years ago, 1 or more year ago, 6 or more months ago, or less than 6 months ago.
Is there a way to do this without writing in physical dates, so that the analysis can be run again later without needing to change the dates?
I have not started to write the query yet, but I have been trying to map it out first.

You should use case. I would recommend something like:
select (case when datecol < dateadd(year, -3, getdate()) as '3 years ago'
when datecol < dateadd(year, -2, getdate()) as '2 years ago'
. . .
end)
I specifically do not recommend using datediff(). It is counterintuitive because it counts the number of "boundaries" between two dates. So, 2016-12-31 and 2017-01-01 are one year apart.

You can use the DATEDIFF function to calculate the number of months, days, years, etc. between two dates, e.g.
select datediff(day, '2016-01-01', '2017-01-01')
returns 366, because 2016 was a leap year
To get the current date, use the GETDATE() function.

I tend to use a generic Tier Table for several reasons.
Logic is moved from code.
Alternate Tiers may be deployed depending on your audience.
Most importantly, things change.
The following will generate a series of dates, and then summarize by the desired tier. I should add, this is a simplified example
Example
-- Create Sample Tier Table
Declare #Tier table (Tier_Group varchar(50),Tier_Seq int,Tier_Title varchar(50),Tier_R1 int,Tier_R2 int)
Insert into #Tier values
('MyAgeTier',1,'+3 Years' ,36,999999)
,('MyAgeTier',2,'2 - 3 Years' ,24,36)
,('MyAgeTier',3,'1 - 2 Years' ,12,24)
,('MyAgeTier',4,'6 Mths - 1 Year',6 ,12)
,('MyAgeTier',5,'<6 Mths' ,0 ,6)
,('MyAgeTier',6,'Total ' ,0 ,999999)
Select Tier_Title
,Dates = count(*)
,MinDate = min(D)
,MaxDate = max(D)
From #Tier A
Join (
-- Your Actual Source
Select Top (DateDiff(DAY,'2010-01-01','2017-07-31')+1)
D=cast(DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),'2010-01-01') as date)
From master..spt_values n1,master..spt_values n2
) B
On Tier_Group = 'MyAgeTier' and DateDiff(MONTH,D,GetDate()) between Tier_R1 and Tier_R2-1
Group By Tier_Title,Tier_R1
Order by Tier_R1 Desc
Returns (this example)

Related

How to get six weeks data from a week column?

I have a legacy query in which I am looking data for six weeks as shown below. In my below AND condition I get data for past six weeks and it worked fine in 2020 middle and end. But since 2021 started, this stopped working because of obvious subtraction I am doing with 6.
AND data.week_col::integer BETWEEN DATE_PART(w, CURRENT_DATE) - 6 AND DATE_PART(w, CURRENT_DATE) - 1
There is a bug in above query because of which it stopped working in 2021. How can I change above condition so that it can work entire year without any issues and give me data for past 6 weeks.
Update
Below is my query which I am running:
select *,
dateadd(d, - datepart(dow, trunc(CONVERT_TIMEZONE('UTC','PST8PDT',client_date))), trunc(CONVERT_TIMEZONE('UTC','PST8PDT',client_date)) + 6) as day,
date_part(week, day) as week_col
from holder data
where data.week_col::integer BETWEEN DATE_PART(w, CURRENT_DATE) - 6 AND DATE_PART(w, CURRENT_DATE) - 1
client_date column has values like this - 2021-01-15 21:30:00.0. And from that I get value of day column and from day column I get value of
week_col column as shown above.
week_col column has values like 53, 52 .... It's a week number in general.
Because of my AND condition I am getting data for week 1 only but technically I want data for 49, 50, 51, 52, 53 and 1 as it is past six weeks. Can I use day column here to get correct past six weeks?
Would this serve as a solution? I do not know much about the redshirt syntax but I read it supports dateadd(). If you are normalizing client_date to a time zone converted day with no time then why not simply use that in the comparison to the current date converted to the same time zone.
WHERE
client_date BETWEEN
DATEADD(WEEK,-6,trunc(CONVERT_TIMEZONE('UTC','PST8PDT',CURRENT_DATE)))
AND
DATEADD(WEEK,-1,trunc(CONVERT_TIMEZONE('UTC','PST8PDT',CURRENT_DATE)))
If the above logic works out then you may want to convert the -6 and -1 week to variables, if that is supported.
Solution 2
This is a bit more verbose but involves virtualizing a calender table and then joining your current date parameter into the calender data, for markers. Finally, you can join your data against the calender which has been normalized by weeks in time chronologically.
This is SQL Server syntax, however, I am certain it can be converted to RS.
DECLARE #D TABLE(client_date DATETIME)
INSERT #D VALUES
('11/20/2020'),('11/27/2020'),
('12/4/2020'),('12/11/2020'),('12/18/2020'),('12/25/2020'),
('01/8/2021'),('01/8/2021'),('1/15/2021'),('1/22/2021'),('1/29/2021')
DECLARE #Date DATETIME = '1/23/2021'
DECLARE #StartDate DATETIME = '01/01/2010'
DECLARE #NumberOfDays INT = 6000
;WITH R1(N) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
R2(N) AS (SELECT 1 FROM R1 a, R1 b),
R3(N) AS (SELECT 1 FROM R2 a, R2 b),
Tally(Number) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM R3)
,WithTally AS
(
SELECT CalendarDate = DATEADD(DAY,T.Number,#StartDate)
FROM Tally T
WHERE T.Number < #NumberOfDays
)
,Calendar AS
(
SELECT
CalendarDate,
WeekIndex = DENSE_RANK() OVER(ORDER BY DATEPART(YEAR, CalendarDate), DATEPART(WEEK, CalendarDate))
FROM
WithTally
),
CalendarAlignedWithCurrentDateParamater AS
(
SELECT *
FROM
Calendar
CROSS JOIN (SELECT WeekIndexForToday=WeekIndex FROM Calendar WHERE Calendar.CalendarDate=#Date ) AS X
)
SELECT
D.*,
C.WeekIndex,
C.WeekIndexForToday
FROM
CalendarAlignedWithCurrentDateParamater C
INNER JOIN #D D ON D.client_date = C.CalendarDate
WHERE
C.WeekIndex BETWEEN C.WeekIndexForToday-6 AND C.WeekIndexForToday-1
OPTION (MAXRECURSION 0)

Weekly find events

I need to look back two years by week to find if there have been multiple events for a week. Like if 7/1/19-7/8/19 if there was 3 events that member would appear. Is there any way to do this other than a giant case statement for each week? Like case when event = y and event_date between todate('07/01/2019','mm/dd/yyyy') and todate('07/08/2019','mm/dd/yyyy') then 1 else 0 end. Would I need to do that for all 104 weeks?
select distinct prov
,svcdate
,svccod
,membno
,unitct
from claim
where svcdate > '20170911'
That query returns the name- bike shop, svcdate(event date)- '20180812, code - h499, member id- 456, and units- 5
Thanks.
You can group by the week of the year, and the year itself. The HAVING clause then allows you to filter based on aggregates:
--Set up example test table and populate with example data
DECLARE #Events TABLE (eventDate DATETIME)
INSERT INTO #Events VALUES ('20180101'), ('20180102'), ('20180103')
SELECT DATEPART(wk, eventDate), DATEPART(year, eventDate)
FROM #Events
WHERE eventDate BETWEEN #START_DATE AND #END_DATE
GROUP BY DATEPART(wk, eventDate), DATEPART(year, eventDate)
having COUNT(1) > 1
The above will return the week number and the year, and you can convert that into a more friendly form.
Edit: Having thought about this again - it could depend if a) you're just interested in weeks with multiple events or b) with more than a specified number of events. If a), you could adopt a simpler WHERE EXISTS (I wasn't sure what your PK was in that table, so have called it claimID):
SELECT *
FROM claim c
WHERE EXISTS (SELECT 1
FROM claim c2
WHERE c.claimID > c2.claimID
and (DATEPART(dw, c.svcdate) = DATEPART(dw, c2.svcdate) AND DATEPART(year, c.svcdate) = DATEPART(year, c2.svcdate))
The above falls down if you're after weeks with n or more events in it - then I think the first example works better.
It also depends on what output you're after - are you after the claim rows themselves, or just a list of dates?

working days between two date firebird

I need to count working days between two date in firebird base(ver 2.5)
I have table(table_date) with working days (date, day--Free/Working)
and i have another table with start_date and end_date.
For example we have two dates start_date=2015-04-04 and end_date=2015-04-10
Day 2015-04-05 and 2015-04-06 are Free.
Between this dates is 6 days but 4 is working days.
how to calculate this in base?
Based on the information you provided, I would guess that something like this should work:
select a.start_date, a.end_date,
(select count(*)
from working_days
where "DATE" between a.start_date and a.end_date
and "DAY" = 'working') as nr_of_workdays
from start_end a

Calculating Open incidents per month

We have Incidents in our system with Start Time and Finish Time and project name (and other info) .
We would like to have report: How many Incidents has 'open' status per month per project.
Open status mean: Not finished.
If incident is created in December 2009 and closed in March 2010, then it should be included in December 2009, January and February of 2010.
Needed structure should be like this:
Project Year Month Count
------- ------ ------- -------
Test 2009 December 2
Test 2010 January 10
Test 2010 February 12
....
In SQL Server:
SELECT
Project,
Year = YEAR(TimeWhenStillOpen),
Month = DATENAME(month, MONTH(TimeWhenStillOpen)),
Count = COUNT(*)
FROM (
SELECT
i.Project,
i.Incident,
TimeWhenStillOpen = DATEADD(month, v.number, i.StartTime)
FROM (
SELECT
Project,
Incident,
StartTime,
FinishTime = ISNULL(FinishTime, GETDATE()),
MonthDiff = DATEDIFF(month, StartTime, ISNULL(FinishTime, GETDATE()))
FROM Incidents
) i
INNER JOIN master..spt_values v ON v.type = 'P'
AND v.number BETWEEN 0 AND MonthDiff - 1
) s
GROUP BY Project, YEAR(TimeWhenStillOpen), MONTH(TimeWhenStillOpen)
ORDER BY Project, YEAR(TimeWhenStillOpen), MONTH(TimeWhenStillOpen)
Briefly, how it works:
The most inner subselect, that works directly on the Incidents table, simply kind of 'normalises' the table (replaces NULL finish times with the current time) and adds a month difference column, MonthDiff. If there can be no NULLs in your case, just remove the ISNULL expression accordingly.
The outer subselect uses MonthDiff to break up the time range into a series of timestamps corresponding to the months where the incident was still open, i.e. the FinishTime month is not included. A system table called master..spt_values is also employed there as a ready-made numbers table.
Lastly, the main select is only left with the task of grouping the data.
A useful technique here is to create either a table of "all" dates (clearly that would be infinite so I mean a sufficiently large range for your purposes) OR create two tables: one of all the months (12 rows) and another of "all" years.
Let's assume you go for the 1st of these:
create table all_dates (d date)
and populate as appropriate. I'm going to define your incident table as follows
create table incident
(
incident_id int not null,
project_id int not null,
start_date date not null,
end_date date null
)
I'm not sure what RDBMS you are using and date functions vary a lot between them so the next bit may need adjusting for your needs.
select
project_id,
datepart(yy, all_dates.d) as "year",
datepart(mm, all_dates.d) as "month",
count(*) as "count"
from
incident,
all_dates
where
incident.start_date <= all_dates.d and
(incident.end_date >= all_dates.d or incident.end_date is null)
group by
project_id,
datepart(yy, all_dates.d) year,
datepart(mm, all_dates.d) month
That is not going to quite work as we want as the counts will be for every day that the incident was open in each month. To fix this we either need to use a subquery or a temporary table and that really depends on the RDBMS...
Another problem with it is that, for open incidents it will show them against all future months in your all_dates table. adding a all_dates.d <= today solves that. Again, different RDBMSs have different methods of giving back now/today/systemtime...
Another approach is to have an all_months rather than all_dates table that just has the date of first of the month in it:
create table all_months (first_of_month date)
select
project_id,
datepart(yy, all_months.first_of_month) as "year",
datepart(mm, all_months.first_of_month) as "month",
count(*) as "count"
from
incident,
all_months
where
incident.start_date <= dateadd(day, -1, dateadd(month, 1, first_of_month)
(incident.end_date >= first_of_month or incident.end_date is null)
group by
project_id,
datepart(yy, all_months.first_of_month),
datepart(mm, all_months.first_of_month)

SQL date subtraction

In SQL, I'd like to list all funds whose anniversary is due this year in 2 months time. What is the syntax?
SELECT *
FROM dbo.Funds
WHERE AnniversaryDate <= DATEADD(MONTH, 2, GETDATE())
That should work in SQL Server 2000 and up.
Marc
SELECT DATE_ADD(CURDATE(), INTERVAL 2 MONTH);
This will do it in MySQL. I haven't added the anniversary comparison because I don't know the structure of your tables.
I am not quite sure how to interpret your question, but it seems you are after something like this.
SELECT *
FROM funds
WHERE CURRENT_DATE <= anniversary
AND CURRENT_DATE > DATE_SUB(anniversary, INTERVAL 2 MONTH)
It is possibly not exact as I don't know which flavour of SQL you are using.
I don't know if "fund anniversary" is some kind of a special term in English, so I'm assuming you want to select something like the birthdays which can be stored with a random year, like 1972-01-03.
If it's not so, please correct me.
In SQL Server, you need to generate a list of years and join with it:
WITH q AS
(
SELECT 0 AS num
UNION ALL
SELECT num + 1
FROM q
WHERE num <= 100
)
SELECT *
FROM mytable
WHERE fund_date BETWEEN DATE_ADD(year, -num, GETDATE()) AND DATE_ADD(year, -num, DATE_ADD(month, 2, GETDATE()))
UNION ALL
SELECT *
FROM mytable
WHERE fund_date <= DATEADD(year, -100, GETDATE()
AND DATE_ADD(year, YEAR(GETDATE()) - YEAR(fund_date), fund_date) BETWEEN GETDATE() AND DATE_ADD(month, 2, GETDATE()))
This query is built in two parts:
The first part selects birthdays for each year from the list using the index range scans.
The second part selects birthdays that are more than 100 years old (using a single range scan) and filters them all.
Since birthdates older than 100 years are very unlike, the second part in fact selects almost nothing and does not affects performance too much.