Find the date range range within the same table for a particular MySQL user

I'm by no means an expert on MySQL, so I'm looking for any help on this.

I need to perform a simple test (in principle), I have this (simplified) table:

tableid | userid | car | From | To -------------------------------------------------------- 1 | 1 | Fiesta | 2015-01-01 | 2015-01-31 2 | 1 | MX5 | 2015-02-01 | 2015-02-28 3 | 1 | Navara | 2015-03-01 | 2015-03-31 4 | 1 | GTR | 2015-03-28 | 2015-04-30 5 | 2 | Focus | 2015-01-01 | 2015-01-31 6 | 2 | i5 | 2015-02-01 | 2015-02-28 7 | 2 | Aygo | 2015-03-01 | 2015-03-31 8 | 2 | 206 | 2015-03-29 | 2015-04-30 9 | 1 | Skyline | 2015-04-29 | 2015-05-31 10 | 2 | Skyline | 2015-04-29 | 2015-05-31 

I need to find two things here:

  • If any user has a date match in their car assignments for more than one day (the end of the task may be on the same day when a new appointment begins).
  • Whether there were any two users trying to get the same car assigned on the same date, or the date ranges overlap for them on the same car.

So the query (or queries) I'm looking for should return these lines:

 tableid | userid | car | From | To -------------------------------------------------------- 3 | 1 | Navara | 2015-03-01 | 2015-03-31 4 | 1 | GTR | 2015-03-28 | 2015-04-30 7 | 2 | Aygo | 2015-03-01 | 2015-03-31 8 | 2 | 206 | 2015-03-29 | 2015-04-30 9 | 1 | Skyline | 2015-04-29 | 2015-05-31 10 | 2 | Skyline | 2015-04-29 | 2015-05-31 

It seems to me that I stuck my head against the wall here, I would be happy that I could make these comparisons in separate queries. I need to display them in one table, but I could always join the results.

I did research and several hours of testing, but I can’t get anywhere from the result I want.

SQLFiddle with above test data

I tried these btw posts (they weren't quite what I needed, but they were close enough, or so I thought):

Comparing two date ranges within the same table

How to compare text column values ​​from one table

This was the closest solution I could find, but when I tried it on one table (connecting to the table for myself), I got crazy results: Checking the table for time overlap?

EDIT

As a workaround, I took a different approach, similar to the position that I found during my research (see above). Now I will check to see if the new lease / lease date matches any date range in the table. If so, I will save the id (s) of the strings with which the date matches. That way, at least I can mark the overlays and allow the user to view the marked lines and allow any overlaps manually.

Thanks to everyone who offered their help, I will mark the Filipino answer as selected (in the next 24 hours) if someone has no better way to achieve this. I have no doubt that after his answer I can ultimately achieve the results that I need. At the moment, although I need to make any decision that works, since I need to finish my project in the next few days, therefore, change the approach.

Edit # 2

Both answers are brilliant and for those who think that this post has the same problem as me, read them and look at the violins! :) They included a lot of amazing brain work! Temporarily I had to go with the solution that I mention in No. 1, “Editing,” but I will adapt my requests to go with the @ Ryan Vincent + @philipxy edit / comments approach of ignoring the initial one-time overlap.

+5
source share
2 answers

Here is the first part: Overlapping cars per user ...

SQLFiddle - correlated query and join query

The second part is more than one user in one car at the same time: SQLFiddle - correlated query and query aggregation . Request below ...

I use correlated queries:

You will most likely need indexes for userid and 'car'. However, please check the “explanation plan” to find out how it accesses the data. And just try :)

Overlapping cars per user

Request:

 SELECT `allCars`.`userid` AS `allCars_userid`, `allCars`.`car` AS `allCars_car`, `allCars`.`From` AS `allCars_From`, `allCars`.`To` AS `allCars_To`, `allCars`.`tableid` AS `allCars_id` FROM `cars` AS `allCars` WHERE EXISTS (SELECT 1 FROM `cars` AS `overlapCar` WHERE `allCars`.`userid` = `overlapCar`.`userid` AND `allCars`.`tableid` <> `overlapCar`.`tableid` AND NOT ( `allCars`.`From` >= `overlapCar`.`To` /* starts after outer ends */ OR `allCars`.`To` <= `overlapCar`.`From`)) /* ends before outer starts */ ORDER BY `allCars`.`userid`, `allCars`.`From`, `allCars`.`car`; 

Results:

 allCars_userid allCars_car allCars_From allCars_To allCars_id -------------- ----------- ------------ ---------- ------------ 1 Navara 2015-03-01 2015-03-31 3 1 GTR 2015-03-28 2015-04-30 4 1 Skyline 2015-04-29 2015-05-31 9 2 Aygo 2015-03-01 2015-03-31 7 2 206 2015-03-29 2015-04-30 8 2 Skyline 2015-04-29 2015-05-31 10 

Why does it work? or How I think about it:

I use a correlated query, so I don’t have duplicates that can be dealt with, and this is probably the easiest to understand for me. There are other ways to express a request. Each has its own advantages and disadvantages. I want something that I can easily understand.

Requirement: for each user, make sure they do not have two or more cars at the same time.

So, for each user record (AllCars) check the full table (overlapCar) to see if you can find another record that overlaps during the current record. If we find it, then select the current record that we are checking (on all maps).

Therefore overlap check:

  • allCars userid and overLap userid must be the same
  • allCars car recording and allCars car recording must be different
  • the allCars time slot and the allCars time slot must overlap.

    Time Range Check:

    Instead of checking for overlapping times, use positive tests. The easiest approach is to check if it overlaps and apply NOT to it.

One car with more than one user at a time ...

Request:

 SELECT `allCars`.`car` AS `allCars_car`, `allCars`.`userid` AS `allCars_userid`, `allCars`.`From` AS `allCars_From`, `allCars`.`To` AS `allCars_To`, `allCars`.`tableid` AS `allCars_id` FROM `cars` AS `allCars` WHERE EXISTS (SELECT 1 FROM `cars` AS `overlapUser` WHERE `allCars`.`car` = `overlapUser`.`car` AND `allCars`.`tableid` <> `overlapUser`.`tableid` AND NOT ( `allCars`.`From` >= `overlapUser`.`To` /* starts after outer ends */ OR `allCars`.`To` <= `overlapUser`.`From`)) /* ends before outer starts */ ORDER BY `allCars`.`car`, `allCars`.`userid`, `allCars`.`From`; 

Results:

 allCars_car allCars_userid allCars_From allCars_To allCars_id ----------- -------------- ------------ ---------- ------------ Skyline 1 2015-04-29 2015-05-31 9 Skyline 2 2015-04-29 2015-05-31 10 

Edit:

In connection with the comments of @philipxy, on time ranges requiring a check of "greater than or equal to", I updated the code here. I have not changed SQLFiddles .

+2
source

For each input and output table, find its value. Those. a statement template, parameterized by column names, aka predicate what a row makes into a true or false statement, aka a sentence. The table contains rows that turn its predicate into a true sentence. Those. rows that make a true sentence go to the table and rows that make a false sentence. For example, for an input table:

 rental [tableid] was user [userid] renting car [car] from [from] to [to] 

Then we express the predicate of the output table in terms of the predicate of the input table. Do not use descriptions like yours 1 and 2:

  • If any user has a date match in their car assignments for more than one day (the end of the task may be on the same day when a new appointment begins).

Instead, find the predicate that an arbitrary row is specified in the table:

 rental [tableid] was user [user] renting car [car] from [from] to [to] in self-conflict with some other rental 

In order for the DBMS to calculate the lines that make it true, we must express it in terms of our predicate (s) plus literals and conditions:

 -- query result holds the rows where FOR SOME t2.tableid, t2.userid, ...: rental [t1.tableid] was user [t1.userid] renting car [t1.car] from [t1.from] to [t1.to] AND rental [t2.tableid] was user [t2.userid] renting car [t2.car] from [t2.from] to [t2.to] AND [t1.userid] = [t2.userid] -- userids id the same users AND [t1.to] > [t2.from] AND ... -- tos/froms id intervals with overlap more than one day ... 

(Inside a SQL SELECT , a cross-sectional JOIN ed statement has column names of the form alias . column . Think of it as another character allowed in column names. The SELECT disables alias . S.)

We convert the query predicate to an SQL query that evaluates the rows that make it true:

  • A table predicate is replaced by a table alias.
  • To use the same predicate / table multiple times, create aliases.
  • Changing the old column to new in the predicate adds AND old = new .
  • AND predicates are replaced by JOIN .
  • OR predicates are replaced by UNION .
  • AND NOT predicates are replaced with EXCEPT , MINUS or the corresponding LEFT JOIN .
  • AND condition is replaced with WHERE or ON condition .
  • For the predicate true FOR SOME columns to drop or when THERE EXISTS columns to drop , SELECT DISTINCT columns to keep .
  • Etc. (See this one .)

From here (completion of ellipses):

 SELECT DISTINCT t1.* FROM t t1 JOIN t t2 ON t1.userid = t1.userid -- userids id the same users WHERE t1.to > t2.from AND t2.to > t1.from -- tos/froms id intervals with overlap more than one day AND t1.tableid <> t2.tableid -- tableids id different rentals 
  1. Whether there were any two users trying to get the same car assigned on the same date, or the date ranges overlap for them on the same car.

Search for the predicate, which in the line indicates an arbitrary line:

 rental [tableid] was user [user] renting car [car] from [from] to [to] in conflict with some other user rental 

In terms of our predicate (s) plus literals and conditions:

 -- query result holds the rows where FOR SOME t2.* rental [t1.tableid] was user [t1.userid] renting car [t1.car] from [t1.from] to [t1.to] AND rental [t2.tableid] was user [t2.userid] renting car [t2.car] from [t2.from] to [t2.to] AND [t1.userid] <> [t2.userid] -- userids id different users AND [t1.car] = [t2.car] -- .cars id the same car AND [t1.to] >= [t2.from] AND [t2.to] >= [t1.from] -- tos/froms id intervals with any overlap AND [t1.tableid] <> [t2.tableid] -- tableids id different rentals 

UNION queries for predicates 1 and 2 returns rows for which predicate 1 OR predicate 2 .

Try to learn how to express predicates - which rows are in the tables - if only as a goal for an intuitive (auxiliary) query.

PS It’s good to always have data to check boundaries and cases without an edge for a condition that is true and false. For example, try query 1 with a GTR starting on the 31st, blocking only one day, which should not be self-conflicting.

PPS A query using duplicate strings, as for NULL, has rather complex query values. It is difficult to say when a tuple enters or stays off the table and how many times. For queries that have simple intuitive meanings for my matches, they cannot have duplicates. Here, unfortunately, SQL differs from the relational model. In practice, people rely on idioms when they allow fuzzy lines, and they rely on lines that differ due to limitations. For example, joins UNIQUE columns on UNIQUE, PK, and FK. For example: the last step of DISTINCT works only at different times than the version that it does not need; time may or may not be an important implementation problem that affects the wording chosen for a given predicate / result.

+2
source

Source: https://habr.com/ru/post/1238314/


All Articles