SQL Server 2008 - datediff month - sql-server

I have the following query below --
SELECT *
FROM
dbo.patient AS p
INNER JOIN
dbo.study AS s ON s.patient_fk = p.pk
/***where p.pk = s.patient_fk***/
WHERE
s.study_custom1 LIKE'%hosp%'
AND s.study_datetime >= DATEADD(month, datediff(month, 0, getdate()) -1, 0)
AND s.study_datetime < DATEADD(month, datediff(month, -1, getdate()) -1, 1)
I want to find all dates for the most recent prior month. In this case it is the month of July, but the query is picking up '8/1/2014' along with July dates.
How do I need to modify this to exclude August and only find July?
Thanks

Try this
select *
from dbo.patient AS p
INNER JOIN dbo.study AS s
on s.patient_fk = p.pk
/***where p.pk = s.patient_fk***/
where s.study_custom1 like '%hosp%'
and DATEPART(year,s.study_datetime) = DATEPART(year,DATEADD(month,-1,GETDATE()))
and DATEPART(month,s.study_datetime) = DATEPART(month,DATEADD(month,-1,GETDATE()))
EDIT: Note: You could speed execution by setting variables instead of computing the month and year of today for every row, but as you had your original in a query without variables I provided a less computationally efficient solution for the sake of making sure you could use it in your application.
Also note that the arithmetic for getting the year and month of "last month" must be done inside the DATEPART so the query will function correctly in January.
EDIT 2: Explaining optimization
My first edit was written before I saw your comment, so I'm not sure if you're asking for an explanation of my original query, or asking for an explanation of my suggested optimization, but I'll guess the optimization, I'll be happy to further explain the query if that's what you meant.
So the weakness of my first solution is it makes 8 function calls per row. Not a big deal, but it could add up for a large database. If you instead define variables that you populate outside the query, you can reduce it to a pair of index scans per row, which is significantly faster. Significantly being maybe 1/10 of a second if you only have thousands of patients :-)
But regardless, here is a more efficient solution.
DECLARE #StartM DATE
DECLARE #EndM DATE
SET #StartM = DATEADD(month,-1,CONVERT(DATE,SUBSTRING(CONVERT(VARCHAR(50),GETDATE(),20),1,8)+'01'))
SET #EndM = DATEADD(month,1,#StartM)
select *
from dbo.patient AS p
INNER JOIN dbo.study AS s
on s.patient_fk = p.pk
/***where p.pk = s.patient_fk***/
where s.study_custom1 like '%hosp%'
and s.study_datetime >=#StartM
and s.study_datetime < #EndM
Note that #EndM will be the midnight of the first day of this month, so even if your s.study_datetime is 11:59 PM on the last day of last month it still gets included. If you build an index on s.study_datetime, this will be very fast.

Related

unable to understand the dateadd function in SQL

I have a SQL query like
SET THIS_YEAR_END = '2022-11-01';
SET THIS_YEAR_START = DATEADD(DAY, -4*7+1, $THIS_YEAR_END);
SET LAST_YEAR_END = '2021-11-02';
SET LAST_YEAR_START = DATEADD(DAY, -4*7+1, $LAST_YEAR_END);
select end_date from (
select * from data
where DATE>= DATEADD(DAY, -27 * 7, $LAST_YEAR_START))
AND END_DATE BETWEEN CUST.END_DATE - 26 * 7 AND CUST.END_DATE - 7
I'm confused with this dateadd function in SQL. Can anyone please explain what exactly it's doing?
Here is the docs: https://docs.snowflake.com/en/sql-reference/functions/dateadd.html
Basically DATEADD is adding a specified value to a certain date. The first parameter is indicating the units of time that you want to add (e.g. DAY or MONTH), the second parameter specifies the number of units (e.g. number of days or number of months) and the third paramter is the date, to which you want to add something.
In your example in line 2 you are reducing THIS_YEAR_END by -4*7+1 (=-27) days.

SQL Server : measuring real-time efficiency by operator

I've been working on some SQL code to measure efficiency in real-time for some production data. Here's a quick background:
Operators will enter in data for specific sub assemblies. This data looks something like this:
ID PO W/S Status Operator TotalTime Date
60129515_2000_6_S025 107294 S025 Completed A 38 05/08/2020
60129515_2000_7_S025 107294 S025 Completed A 46 05/08/2020
60129515_2000_8_S025 107294 S025 Completed A 55 05/08/2020
60129515_2025_6_S020 107295 S020 Completed B 58 05/08/2020
60129515_2025_7_S020 107295 S020 Completed B 47 05/08/2020
60129515_2025_8_S020 107295 S020 Completed B 45 05/08/2020
60129515_2000_1_S090 107294 S090 Completed C 33 05/08/2020
60129515_2000_2_S090 107294 S090 Completed C 34 05/08/2020
60129515_2000_3_S090 107294 S090 Completed C 21 05/08/2020
The relevant columns are the Operator, TotalTime and Date (note that the date is stored as varchar(50) because it plays nicer with Microsoft PowerApps that way).
What I need to do is:
Aggregate the sum of "TotalTime" grouped by Operator
Calculate the time elapsed based on a condition:
If between 7AM and 4PM, calculate the time elapsed since 7AM of the current day
If after 4PM, return the total time between 7AM and 4PM of the current day
Divide the SUM(TotalTime) by the TimeElapsed (AKA the first list item / second list item) in order to get a rough estimate of labor hours worked vs. hours passed in the day.
This calculation would change every time the query was ran. This will allow the Microsoft PowerApp that is pulling this query to refresh the efficiency measure in real time. I've taken a stab at it already - see below:
SELECT
md.Operator,
CASE
WHEN DATEADD(HOUR, -5, GETUTCDATE()) > CONVERT(DATETIME, CONVERT(DATE, DATEADD(HOUR, -5, GETUTCDATE()))) + '7:00' AND GETDATE() < CONVERT(DATETIME, CONVERT(DATE, DATEADD(HOUR, -5, GETUTCDATE()))) + '15:45'
THEN (SUM(isNull(md.TotalTime, 0)) + SUM(isNull(md.DelTime, 0))) * 1.0 / DATEDIFF(MINUTE, CONVERT(DATETIME, CONVERT(DATE, DATEADD(HOUR, -5, GETUTCDATE()))) + '7:00' , DATEADD(HOUR, -5, GETUTCDATE())) * 100.0
ELSE (SUM(isNull(md.TotalTime, 0)) + SUM(isNull(md.DelTime, 0))) / 420 * 100.0
END AS OpEfficiency
FROM
[Master Data] AS md
WHERE
md.[Date] = CONVERT(varchar(50), DATEADD(HOUR, -5, GETUTCDATE()), 101)
GROUP BY
md.Operator
Note: the DelTime is a different column regarding delay times. I am also converting back from UTC time to avoid any time zone issues when transferring to PowerApps.
However, this is horribly inefficient. I am assuming it is because the Date needs to be converted to datetime every single time. Would it work better if I had a calculated column that already had the date converted? Or is there a better way to calculate time elapsed since a certain time?
Thanks in advance.
There are a few things you can do to increase efficiency considerably. First, you want to make sure SQL can do a simple comparison when selecting rows, so you'll start by calculating a string to match your date on since your [Date] field is a string not a date.
Second, calculate the minutes in your shift (either 540 for a full shift or scaled down to 0 at 7 AM exactly) ahead of time so you aren't calculating minutes in each row.
Third, when summing for operators, use a simple sum on the minutes and calculate efficiency from that sum and your pre-calculated shift so far minutes.
One note - I'm casting the minutes-so-far as FLOAT in my example, maybe not the best type but it's clearer than other decimal types like DECIMAL(18,6) or whatever. Pick something that will show the scale you want.
My example uses a Common Table Expression to generate that date string and minutes-so-far FLOAT, that's nice because it fits in a direct query, view, function, or stored procedure, but you could DECLARE variables instead if you wanted to.
By filtering with an INNER JOIN on the [Date] string against the pre-calculated TargetDate string, I make sure the data set is pared down to the fewest records before doing any math on anything. You'll definitely want to INDEX [Date] to keep this fast as your table fills up.
All these together should give a pretty fast query, good luck
with cteNow as ( --Calculate once, up front - date as string, minutes elapsed as FLOAT (or any non-integer)
SELECT CASE WHEN 60*DATEPART(HOUR, GETUTCDATE())+DATEPART(MINUTE, GETUTCDATE()) > 60*21
--4PM in UTC-5, expressed in minutes
THEN CONVERT(float,(16-7)*60) --minutes in (4 PM-7 AM) * 60 minutes/hour
ELSE --Assume nobody is running this at 6 AM, so ELSE = between 7 and 4
CONVERT(float,60*DATEPART(HOUR, GETUTCDATE()) + DATEPART(MINUTE, GETUTCDATE()) - ((7+5)*60))
--Minutes since midnight minus minutes from midnight to 7 AM, shifted by
--UTS offset of 5 hours
END as MinutesToday --Minutes in today's shift so far
, FORMAT(DATEADD(HOUR,-5,GETUTCDATE()),'MM/dd/yyyy') as TargetDate --Date to search for
--as a string so no conversion in every row comparison. Also, index [Date] column
)
SELECT md.Operator, SUM(md.TotalTime) as TotalTime, SUM(md.TotalTime) / MinutesToday as Efficiency
FROM [Master Data] AS md INNER JOIN cteNow as N on N.TargetDate = md.[Date]
GROUP BY md.Operator, MinutesToday
BTW, you didn't make allowances for lunch or running before 7 AM, so I also ignored those. I think both could be addressed in cteNOW without adding much complexity.

question regarding oracle sysdate to last month date

OBSERVED_DATE BETWEEN add_months(trunc(sysdate,'mm'),-1) and last_day(add_months(trunc(sysdate,'mm'),-1))
I need to run query between the 01-11-2019 to 30-11-2019 how do I change the sysdate to last month values
Assuming your dates may have non-zero time of day, it is best to write such conditions with explicit inequalities, rather than using between. Between is inclusive of both ends, when what you need (in almost all cases) is >= 1 November and < 1 December.
If, instead, you write <= 30 November (which is what you do when you use between the way you are trying now), you will miss everything from the last day of the month, 30 November, that has a time-of-day greater than midnight.
The where clause should look like this:
...
where observed_date >= add_months(trunc(sysdate, 'mm'), -1)
and observed_date < trunc(sysdate, 'mm')
Still, if you insist on getting the last day of the previous month (such as 30 November) for whatever reason, that is simply
trunc(sysdate, 'mm') - 1
(that is, subtract one day from the first day of the current month).
There is another function which returns last day of the month. I.e. last_day
So your condition should look like this:
where observed_date
between add_months(trunc(sysdate, 'mm'), -1)
and last_day(add_months(sysdate, -1))
Cheers!!

Is Date between 1st of 1st Month and 29th of 7th month of any year (Sql Server 2008+)

I need to check if a Date is between 1st of 1st month and 29th of 7th month of any year.
Before going ahead and hacking extracting Month Day from the date and comparing it to being between 0101 and 0729 (or just checking if the date dd mm is less than 629) type of hideousity solution, I must ask is there a function to check wether any date falls in between particalr days and month , invariant of the it's year.
Other wise extracting months and days from the date and doing some hacky arithmatic is easy, but I want to be leave a better code for the poor programmer who will come after me and not having to guess why the hell some freaky made up arithmatic is going on, I rather be explicit even if it takes longer.
Another option:
DECLARE #TheDate date = '2016-07-29';
SELECT 1
WHERE DATEADD(Year, -(YEAR(#TheDate) - 2000), #TheDate)
BETWEEN '2000-01-01' AND '2000-07-29'
where datetimeField < dateFromParts( year( dateTimeField ), 7, 30)
EDIT: Above would work in 2012 and later. For 2008 you could do some arithmetic which is "very simple" for a developer:
where datetimeField < CAST(CAST(year(dateTimeField) AS CHAR(4))+'0730' AS DATE);
Or simplified a bit:
where datetimeField < CAST(year(dateTimeField) AS CHAR(4))+'0730';
PS: A common pitfall in using BETWEEN (which acts like x >= v1 and x <= v2) on a datetime field, where the field might have time part, is not suggested and would never help to precisely get the correct records (because there is no way to specify the ending time). Instead you should always use x >= v1 and x < v2 style where v2 is the minimum exclusive upper value. ie: To get all the sales in May 2000 (saleDate has time component):
saleDate >= '20000501' and saleDate < '20000601'
NOT:
saleDate between '20000501' and '20000531'
OR NOT:
saleDate >= '20000501' and saleDate <= '20000531'
Select * from TableWithNoName where DateField1 between DATETIMEFROMPARTS( YEAR(DateField1),1, 1, 0, 0, 0, 0 ) and DATETIMEFROMPARTS( YEAR(DateField1),7, 29, 23, 59, 59, 999 )
assuming DateField1 is a Date Value

Conditional Time Period for Totals

I feel like I'm making this harder than it should be. I'm trying to display a sum for month C but this sum must include totals for months A, B & C. Then I need to do the same thing for Month D which includes totals for months B, C & D. Once I have this figured out I need to break it down by individual accounts but that part shouldn't be too difficult.
I have a date table to call on but it doesn't have month start or end dates which seems to be causing my difficulty.
So the solution to the above issue is to use a CTE (Common Table Expression) in the join statement identifying the date range accepted for the time period.
Select *
FROM A
LEFT JOIN B CASE WHEN a.DateID >= b.PeriodStart AND a.DateID <= b.PeriodEnd THEN 1 ELSE 0 END = 1

Resources