SQL Querying a slowly changing dimension - sql

I have a table with Staion Platforms and when they were operational and when they were closed.
I want to get the count of stations that were operational between 01/01/2010 and 31/01/2010
On the right side is the result Im looking for. I've highlighted in Yellow those that should not be included in the result. The stations need to be grouped by Location. MAP_id is the PK.
The way I think it should work is to go to each row and check whether the start and end dates fall within Jan 2010.
Can this Query work?
SELECT ...
FROM ...
WHERE
'2010/01/31' between startdate and
case when enddate is null then Getdate()
else enddate
end
Any help would be appriciated.

Your condition is: "operational between 01/01/2010 and 31/01/2010". I would expect to see both these dates in the query.
This is an example of an overlapping time-intervals problem. What this is saying is that the station started on or before 01/01/2010 and ended on or after 31/01/2010. Here is the where clause for that logic:
where startdate <= '2010-01-01' and
(enddate is null or enddate >= '2010-01-31');
Note that the logic might be slightly different, depending on whether the end points are included or not.
You can also write this as:
where startdate <= '2010-01-01' and
coalesce(enddate, getdate()) >= '2010-01-31');

Related

If statement in a WHERE clause for between two dates

I have a script that counts the number of doses a client has had between their start date and 180 days out.
Now i am trying to have some form of an IF (or CASE) statement in the where clause so its either between the first date and 180 days out OR if that 180 days exceeds 6/30/20, then just do the count between start date and 6/30/20.
In my research i couldnt find anything about using an IF else (or CASE) with dates, in the WHERE function.
This is my current script in SQL Server
SELECT
t.clinic,
t.display_id,
m.FirstDate,
DATEADD(DAY,180,MIN(take_on_date)) AS Days_180,
COUNT(t.dose_number) AS Doses
FROM (SELECT CLINIC
, display_id
, MIN(TAKE_ON_DATE) AS FirstDate
FROM factMedHist
GROUP BY Clinic, display_id
) AS m
INNER JOIN factMedHist AS t
ON t.Clinic = m.Clinic
AND t.display_id = m.display_id
WHERE t.take_on_date
BETWEEN m.FirstDate AND DATEADD(DAY,180,m.FirstDate)
GROUP BY t.Clinic, t.display_id,m.FirstDate
So "start date" = "FirstDate" = "min(TAKE_ON_DATE)". And "Client" = "display_id" but you group and join on the tuple <display_id, Clinic>. I see many struggles in the future based on this unfortunately common issue. Consistent terminology is important.
So here is one take on your issue. A bit verbose to demonstrate what it does. Note also the provision of a MVCE - something that you should provide to encourage others to help. It is a bit of effort you should not expect others to take on just to solve your issues.
You were on the correct path with CASE - but lost it when you started thinking of it as a control-of-flow construct as it is in most other languages. You can compute the startdate and enddate for each client (clinic, display_id)
with cte as (select *,
min(takedate) over (partition by display_id, clinic order by takedate) as startdate,
dateadd(day, 180, min(takedate) over (partition by display_id, clinic order by takedate)) as enddate
from #medhist
)
You were doing that - but the problem is that you need reference that end date in the where clause to filter the rows as desired. Like this:
where takedate <= case when enddate <= '20200630' then enddate else '20200630' end
Fiddle here. Notice that the first take date is irrelevant in the where clause. This is one way to achieve your result. Another obvious approach is to use a conditional sum. That would be good practice if you wanted to increase your knowledge. Depending on your situation one might be more efficient than the other. CTEs are just syntactic sugar but do allow the building of logic in a piece-by-piece approach - something I find can be very helpful in developing a completed tsql statement.
WHERE t.take_on_date BETWEEN m.FirstDate AND DATEADD(DAY,180,m.FirstDate)
OR t.take_on_date > DATEADD(DAY, 180, '20200630')

SQL Server: compare only month and day - SARGable

I have a table storing a datetime column, which is indexed. I'm trying to find a way to compare ONLY the month and day (ignores the year totally).
Just for the record, I would like to say that I'm already using MONTH() and DAY(). But I'm encountering the issue that my current implementation uses Index Scan instead of Index Seek, due to the column being used directly in both functions to get the month and day.
There could be 2 types of references for comparison: a fixed given date and today (GETDATE()). The date will be converted based on time zone, and then have its month and day extracted, e.g.
DECLARE #monthValue DATETIME = MONTH(#ConvertDateTimeFromServer_TimeZone);
DECLARE #dayValue DATETIME = DAY(#ConvertDateTimeFromServer_TimeZone);
Another point is that the column stores datetime with different years, e.g.
1989-06-21 00:00:00.000
1965-10-04 00:00:00.000
1958-09-15 00:00:00.000
1965-10-08 00:00:00.000
1942-01-30 00:00:00.000
Now here comes the problem. How do I create a SARGable query to get the rows in the table that match the given month and day regardless of the year but also not involving the column in any functions? Existing examples on the web utilise years and/or date ranges, which for my case is not helping at all.
A sample query:
Select t0.pk_id
From dob t0 WITH(NOLOCK)
WHERE ((MONTH(t0.date_of_birth) = #monthValue AND DAY(t0.date_of_birth) = #dayValue))
I've also tried DATEDIFF() and DATEADD(), but they all end up with an Index Scan.
Adding to the comment I made, on a Calendar Table.
This will, probably, be the easiest way to get a SARGable query. As you've discovered, MONTH([YourColumn]) and DATEPART(MONTH,[YourColumn]) both cause your query to become non-SARGable.
Considering that all your columns, at least in your sample data, have a time of 00:00:00 this "works" to our advantage, as they are effectively just dates. This means we can easily JOIN onto a Calendar Table using something like:
SELECT dob.[YourColumn]
FROM dob
JOIN CalendarTable CT ON dob.DateOfBirth = CT.CalendarDate;
Now, if we're using the table from the above article, you will have created some extra columns (MonthNo and CDay, however, you can call them whatever you want really). You can then add those columns to your query:
SELECT dob.[YourColumn]
FROM dob
JOIN CalendarTable CT ON dob.DateOfBirth = CT.CalendarDate
WHERE CT.MonthNo = #MonthValue
AND CT.CDay = #DayValue;
This, as you can see, is a more SARGable query.
If you want to deal with Leap Years, you could add a little more logic using a CASE expression:
SELECT dob.[YourColumn]
FROM dob
JOIN CalendarTable CT ON dob.DateOfBirth = CT.CalendarDate
WHERE CT.MonthNo = #MonthValue
AND CASE WHEN DATEPART(YEAR, GETDATE()) % 4 != 0 AND CT.CDat = 29 AND CT.MonthNo = 2 THEN 28 ELSE CT.Cdat END = #DayValue;
This treats someone's birthday on 29 February as 28 February on years that aren't leap years (when DATEPART(YEAR, GETDATE()) % 4 != 0).
It's also, probably, worth noting that it'll likely be worth while changing your DateOfBirth Column to a date. Date of Births aren't at a given time, only on a given date; this means that there's no implicit conversion from datetime to date on your Calendar Table.
Edit: Also, just noticed, why are you using NOLOCK? You do know what that does, right..? Unless you're happy with dirty reads and ghost data?

Giving the wrong records when used datetime parameter in MS Access Query

I am working MS-Access 2007 DB .
I am trying to write the query for the Datetime, I want to get records between 14 December and 16 December so I write the bellow query.
SELECT * FROM Expense WHERE CreatedDate > #14-Dec-15# and CreatedDate < #16-Dec-15#
( I have to use the two dates for the query.)
But It returning the records having CreatedDate is 14 December...
Whats wrong with the query ?
As #vkp mentions in the comments, there is a time part to a date as well. If it is not defined it defaults to midnight (00:00:00). As 14-dec-2015 6:46:56 is after 14-dec-2015 00:00:00 it is included in the result set. You can use >= 15-dec-15 to get around this, as it will also include records from 15-dec-2015. Same goes for the end date.
It seems you want only records from Dec 15th regardless of the time of day stored in CreatedDate. If so, this query should give you what you want with excellent performance assuming an index on CreatedDate ...
SELECT *
FROM Expense
WHERE CreatedDate >= #2015-12-15# and CreatedDate < #2015-12-16#;
Beware of applying functions to your target field in the WHERE criterion ... such as CDATE(INT(CreatedDate)). Although logically correct, it would force a full table scan. That might not be a problem if your Expense table contains only a few rows. But for a huge table, you really should try to avoid a full table scan.
You must inlcude the time in your thinking:
EDIT: I wrote this with the misunderstanding, that you wanted to
include data rows from 14th to 16th of Dec (three full days).
If you'd write <#17-Dec-15# it would be the full 16th. Or you'd have to write <=#16-Dec-15 23:59:59#.
A DateTime on the 16th of December with a TimePart of let's say 12:30 is bigger than #16-Dec-15#...
Just some backgorund: In Ms-Access a DateTime is stored as a day's number and a fraction part for the time. 0.5 is midday, 0.25 is 6 in the morning...
Comparing DateTime values means to compare Double-values in reality.
Just add one day to your end date and exclude this:
SELECT * FROM Expense WHERE CreatedDate >= #2015/12/14# AND CreatedDate < #2015/12/17#
Thanks A Lot guys for your help...
I finally ended with the solution given by Darren Bartrup-Cook and Gustav ....
My previous query was....
SELECT * FROM Expense WHERE CreatedDate > #14-Dec-15# and CreatedDate < #16-Dec-15#
And the New working query is...
SELECT * FROM Expense WHERE CDATE(INT(CreatedDate)) > #14-Dec-15# and CDATE(INT(CreatedDate)) < #16-Dec-15#

SQL query to search 3 date fields

I'm trying to run a query against 3 different fields. I want it to return all the accounts that meet the first range, bring back all accounts that meet the second range and same for the third. I tried using and but get dates outside of the range.
select
*
from
Permits
where
created between '1/1/2015' and '1/21/2015'
and updated between '1/1/2015' and '1/21/15'
and noResponseDateSet between '1/1/15' and '1/21/15'
order by
alarmNo
Thanks for helping me
Use ISO standard date formats:
select created, updated, noResponseDateSet
From Permits
where created between '2015-01-01' and '2015-01-21' and
updated between '2015-01-01' and '2015-01-21' and
noResponseDateSet between '2015-01-01' and '2015-01-21' ;
This should fix the problem, unless you have one combination of rather arcane international date settings.
CONVERT can do the dirty parsing work for you. It will accept 1/1/2015 or 01-01-2015 or most other combinations.
select *
From Permits
where created between CONVERT(DATETIME,'1/1/2015') and CONVERT(DATETIME,'1/21/2015')
and updated between CONVERT(DATETIME,'1/1/2015') and CONVERT(DATETIME,'1/21/15')
and noResponseDateSet between CONVERT(DATETIME,'1/1/15') and CONVERT(DATETIME,'1/21/15')
order by alarmNo

Query for start and end date

I already raided the net , yet still no answers.
Well, I'm creating a hotel room reservation system.
My room availability query works fine with this query :
SELECT r.roominv_id, r.room_id, m.room_type,r.room_number,r.room_status
FROM roominventory r
INNER JOIN room AS m ON m.room_id = r.room_id
WHERE m.room_type LIKE '%$roomtype%'
AND r.room_status = 'available'
AND r.roominv_id NOT IN (SELECT b.roominv_id
FROM reserve b
WHERE NOT (b.chckout < '$chckin'
OR b.chckin > '$chckout'))
I wanted February 2 as my start date in afternoon (hotel reservations must always start after 12 noon) and February 3 as my end date ( before 12 noon )
but in my current query , if I choose February 2 as startdate and February 3 as enddate , in database, it will appear as a 2 day reservation.
So my question is:
how will I query that clients can still choose the end date from a already reserved room?
I actually saw a site that have a datepicker that have somewhat like AM/PM http://reservations.directwithhotels.com/reservation/selectDates/148/campaign/ew
BUT I'm so noob at javascript.
Depending on your data type for chckout and chckin, you may just want to use the BETWEEN operator, or use inclusive equality operators (>=, <=).
I think you just need to change the where clause to:
WHERE NOT (b.chckout < '$chckin'
OR b.chckin >= '$chckout')
This assumes that chckin and chckout are stored as dates. Otherwise you need to convert them to dates, which depends on the database . . . here are some ways:
date(b.chckin)
cast(b.chckin as date)
trunc(b.chckin)