Extracting Dates in SQL Server - sql

I have a database which has information about clinics.
Each clinic has an opening date(say, 2014-05-23 00:00:00) and a field called MIO(monthsInOperation), which basically is the number of months the clinic has operated.
For each clinic I need the Calendar Month and Year in the format (say, July-13) based on the current month of operation(MIO), which in the database can be 0,1,2,....and so on.
I know I need to use DATEADD() or DATEPART() or DATEDIFF() function. But I am unable to get the desired result.
The answer for the last Clinic should be Feb-2012

Correct me if I'm misunderstanding your question, but is there a reason you can't just do something like this?
SELECT DATEADD(MONTH, MonthsInOperation, OpenDate) FROM Clinics
That won't give you just a month and year, but there's no such type in SQL anyway. You can handle the resulting DATE or DATETIME on your client's code and ToString it to be in the format (MM-yyyy or what have you) that you want it in. You could also separate them out using the MONTH(DATE) and YEAR(DATE) functions, although I wouldn't bother, personally. It seems like more a UI matter that you want only to see the month and year.
SELECT MONTH(DATEADD(MONTH, MonthsInOperation, OpenDate)), YEAR(DATEADD(MONTH, MonthsInOperation, OpenDate))
It sounds like that will output the two columns you need to work with: the month and year.
However,
SELECT DATENAME(MONTH, DATEADD(month, MonthsInOperation, OpenDate)) + "-" + YEAR(DATEADD(MONTH, MonthsInOperation, OpenDate)))
should give exactly what you want, again, if I'm understanding you properly.

If you need a list of the months when each clinic is open, you can do that. The first thing you need is a list of numbers, the rest is pretty easy.
with n as (
select 0 as n
union all
select n + 1
from n
where n < 50
)
select c.*, year(dateadd(month, n.n, c.opening_date)),
month(dateadd(month, n.n, c.opening_date))
from clinics c join
n
on n.n <= c.mio;
I've chosen to format the year and month as two separate numeric columns. You can use datename() or convert() to get a specific format.
EDIT:
If you just want the latest month, that is much simpler:
select c.*, year(dateadd(month, c.mio, c.opening_date)),
month(dateadd(month, c.mio, c.opening_date))
from clinics c;

Related

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...

SQL Query Subtract 1 month

I need to query SQL for data that falls into a last month date. I thought I was using the correct query logic but I get no results and I know there are results. The snippet of code is this:
MONTH(n.JOIN_DATE) = DATEADD(month, - 1, GETDATE())
This is giving me no results and I need to get anyone who has a join date of last month. What am I missing?
Use this:
MONTH(n.JOIN_DATE) = MONTH(DATEADD(month, - 1, GETDATE()))
You need to compare apples with apples, so compare the numerical month on both sides of the equation.
Massive credit to #PaulL for figuring this out before I did.
Update:
As #DasBlinkenLight and Matt pointed out, just comparing by month leaves the door open for multiple years to be returned. One possible fix would be to also compare the years, e.g.
WHERE MONTH(n.JOIN_DATE) = MONTH(DATEADD(month, - 1, GETDATE())) AND
YEAR(n.JOIN_DATE) = YEAR(DATEADD(month, - 1, GETDATE()))
MONTH(...) produces a month number. You should not compare it to the result returned by DATEADD, which is actually a date.
If you are looking for everyone who has joined less than a month ago, you can do it like this:
WHERE DATEADD(month, 1, n.JOIN_DATE) > GETDATE()
This takes into account the year and the day as well, not only the month.
If you are looking for everyone who joined last month, no matter on what day, you can use a more complex condition:
WHERE MONTH(DATEADD(month, -1, GETDATE()) = MONTH(n.JOIN_DATE)
AND YEAR (DATEADD(month, -1, GETDATE()) = YEAR (n.JOIN_DATE)
The second condition is necessary to avoid confusion between members joining last month and members joining on the same month one or more years ago.
MONTH(n.JOIN_DATE) returns a numeric which indicate the month in date m.JOIN_DATE
DATEADD(month, - 1, GETDATE()) returns a date which indicate date in last month.
So, you can use this instead :
MONTH(n.JOIN_DATE)= MONTH(DATEADD(month, - 1, GETDATE()))
OR
n.JOIN_DATE = DATEADD(month, - 1, GETDATE())
MONTH(n.JOIN_DATE) will only return the numerical value of the month (e.g.: 11 or 5).
DATEADD(MONTH, -1, GETDATE()) will simply subtract one month from the current date. It is still in a DATETIME format.
You may be looking for:
MONTH(n.JOIN_DATE) = MONTH(DATEADD(MONTH, -1, GETDATE()))
SELECT
DATEFROMPARTS(YEAR(DATEADD(month,-1,GETDATE())),MONTH(DATEADD(month,-1,GETDATE())),1) AS StartOfLastMonth
,DATEADD(day,-1,(DATEFROMPARTS(YEAR(GETDATE()),MONTH(GETDATE()),1))) AS EndOfLastMonthAsDate
,DATEADD(day,-3,CAST(DATEFROMPARTS(YEAR(GETDATE()),MONTH(GETDATE()),1) AS DATETIME)) AS EndOfLastMonthMidngith
,CAST(DATEADD(month,-1,GETDATE()) AS DATE) AS OneMonthAgoStrartOfDay
,CAST(GETDATE() AS DATE) AS StartOfToday
,DATEADD(MS,-3,CAST(CAST(GETDATE() AS DATE) AS DATETIME)) AS MidnightLastNight
Okay As people have definitely illustrated there are a lot of different answers to your question and all are built upon similar premise. using DATEADD() with a negative number to go back a month. Or to compare month and year to see if they are the same. The former being geared at 1 month ago to today and the later being last month.
All of the answers so far, expect #DasBlinkenLight and #TimBiegeleisen, fail to take into account TIME component of your column and the GETDATE() function if you have one. If your column is DATETIME you will need to take that into account. The above SELECT query that will arm you with some ways of getting to different dates that i suspect will meet your needs.
As far as using BETWEEN with dates be careful! because the values you put in are inclusive so if you put GETDATE() on the right side of the between statement you will get today's results too but you might really want UP TO to today in which case you should change your right side argument. Also I am not sure about Oracle, mysql, etc. but Micrsofot SQL-Server is accurate to .003 milliseconds. So if you really want to look at midnight of a date you should look at 23:59:59.997 because .998 and .999 will round up to the next day.
Also to further simplify if you don't want time components you can also cast your column to DATE and it essentially drops off the time and the BETWEEN because a little clearer too, e.g. CAST(n.JOIN_DATE AS DATE)
There are definitely other questions on this subject on stackoverflow I encourage you to research.
I knew this was a simple one and I was missing something to it. My code was wrong based upon the many responses I received on this and I was comparing apples to oranges and not apples to apples. Once I added the Month() around the dateadd function, it worked.
The reply to this answer are correct. But in the light of best practice writing your query this way will make it less SARGABLE, hence making it ignore indexes if you have one. It might be better to write it as
WHERE n.JOIN_DATE between DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-1, 0) AND DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1)
based on the comment below I have modified the query. I guess I did not read the question in depth.

Get date for nth day of week in nth week of month

I have a column with values like '3rd-Wednesday', '2nd-Tuesday', 'Every-Thursday'.
I'd like to create a column that reads those strings, and determines if that date has already come this month, and if it has, then return that date of next month. If it has not passed yet for this month, then it would return the date for this month.
Expected results (on 4/22/16) from the above would be: '05-18-2016', '05-10-2016', '04-28-2016'.
I'd prefer to do it mathematically and avoid creating a calendar table if possible.
Thanks.
Partial answer, which is by no means bug free.
This doesn't cater for 'Every-' entries, but hopefully will give you some inspiration. I'm sure there are plenty of test cases this will fail on, and you might be better off writing a stored proc.
I did try to do this by calculating the day name and day number of the first day of the month, then calculating the next wanted day and applying an offset, but it got messy. I know you said no date table but the CTE simplifies things.
How it works
A CTE creates a calendar for the current month of date and dayname. Some rather suspect parsing code pulls the day name from the test data and joins to the CTE. The where clause filters to dates greater than the Nth occurrence, and the select adds 4 weeks if the date has passed. Or at least that's the theory :)
I'm using DATEFROMPARTS to simplify the code, which is a SQL 2012 function - there are alternatives on SO for 2008.
SELECT * INTO #TEST FROM (VALUES ('3rd-Wednesday'), ('2nd-Tuesday'), ('4th-Monday')) A(Value)
SET DATEFIRST 1
;WITH DAYS AS (
SELECT
CAST(DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),N.Number) AS DATE) Date,
DATENAME(WEEKDAY, DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),N.Number)) DayName
FROM master..spt_values N WHERE N.type = 'P' AND N.number BETWEEN 0 AND 31
)
SELECT
T.Value,
CASE WHEN MIN(D.Date) < GETDATE() THEN DATEADD(WEEK, 4, MIN(D.DATE)) ELSE MIN(D.DATE) END Date
FROM #TEST T
JOIN DAYS D ON REVERSE(SUBSTRING(REVERSE(T.VALUE), 1, CHARINDEX('-', REVERSE(T.VALUE)) -1)) = D.DayName
WHERE D.Date >=
DATEFROMPARTS(
YEAR(GETDATE()),
MONTH(GETDATE()),
1+ 7*(CAST(SUBSTRING(T.Value, 1,1) AS INT) -1)
)
GROUP BY T.Value
Value Date
------------- ----------
2nd-Tuesday 2016-05-10
3rd-Wednesday 2016-05-18
4th-Monday 2016-04-25

Sales Grouped by Week of the year

I have a requirement to output the number sales in a year to date in weekly format where Monday is the first day of the week and Sunday is the last.
The table structure is as follows.
SalesId | Representative | DateOfSale.
Below is what I have tried but it doesn't seem to give me the correct result. The counts don't seem to add up for a given week. The Sunday results are not included in the correct week. I am thinking it has something to do with the date not including 11:59:59.999 for the last day of the week.
SELECT DATEADD(wk, DATEDIFF(wk, 6, Sales.DateOfSale), 6) as [Week Ending], count(SalesID) as Sales,
count(distinct(representative)) as Agents, count(SalesID) / count(distinct(representative)) as SPA
FROM Sales
where DateOfSale >= DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)
GROUP BY DATEADD(wk, DATEDIFF(wk, 6, Sales.DateOfSale), 6)
ORDER BY DATEADD(wk, DATEDIFF(wk, 6, Sales.DateOfSale), 6)
I am hoping to have something like this:
Week Ending | Sales
01/05/2014 | 5
01/12/2014 | 8
01/19/2014 | 11
01/26/2014 | 14
Please excuse the formatting of the table above. I couldn't seem to figure out how to create a pipe/newline based table using the editor.
~Nick
I suggest creating a table or table parameter that has all of your calendar information. In this case, it would need at minimum the column WeekEnding.
For example
DECLARE #MyCalendar TABLE
(
WeekEnding date
);
Populate this with your valid WeekEnding dates. I might also make parameters to limit the amount of sales data, e.g. #BeginDate and #EndDate.
If you join using "<=" on the week ending date, then I believe you will get the return you want:
SELECT
MyCalendar.WeekEnding,
COUNT(Sales.SalesId) Sales,
COUNT(DISTINCT Sales.Representative) Agents,
CAST(COUNT(Sales.SalesId) AS float) / CAST(COUNT(DISTINCT Sales.Representative) AS float) Spa
FROM
Sales
INNER JOIN
#MyCalendar MyCalendar
ON
Sales.DateOfSale <= MyCalendar.WeekEnding
WHERE
Sales.DateOfSale BETWEEN #BeginDate AND #EndDate
GROUP BY
MyCalendar.WeekEnding;
I am assuming you are using SQL 2012, but I believe this will work in 2008 too. I might point out two other things. First, consider your data type when dividing the COUNT of SalesId by the distinct count of Representative. You may not get the return you expect, and that is why I cast as float. Second, you apply count distinct slightly differently than what I use; the extra parenthesis are not needed.
I have a simplified version in SQL Fiddle.

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.