SQL filter if given date range exist [duplicate] - sql

This table is used to store sessions (events):
CREATE TABLE session (
id int(11) NOT NULL AUTO_INCREMENT
, start_date date
, end_date date
);
INSERT INTO session
(start_date, end_date)
VALUES
("2010-01-01", "2010-01-10")
, ("2010-01-20", "2010-01-30")
, ("2010-02-01", "2010-02-15")
;
We don't want to have conflict between ranges.
Let's say we need to insert a new session from 2010-01-05 to 2010-01-25.
We would like to know the conflicting session(s).
Here is my query:
SELECT *
FROM session
WHERE "2010-01-05" BETWEEN start_date AND end_date
OR "2010-01-25" BETWEEN start_date AND end_date
OR "2010-01-05" >= start_date AND "2010-01-25" <= end_date
;
Here is the result:
+----+------------+------------+
| id | start_date | end_date |
+----+------------+------------+
| 1 | 2010-01-01 | 2010-01-10 |
| 2 | 2010-01-20 | 2010-01-30 |
+----+------------+------------+
Is there a better way to get that?
fiddle

I had such a query with a calendar application I once wrote. I think I used something like this:
... WHERE new_start < existing_end
AND new_end > existing_start;
UPDATE This should definitely work ((ns, ne, es, ee) = (new_start, new_end, existing_start, existing_end)):
ns - ne - es - ee: doesn't overlap and doesn't match (because ne < es)
ns - es - ne - ee: overlaps and matches
es - ns - ee - ne: overlaps and matches
es - ee - ns - ne: doesn't overlap and doesn't match (because ns > ee)
es - ns - ne - ee: overlaps and matches
ns - es - ee - ne: overlaps and matches
Here is a fiddle

SELECT * FROM tbl WHERE
existing_start BETWEEN $newStart AND $newEnd OR
existing_end BETWEEN $newStart AND $newEnd OR
$newStart BETWEEN existing_start AND existing_end
if (!empty($result))
throw new Exception('We have overlapping')
These 3 lines of sql clauses cover the 4 cases of overlapping required.

Lamy's answer is good, but you can optimize it a little more.
SELECT * FROM tbl WHERE
existing_start BETWEEN $newSTart AND $newEnd OR
$newStart BETWEEN existing_start AND existing_end
This will catch all four scenarios where the ranges overlap and exclude the two where they don't.

I had faced the similar problem. My problem was to stop booking between a range of blocked dates. For example booking is blocked for a property between 2nd may to 7th may. I needed to find any kind of overlapping date to detect and stop the booking. My solution is similar to LordJavac.
SELECT * FROM ib_master_blocked_dates WHERE venue_id=$venue_id AND
(
(mbd_from_date BETWEEN '$from_date' AND '$to_date')
OR
(mbd_to_date BETWEEN '$from_date' AND '$to_date')
OR
('$from_date' BETWEEN mbd_from_date AND mbd_to_date)
OR
('$to_date' BETWEEN mbd_from_date AND mbd_to_date)
)
*mbd=master_blocked_dates
Let me know if it doesn't work.

Given two intervals like (s1, e1) and (s2, e2) with s1<e1 and s2<e2
You can calculate overlapping like this:
SELECT
s1, e1, s2, e2,
ABS(e1-s1) as len1,
ABS(e2-s2) as len2,
GREATEST(LEAST(e1, e2) - GREATEST(s1, s2), 0)>0 as overlaps,
GREATEST(LEAST(e1, e2) - GREATEST(s1, s2), 0) as overlap_length
FROM test_intervals
Will also work if one interval is within the other one.

Recently I was struggling with the same issue and came to end with this one easy step (This may not be a good approach or memory consuming)-
SELECT * FROM duty_register WHERE employee = '2' AND (
(
duty_start_date BETWEEN {$start_date} AND {$end_date}
OR
duty_end_date BETWEEN {$start_date} AND {$end_date}
)
OR
(
{$start_date} BETWEEN duty_start_date AND duty_end_date
OR
{$end_date} BETWEEN duty_start_date AND duty_end_date)
);
This helped me find the entries with overlapping date ranges.
Hope this helps someone.

You can cover all date overlapping cases even when to-date in database can possibly be null as follows:
SELECT * FROM `tableName` t
WHERE t.`startDate` <= $toDate
AND (t.`endDate` IS NULL OR t.`endDate` >= $startDate);
This will return all records that overlaps with the new start/end dates in anyway.

Mackraken's answer above is better from a performance perspective as it doesn't require several OR's in order to evaluate if two dates overlap. Nice solution!
However I found that in MySQL you need to use DATEDIFF instead of the minus operator -
SELECT o.orderStart, o.orderEnd, s.startDate, s.endDate
, GREATEST(LEAST(orderEnd, endDate) - GREATEST(orderStart, startDate), 0)>0 as overlaps
, DATEDIFF(LEAST(orderEnd, endDate), GREATEST(orderStart, startDate)) as overlap_length
FROM orders o
JOIN dates s USING (customerId)
WHERE 1
AND DATEDIFF(LEAST(orderEnd, endDate),GREATEST(orderStart, startDate)) > 0;

Related

SQL Server query order by sequence serie

I am writing a query and I want it to do a order by a series. The first seven records should be ordered by 1,2,3,4,5,6 and 7. And then it should start all over.
I have tried over partition, last_value but I cant figure it out.
This is the SQL code:
set language swedish;
select
tblridgruppevent.id,
datepart(dw,date) as daynumber,
tblRidgrupper.name
from
tblRidgruppEvent
join
tblRidgrupper on tblRidgrupper.id = tblRidgruppEvent.ridgruppid
where
ridgruppid in (select id from tblRidgrupper
where corporationID = 309 and Removeddate is null)
and tblridgruppevent.terminID = (select id from tblTermin
where corporationID = 309 and removedDate is null and isActive = 1)
and tblridgrupper.removeddate is null
order by
datepart(dw, date)
and this is a example the result:
5887 1 J2
5916 1 J5
6555 2 Junior nybörjare
6004 2 Morgonridning
5911 3 J2
6467 3 J5
and this is what I would expect:
5887 1 J2
6555 2 Junior nybörjare
5911 3 J2
5916 1 J5
6004 2 Morgonridning
6467 3 J5
You might get some value by zooming out a little further and consider what you're trying to do and how else you might do it. SQL tends to perform very poorly with row by row processing as well as operations where a row borrows details from the row before it. You also could run into problems if you need to change what range you repeat at (switching from 7 to 10 or 4 etc).
If you need a number there somewhat arbitrarily still, you could add ROW_NUMBER combined with a modulo to get a repeating increment, then add it to your select/where criteria. It would look something like this:
((ROW_NUMBER() OVER(ORDER BY column ASC) -1) % 7) + 1 AS Number
The outer +1 is to display the results as 1-7 instead of 0-6, and the inner -1 deals with the off by one issue (the column starting at 2 instead of 1). I feel like there's a better way to deal with that, but it's not coming to me at the moment.
edit: Looking over your post again, it looks like you're dealing with days of the week. You can order by Date even if it's not shown in the select statement, that might be all you need to get this working.
The first seven records should be ordererd by 1,2,3,4,5,6 and 7. And then it should start all over.
You can use row_number():
order by row_number() over (partition by DATEPART(dw, date) order by tblridgruppevent.id),
datepart(dw, date)
The second key keeps the order within a group.
You don't specify how the rows should be chosen for each group. It is not clear from the question.

oracle sql return 0 if count is null

I have two tables TT_RESULT_SUMMARY_TAB being the parent table and TT_RESULT_DETAIL_TAB being the child table. Primary key 'UTC_END_TIME' of TT_RESULT_SUMMARY_TAB being the foreign key of TT_RESULT_DETAIL_TAB.
Only if a certain day has errors then error information will be inserted to TT_RESULT_DETAIL_TAB.Even if no errors found relevant information will be inserted to parent table TT_RESULT_SUMMARY_TAB everyday,though TT_RESULT_DETAIL_TAB has no error information for the day.
What I'm attempting is to figure out what total number of errors for all the days in the parent table TT_RESULT_SUMMARY_TAB
say 2016/11/30 has a total of 1000 errors
and 2016/12/01 has total of zero errors
+----------------------+------------+
|total_number_of_errors| GivenDay |
+----------------------+------------+
| 1000 | 2016/11/30 |
| 0 | 2016/12/01 |
+----------------------+------------+
here's what I tried on oracle developer
SELECT NVL(count(TEST_NAME),0)
AS total_number_of_errors,to_char(UTC_END_TIME, 'yyyy,mm,dd')
As GivenDay from TT_RESULT_DETAIL_TAB group by UTC_END_TIME Order by UTC_END_TIME Desc
But I've no information on 2016/12/01 saying 0.
You are looking for "partition outer join" (look it up on Google).
https://docs.oracle.com/cd/E11882_01/server.112/e25555/tdpdw_sql.htm#TDPDW007
You need to build a table, view or subquery with all the individual days for the interval of interest. Then you do an OUTER join to your existing query, and use coalesce(total_number_of_values, 0) to get 0 rather than null for dates that are not present in the errors table.
For example:
select nvl(t.total_number_of_errors, 0) as total_number_of_errors, d.dt as givenday
from your_query t right outer join
( select to_date('2016/11/30', 'yyyy/mm/dd' dt from dual union all
select to_date('2016/12/01', 'yyyy/mm/dd' from dual
) d
on t.error_date = d.dt;
There are more efficient ways to build the "all-dates-in-the-interval" subquery (using CONECT BY LEVEL <= ...) - not covering it here since it's really a separate question.

Apache PIG- set date of current row as next records date minus one day for a given id

I have requirement to set end_dt as next records effective_dt minus 1 day for a given id and default it to 9999-12-31 for last record of a given id in pig.
input data-
id eff_dt end_dt
1 2012-02-28 9999-12-31
1 2013-03-15 9999-12-31
1 2014-05-01 9999-12-31
Required result- (order by eff_dt and then get the next record)
id eff_dt end_dt
1 2012-02-28 2013-02-14
1 2013-03-15 2014-04-30
1 2014-05-01 9999-12-31
i am new to apache PIG, found that we can use lead/lag , stitch/flatten but not getting how to use it in the script to achieve above result .I am facing few issues.
Issue 1 :- PIG accepts date as chararray. Need to convert eff_dt into date.
Issue 2 :- want to know syntax for 'date minus 1 day'.
Issue 3 :- How to use lead lag to get next record and do a minus one day and default if there is no next record.
Got below sample code from apache pig site but not getting how to transform it to use it in my use case.:-
To find the record 3 ahead of the current record, using a window between the current row and 3 records ahead and a default value of 0.
A = load 'T';
B = group A by si;
C = foreach B {
C1 = order A by i;
generate flatten(Stitch(C1, Over(C1.i, 'lead', 0, 3, 3, 0)));
}
D = foreach C generate s, $9;
This is equivalent to the SQL statement
select s, lead(i, 3, 0) over (partition by si order by i rows between current row and 3 following) over T;
Any help will be appreciated.
You have 3 questions, to which I can only answer the first two at the moment:
How to convert yyyy-mm-dd into a date and substract a day:
dataB = FOREACH data {
date = ToDate(eff_dt, 'yyyy-MM-dd');
dayBefore = SubtractDuration(date, 'P1D');
dayBeforeFormated = ToString(dayBefore, 'yyyy-MM-dd');
GENERATE eff_dt, dayBeforeFormated;
}
I finally had a chance to try the Over and Stich method from piggybank. Here's a working solution.
-- first load the piggybank and define shorthand to Over and Stitch functions
REGISTER '/data/lib/piggybank-0.12.0.jar';
DEFINE Over org.apache.pig.piggybank.evaluation.Over();
DEFINE Stitch org.apache.pig.piggybank.evaluation.Stitch();
-- load the input data
data = LOAD '/data' USING PigStorage('\t') AS (id:int, eff_dt:chararray);
-- generate the previous date (that could be done later)
data_before = FOREACH data {
date = ToDate(eff_dt, 'yyyy-MM-dd');
dayBefore = SubtractDuration(date, 'P1D');
eff_before = ToString(dayBefore, 'yyyy-MM-dd');
GENERATE id as id, eff_dt as eff_dt, eff_before as eff_before;
}
-- Stitch join two bags based on position
-- Over apply a function on a group. Here we use the lead operator to get the next tuple
data_over = FOREACH (GROUP data_before ALL) {
out = Stitch(data_before, Over(data_before.eff_before, 'lead', 0, 1, 1, '9999-99-99'));
GENERATE FLATTEN(out) as (id, eff_dt, eff_before, end_dt);
}
-- finally, we output (we could have transform the date here)
data_final = FOREACH data_over GENERATE id, eff_dt, end_dt;
The output of this script is :
(1,2012-02-28,2013-03-14)
(1,2013-03-15,2014-04-30)
(1,2014-05-01,9999-99-99)

finding range by comparing two tables

I have a table in database as "EXPERIENCE RANGE" with rows as (I can also edit this table according to my need)
0
0.5
1
2
3
5
10
20
I have total experience as integer. I need to display the range in which it lies.
Example - for experience of 8, Range will be 5 - 10
I need to write a sql query. Any ideas will be quite helpful as I am new to SQL.
I cannot hard code it..need to take values from tables only.
Assuming that you are using Oracle, the following query works fine with your existing table:
SELECT
( SELECT MAX( value ) FROM experience_range WHERE value <= :search_value ) AS range_start,
( SELECT MIN( value ) FROM experience_range WHERE value > :search_value ) AS range_end
FROM dual;
No need to hardcode the values, and no need to store the lower and upper bounds redundantly.
you can do it with CASE Expression, the syntax is:
SELECT
CASE
WHEN experience >= 0 and experience <= 4 THEN '0-4'
WHEN experience >= 5 and experience <= 10 THEN '5-10'
.....
ELSE 'No Range'
END as Range
FROM Table_Name
If you do need to store the ranges in a table, I would personally suggest altering the structure of the range table (Assuming you are able to), maybe something like:
|--------------------------------------|
|ID|DESCRIPTION|LOWER_LIMIT|UPPER_LIMIT|
|1 | 0 - 0.5 | 0 | 0.5 |
|2 | 0.5 - 1 | 0.5 | 1 |
...
Then you could get your range by running something like:
SELECT DESCRIPTION FROM [RANGES] WHERE <VALUE> >= LOWER_LIMIT AND <VALUE> < UPPER_LIMIT
EDIT - Mikhail's answer also works, defining the ranges within the query itself is also an option and probably simpler providing you don't need to get these ranges from several reports. (That would require editing every report/query individually)
EDIT 2 - I see you are not able to hardcode the ranges, in which case the above would be best. Can I ask why you are unable to hardcode them?

How do you select using a range of strings in SQL?

I have a table of vehicles with registration numbers, and want to select a subset of them that are between some user-supplied 'from' and 'to' values.
So lets say the table looks like this:
id reg_num
1 DD1111
2 DD1112
3 DE2245
4 EE5678
5 EF6547
The SQL I have so far looks like this:
select *
from vehicles
where reg_num >= 'DD' -- this value is user supplied
and reg_num <= 'DE' -- and so is this one
Which should (by my thinking) return:
1 DD1111
2 DD1112
3 DE2245
But instead, only returns:
1 DD1111
2 DD1112
I imagine that SQL server sees 'DE2245' as greater than 'DE', and so excludes the row.
My question: How do I get SQL server to include all rows that start with 'DE'?
You have to add 'zzzz's at the end as many as necessary to match your column width definition.
select * from vehicles
where reg_num >= 'DD' and reg_num <= 'DE' + 'ZZZZZZZZZZZZ'
where reg_num >= #userValueFrom
and left(reg_num,char_length(#userValueTo) <= #userValueTo
but please note that this where does not utilize any index because of a function on the column in SARG.
If the format is guaranteed, you can simply do:
SELECT *
FROM vehicles
WHERE LEFT(reg_num, 2) BETWEEN 'DD' AND 'DE'
But again, this is supposedly not SARGable - which always baffles me, because surely an index on reg_num can be used...
DE2245 is not less than DE. To make it more clear, DE2245 is less than DE3