Get items that were created exactly one, two, n... years ago - sql

From some table I should select items, that were created exactly at least one year ago - at the same month and day as today. So, items created at 2011-10-10, 2013-10-10 will suit, but not at 2014-10-10.
The following query works just fine, but maybe there is a more elegant way? Thanks in advance.
select * from myTable
where
DATEPART(MONTH, CreatedOn) = DATEPART(MONTH, GETDATE()) -- item was created at the same month
and DATEPART(DAY, CreatedOn) = DATEPART(DAY, GETDATE()) -- and at the same day
and DATEDIFF(YEAR, CreatedOn, GETDATE()) >= 1 -- and at least one year ago

I would (if possible) change the table to have an extra, computed column, defined as:
DATEADD(year,DATEDIFF(year,CreatedOn,0),CONVERT(date,CreatedOn))
Say, called AnniversaryDate. This has the effect of "normalizing" the dates into the year 1900. This also treats 29th February the same as 28th February (both get normalized to 28th).
You now create an index on this computed column, making searches within it quick and easy. You use the same formula to transform GETDATE() into a "normalized" date for searching this data. All you have to do now is exclude results that occurred this year, which should be as simple as one extra filter. So your final query wound be:
AnniversaryDate = DATEADD(year,DATEDIFF(year,GETDATE(),0),
CONVERT(date,GETDATE())) and
CreatedOn < CONVERT(date,GETDATE())
(I'd probably create a single index on both AnniversaryDate and CreatedOn, to maximally benefit from this query).

Related

SQL Changing Date by one workday and filter for it

I'm running the following query
SELECT a.DAT
FROM a
and getting a column of datetimes. I would like to change this column or alternatively add a column, that adds a workday to the corresponding datetime and I want to be able to filter for the date at the same time
My approach for changing the workday is like this CASE WHEN DATENAME(dw, date) = 'Friday' THEN DATEADD(dw, 3, date), but I don't know how to fit that in. Here I'm adding 3 days to Friday because it should skip to Monday the next workday, in the other cases I would add only 1 day, for Monday to Thursday.
Maybe there is a better way?
In this image I tried to show how the result of an examplatory query should
look like. I have all dates available but I want to filter for the 14th, which should show me the 11 dates, because of the added workday. Since June 11 is Friday
I think my Main problem is I don't know how make the extra column depending on the given datetime, it should add 3 on Fridays and 1 on every other day (Saturday and Sunday don't exist in the base data)
Thanks for your responses.
I have a solution now. If it can be easier, let me know, if you like. Anyway, thanks for your help everyone
Solution:
DECLARE #Date DATETIME
Set #Date = '14/06/2021'
SELECT
a.DAT,
(CASE WHEN DATENAME(dw, a.DAT) = 'Friday' THEN DATEADD(DAY, 3, a.DAT) ELSE DATEADD(DAY, 1, a.DAT) END) as RealDate
FROM a
WHERE (CASE WHEN DATENAME(dw, #Date) = 'Monday' THEN DATEADD(DAY, -3, #Date) ELSE DATEADD(DAY, -1, #Date) END) = a.DAT
You seem to be describing a case expression:
SELECT (CASE WHEN DATENAME(weekday, date) = 'Friday'
THEN DATEADD(DAY, 3, date)
ELSE DATEADD(DAY, 1, date)
END) as next_date
Welcome to S/O. If your table already has a datetime column, that is probably best. Dont try to break things down such as a date column, a time column, and some workday column. There are plenty of functions that allow you to extract parts as needed. However, your post tags only show SQL instead of a specific database such as sql-server, mysql, oracle, etc. but appears SQL-Server via DateAdd() function.
With date functions you could do things like filtering based on the "Day" of the given date/time column NOT being a Sat or Sun. Or, filtering on a single month by a >= first day of the month but less than first day of following month. This way you dont have to worry about time factors. This type of query would allow inclusion of working up to 11:59:59pm before following day.
If you edit your question with any additional clarifications on why adding 3 days per your example may help offer additional resolution. Try not to just put things that could be long in comments. Then just comment back on my post and I'll try to follow-up (in addition to others that may follow this post).

Report that updates yearly

I have created a report that is supposed to look at the number of baptisms at our church for the ministry year. The Ministry year runs from Aug 1 - July 31. I currently have the report set to tell me the names of anyone that has a baptism date greater than 8/1/2016. But I would need to change that year each year for it to report properly. so I wanted to use a Case statement to have it update each year, but i am getting an error message with this: (The error is in the where clause, so I didn't include the entire report)
WHERE (P.organization_id = 1) AND
((CandidateProcesses_BaptismDate68.datetime_value) between (
case
When datepart(month, getdate()) < 8 then ('8/1/'+ datepart(year, getdate()))
When datepart(month, getdate()) >7 then ('8/1/'+
datepart((year,getdate())-1))End) and Getdate())
Does anyone see why I am getting an error?
Thanks!
You are getting an error trying to add a string and a number. You could fix that using datename() rather than datepart(). But, I think this is a simpler approach:
WHERE (P.organization_id = 1) AND
year(dateadd(month, -7, CandidateProcesses_BaptismDate68.datetime_value)) = year(dateadd(month, -7, getdate()))
This subtract 7 months to get the "ministry year" and then compares that to the current date minus seven months. That is, it subtracts 7 months and then normalizes on the calendar year.
This is a bit more expensive than your version, because it cannot use an index on CandidateProcesses_BaptismDate68(datetime_value). However, I doubt the database of baptisms is so large that the query will take very long anyway. (If that is an issue, then your version can be made to work with some simple modifications.)

Calculating orders placed on the end of the month

I'm currently studying SQL Server using the book Ben-Gan, Itzik. T-SQL Fundamentals. Below is a query used to select order placed at end of the month. (I know that function EOMONTH() can also be used)
SELECT orderid, orderdate, custid, empid
FROM Sales.Orders
WHERE orderdate = DATEADD( month, DATEDIFF( month, '18991231', orderdate), '18991231');
The author's explanation is:
This expression first calculates the difference in terms of whole
months between an anchor last day of some month (December 31, 1899, in
this case) and the specified date. Call this difference diff. By
adding diff months to the anchor date, you get the last day of the
target month.
However, I'm still a bit confused as to how it actually works. Would someone kindly explain it?
That seems like a rather arcane way to do this. What the code is doing is calculating the number of months since the last day of some month. Then, it adds this number of months to that date. Because of the rules of dateadd(), the month remains the last date.
However, I prefer a simpler method:
where day(dateadd(day, 1, orderdate)) = 1
I find this much clearer.
select DATEDIFF(MONTH, '20160131', '20160201')
give us 1 month and
SELECT DATEADD(month, 1, '20160131')
give us 2016-02-29 00:00:00.000
that's ok
I tried out the query myself and seem to have got the hang of it. here is what i wrote just in case anyone else is interested
SELECT DATEADD(month, DATEDIFF(MONTH, '20160131', '20160201'), '20160131');
result:
2016-02-29 00:00:00.000
so my interpretation is that adding one or more "month" to a particular date in which the last date of the month is 31 will always return a date in which the date is the last day of the month. if this sentence makes any sense...

Sum of values per month divided by days of month

I have a Sales table:
id_item, quantity, date_time
What I need is to sum the items sold in a month and divide them by the days of the month from a selected period of months.
Example - The user selects the dates of Oct 1 to Dec 31. I need to show the items_sold/days_of_month:
Month Items sold Days of month Items/Day
Sep 25 30 0.83333
Oct 36 31 1.16
Dec 15 31 0.4838
I have to specify by Kind of item. the kind is obtained from another table called Items. I use dateformat dd/mm/yy.
select
month(date_time),
sum(quantity) / (select(datepart(dd,getdate())))
from
sales v
join items a on v.id_item=a.id_item
where
a.kind='Kind of Item'
and cast(Convert(varchar(10), date_time, 112) as datetime)
between '01/10/2012' and '31/12/2012'
group by
month(date_time)
My problem is selecting the days of the months, how can I select x number of months and divide the sum(quantity) of each month by the days of each?
I know this part of the code only selects the days of the current month:
(select(datepart(dd,getdate())))
Try this on for size:
DECLARE
#FromDate datetime,
#ToDate date; -- inclusive
SET #FromDate = DateAdd(month, DateDiff(month, 0, '20121118'), 0);
SET #ToDate = DateAdd(month, DateDiff(month, 0, '20121220') + 1, 0);
SELECT
Year = Year(S.date_time),
Month = Month(S.date_time),
QtyPerDay =
Sum(s.quantity) * 1.0
/ DateDiff(day, M.MonthStart, DateAdd(month, 1, M.MonthStart))
FROM
dbo.Sales S
INNER JOIN dbo.Items I
ON S.id_item = I.id_item
CROSS APPLY (
SELECT MonthStart = DateAdd(month, DateDiff(month, 0, S.date_time), 0)
) M
WHERE
I.kind = 'Kind of Item'
AND S.date_time >= #FromDate
AND S.date_time < #ToDate
GROUP BY
Year(S.date_time),
Month(S.date_time),
M.MonthStart
It will select any full month that is partially enclosed by the FromDate and ToDate. The * 1.0 part is required if the quantity column is an integer, otherwise you will get an integer result instead of a decimal one.
Some stylistic notes:
Do NOT use string date conversion on a column to ensure you get whole days. This will completely prevent any index from being used, require more CPU, and furthermore is unclear (what does style 112 do again!?!?). To enclose full date periods, use what I showed in my query of DateCol >= StartDate and DateCol < OneMoreThanEndDate. Do a search for "sargable" to understand a very key concept here. A very safe and valuable general rule is to never put a column inside an expression if the condition can be rewritten to avoid it.
It is good that you're aliasing your tables, but you should use those aliases throughout the query for each column, as I did in my query. I recognize that the aliases V and A came from another language so they make sense there--just in general try to use aliases that match the table names.
Do include the schema name on your objects. Not doing so is not a huge no-no, but there are definite benefits and it is best practice.
When you ask a question it is helpful to explain all the logic so people don't have to guess or ask you--if you know (for example) that users can input mid-month dates but you need whole months then please indicate that in your question and state what needs to be done.
Giving the version of SQL server helps us zero in on the syntax required, as prior versions are less expressive. By telling us the version we can give you the best query possible.
Note: there is nothing wrong with putting the date calculation math in the query itself (instead of using SET to do it). But I figured you would be encoding this in a stored procedure and if so, using SET is just fine.

SQL Server 2005, Calculating upcoming birthdays from date of birth

This one has bugged me for a while now. Recently when revisiting some code I wrote for a customer a few years ago I was wondering if there is a more elegant solution to the problem.
The customer stores all of their clients information including date of birth (date time field)
They run an extract every Monday that retrieves any customer whose birthday will fall within the following week.
I.e. if the extract was run on Monday Jan 1st, Customers whose birthday fell between (and including) Monday Jan 8th -> Sunday Jan 14th would be retrieved.
My solution was to use the Datepart(dy) function and calculate all upcoming birthdays based off the customers date of birth converted to day of year, adding some logic to include for the extract being run at the end of a year.
The problem was that using Day of year throws results off by 1 day if the customer was born on a leap year and / or the extract is run on a leap-year after the 29th of Feb, so once again I had to add more logic so the procedure returned the expected results.
This seemed quite over-kill for what should be a simple task
For simplicity let’s say the table 'customer' contains 4 fields, first name, last name, dob, and address.
Any suggestions on how to simplify this would really be appreciated
Wes
Would something like this work for you?
select * from Customers c
where dateadd(year, 1900-year(dob), dob)
between dateadd(year, 1900-year(getdate()), getdate())
and dateadd(year, 1900-year(getdate()), getdate())+7
Why not use DATEPART(wk) on this year's birthday?
SET DATEFIRST 1 -- Set first day of week to monday
SELECT * FROM customer
WHERE DATEPART(wk, DATEADD(yy, DATEPART(yy, GETDATE()) - DATEPART(yy, customer.dob), customer.dob)) = DATEPART(wk, GETDATE()) + 1
It selects all customers who's birthday's weeknumber is one greater than the current weeknumber.
I think DATEADD should do the proper thing.
YEAR(GETDATE() - dbo.Patients.Dob) - 1900
I can safely assume you will never have customers born before 1900
Please Try This one.
SELECT TOP 10 BirthDate, FirstName
FROM Customers
WHERE DATEPART(mm,BirthDate) >= DATEPART(mm,GETDATE())
AND DATEPART(day,BirthDate) >= DATEPART(day,getdate())
OR DATEPART(mm,BirthDate) > DATEPART(mm,getdate())
ORDER BY DatePart(mm,BirthDate),DatePart(day,BirthDate)
this query will get upcoming birthdays including today itself