mysql select rows not in time period (defined by two columns) - mysql

The issue looks like that:
I have a table with classes , each has start_time and end_time (stored as INT - minutes, eg 120 - 2:00, 130 - 2:10), what I need to do is to take selected by user classes and filter rest of them to retrive classes that do not collide with selected. Can anyone help with this ? Maybe some clue?
sample rows:
id start end
1 0 100
2 50 150
3 160 200
4 50 150
5 50 100
6 200 300
if I have selected id=1 then it should return row 3,6 (it covers with 2,4,5 between 50 and 100 so it's impossible to participate in both classes)
if I have selected id=2,6 then it should return row 3
if I have selected id=2 then it should return rows 3,6
if I have selected id=6 then it should return rows 1,2,3,4,5
if I have selected id=3 then it should return rows 1,2

That gives overlapping ids for one given id:
select id from classes c
inner join classes noc on noc.id = <given id>
where c.start > noc.end or c.end < noc.start
;
EDIT:
As far as I understood the extended examples now, you want to give arbitrary subsets of ids as input and want to have all ids which don't overlap with any of them. Let's try:
select c.*
from classes c
left join classes noc on noc.id in (<idlist>)
and noc.start < c.end
and noc.end > c.start
where noc.id is null;
The "<" and ">" might be "<=" and ">=" depending on meaning of "overlap".
You do not sound like searching for subclasses, but I think you will find your way through the set-djungle! ;-)
Your example "selected id=3" gives 1,2,4,5,6 to me, because 3 overlaps with no one.
Explanation:
Go through classes
Look for classes that overlap with the given classes
And show me only those classes where no overlapping class is found.
The noc stands for "not allowed class". If that class is found, there exists an overlapping to your given set.

Based on hardly anything other than assuming one table as you stated, you will need a self join:
SELECT rest.*
FROM classes AS chosen
RIGHT JOIN classes AS rest
ON rest.start_time NOT BETWEEN chosen.start_time AND chosen.end_time
AND rest.end_time NOT BETWEEN chosen.start_time AND chosen.end_time
WHERE chosen.ClassID = '4'
I've aliased the tables as 'chosen' for chosen class, and 'rest' for the rest of the class list. This will return all the 'rest' of the classes that don't overlap your chosen class mentioned in the WHERE clause.

Related

MySQL get duplicate rows in subquery

I want to display all duplicate records from my table, rows are like this
uid planet degree
1 1 104
1 2 109
1 3 206
2 1 40
2 2 76
2 3 302
I have many different OR statements with different combinations in subquery and I want to count every one of them which matches, but it only displays the first match of each planet and degree.
Query:
SELECT DISTINCT
p.uid,
(SELECT COUNT(*)
FROM Params AS p2
WHERE p2.uid = p.uid
AND(
(p2.planet = 1 AND p2.degree BETWEEN 320 - 10 AND 320 + 10) OR
(p2.planet = 7 AND p2.degree BETWEEN 316 - 10 AND 316 + 10)
...Some more OR statements...
)
) AS counts FROM Params AS p HAVING counts > 0 ORDER BY p.uid DESC
any solution folks?
updated
So, the problem most people have with their counting-joined-sub-query-group-queries, is that the base query isn't right, and the following may seem like a complete overkill for this question ;o)
base data
in this particular example what you would want as a data basis is at first this:
(uidA, planetA, uidB, planetB) for every combination of player A and player B planets. that one is quite simple (l is for left, r is for right):
SELECT l.uid, l.planet, r.uid, r.planet
FROM params l, params r
first step done.
filter data
now you want to determine if - for one row, meaning one pair of planets - the planets collide (or almost collide). this is where the WHERE comes in.
WHERE ABS(l.degree-r.degree) < 10
would for example only leave those pairs of planet with a difference in degrees of less than 10. more complex stuff is possible (your crazy conditional ...), for example if the planets have different diameter, you may add additional stuff. however, my advise would be, that you put some additional data that you have in your query into tables.
for example, if all 1st planets players have the same size, you could have a table with (planet_id, size). If every planet can have different sizes, add the size to the params table as a column.
then your WHERE clause could be like:
WHERE l.size+r.size < ABS(l.degree-r.degree)
if for example two big planets with size 5 and 10 should at least be 15 degrees apart, this query would find all those planets that aren't.
we assume, that you have a nice conditional, so at this point, we have a list of (uidA, planetA, uidB, planetB) of planets, that are close to colliding or colliding (whatever semantics you chose). the next step is to get the data you're actually interested in:
limit uidA to a specific user_id (the currently logged in user for example)
add l.uid = <uid> to your WHERE.
count for every planet A, how many planets B exist, that threaten collision
add GROUP BY l.uid, l.planet,
replace r.uid, r.planet with count(*) as counts in your SELECT clause
then you can even filter: HAVING counts > 1 (HAVING is the WHERE for after you have GROUPed)
and of course, you can
filter out certain players B that may not have planetary interactions with player A
add to your WHERE
r.uid NOT IN (1)
find only self collisions
WHERE l.uid = r.uid
find only non-self collisions
WHERE l.uid <> r.uid
find only collisions with one specific planet
WHERE l.planet = 1
conclusion
a structured approach where you start from the correct base data, then filter it appropriately and then group it, is usually the best approach. if some of the concepts are unclear to you, please read up on them online, there are manuals everywhere
final query could look something like this
SELECT l.uid, l.planet, count(*) as counts
FROM params l, params r
WHERE [ collision-condition ]
GROUP BY l.uid, l.planet
HAVING counts > 0
if you want to collide a non-planet object, you might want to either make a "virtual table", so instead of FROM params l, params r you do (with possibly different fields, I just assume you add a size-field that is somehow used):
FROM params l, (SELECT 240 as degree, 2 as planet, 5 as size) r
multiple:
FROM params l, (SELECT 240 as degree, 2 as planet, 5 as size
UNION
SELECT 250 as degree, 3 as planet, 10 as size
UNION ...) r

Need to find whether overlap is present in sql

I haev been stuck in a problem from quite some time and trying to figure out how to solve this using sql.
I have a table which has 3 columns :
LowerLimit UpperLimit Code
1 10 A
10.01 20 B
20.01 40 C
40.01 100 D
So in such case I need to check if there overlap present or not. The Upperlimit should not match with the LowerLimit of the next row and the permissible difference is only 0.01 . Is it possible to solve this using queries or do I need to iterate the whole range and find whether there is no overlap???
Any help is appreciated.
You can do this with exists to get the first row of overlap. For your specific logic:
select t.*
from t
where exists (select 1
from t t2
where t2.upperlimit >= t.lowerlimit and
t2.upperlimit < t.upperlimit + 0.01
);
If you want both rows, you can formulate this as a join or using a second exists to get the previous row.
I don't like your data representation. I would simple make the lower bound inclusive and the upper bound exclusive. Then the next lower bound could simply be the previous upper bound. You would not be able to use between but that is a bad idea anyway on numbers with decimal parts.

how to get lowest available unique value

im trying to get the lowest available unique value but with some parameters.
i found this
and my statement will look like this
SELECT MIN(t1.cloakroomnumber + 1) AS nextID FROM Wardrobe_CloakTable t1
LEFT JOIN Wardrobe_CloakTable t2
ON t1.cloakroomnumber + 1 = t2.cloakroomnumber
WHERE t1.payingcustomerID = 2 AND t1.cloakroomsection = 'B' AND t2.cloakroomnumber IS NULL
but this is returning something that is not correct at all.
So the question is where should i put my WHERE to the MIN argument.
my tables are like:
ID (unique not important)
payingcustomerID (FK - thats why i need to get lowest for just 1 customer)
deviceID (not important)
terminalnumber (not important)
qrcode (not important)
cloakroomsection (defines if query is inserted in the same physical section)
cloakroomnumber (defines the unique number but its only unique in the
way that its unique in the same section AND for the same
paycustomerID)
if i have 5 different cloakroomnumbers in my database with same cloakroomsection and same payingcustomerID looking like
1
2
3
5
6
i want to be able to get number 4
the statement in the start i got from the other post is working correctly without my try to manipulate it. but its not taking into account payingcustomer and cloakroomsection. thats what im trying to add.

MySQL query for items where average price is less than X?

I'm stumped with how to do the following purely in MySQL, and I've resorted to taking my result set and manipulating it in ruby afterwards, which doesn't seem ideal.
Here's the question. With a dataset of 'items' like:
id state_id price issue_date listed
1 5 450 2011 1
1 5 455 2011 1
1 5 490 2011 1
1 5 510 2012 0
1 5 525 2012 1
...
I'm trying to get something like:
SELECT * FROM items
WHERE ([some conditions], e.g. issue_date >= 2011 and listed=1)
AND state_id = 5
GROUP BY id
HAVING AVG(price) <= 500
ORDER BY price DESC
LIMIT 25
Essentially I want to grab a "group" of items whose average price fall under a certain threshold. I know that my above example "group by" and "having" are not correct since it's just going to give the AVG(price) of that one item, which doesn't really make sense. I'm just trying to illustrate my desired result.
The important thing here is I want all of the individual items in my result set, I don't just want to see one row with the average price, total, etc.
Currently I'm just doing the above query without the HAVING AVG(price) and adding up the individual items one-by-one (in ruby) until I reach the desired average. It would be really great if I could figure out how to do this in SQL. Using subqueries or something clever like joining the table onto itself are certainly acceptable solutions if they work well! Thanks!
UPDATE: In response to Tudor's answer below, here are some clarifications. There is always going to be a target quantity in addition to the target average. And we would always sort the results by price low to high, and by date.
So if we did have 10 items that were all priced at $5 and we wanted to find 5 items with an average < $6, we'd simply return the first 5 items. We wouldn't return the first one only, and we wouldn't return the first 3 grouped with the last 2. That's essentially how my code in ruby is working right now.
I would do almost an inverse of what Jasper provided... Start your query with your criteria to explicitly limit the few items that MAY qualify instead of getting all items and running a sub-select on each entry. Could pose as a larger performance hit... could be wrong, but here's my offering..
select
i2.*
from
( SELECT i.id
FROM items i
WHERE
i.issue_date > 2011
AND i.listed = 1
AND i.state_id = 5
GROUP BY
i.id
HAVING
AVG( i.price) <= 500 ) PreQualify
JOIN items i2
on PreQualify.id = i2.id
AND i2.issue_date > 2011
AND i2.listed = 1
AND i2.state_id = 5
order by
i2.price desc
limit
25
Not sure of the order by, especially if you wanted grouping by item... In addition, I would ensure an index on (state_id, Listed, id, issue_date)
CLARIFICATION per comments
I think I AM correct on it. Don't confuse "HAVING" clause with "WHERE". WHERE says DO or DONT include based on certain conditions. HAVING means after all the where clauses and grouping is done, the result set will "POTENTIALLY" accept the answer. THEN the HAVING is checked, and if IT STILL qualifies, includes in the result set, otherwise throws it out. Try the following from the INNER query alone... Do once WITHOUT the HAVING clause, then again WITH the HAVING clause...
SELECT i.id, avg( i.price )
FROM items i
WHERE i.issue_date > 2011
AND i.listed = 1
AND i.state_id = 5
GROUP BY
i.id
HAVING
AVG( i.price) <= 500
As you get more into writing queries, try the parts individually to see what you are getting vs what you are thinking... You'll find how / why certain things work. In addition, you are now talking in your updated question about getting multiple IDs and prices at apparent low and high range... yet you are also applying a limit. If you had 20 items, and each had 10 qualifying records, your limit of 25 would show all of the first item and 5 into the second... which is NOT what I think you want... you may want 25 of each qualified "id". That would wrap this query into yet another level...
What MySQL does makes perfectly sense. What you want to do does not make sense:
if you have let's say 4 items, each with price of 5 and you put HAVING AVERAGE <= 7 what you say is that the query should return ALL the permutations, like:
{1} - since item with id 1, can be a group by itself
{1,2}
{1,3}
{1,4}
{1,2,3}
{1,2,4}
...
and so on?
Your algorithm of computing the average in ruby is also not valid, if you have items with values 5, 1, 7, 10 - and seek for an average value of less than 7, element with value 10 can be returned just in a group with element of value 1. But, by your algorithm (if I understood correctly), element with value 1 is returned in the first group.
Update
What you want is something like the Knapsack problem and your approach is using some kind of Greedy Algorithm to solve it. I don't think there are straight, easy and correct ways to implement that in SQL.
After a google search, I found this article which tries to solve the knapsack problem with AI written in SQL.
By considering your item price as a weight, having the number of items and the desired average, you could compute the maximum value that can be entered in the 'knapsack' by multiplying desired_cost with number_of_items
I'm not entirely sure from your question, but I think this is a solution to your problem:
SELECT * FROM items
WHERE (some "conditions", e.g. issue_date > 2011 and listed=1)
AND state_id = 5
AND id IN (SELECT id
FROM items
GROUP BY id
HAVING AVG(price) <= 500)
ORDER BY price DESC
LIMIT 25
note: This is off the top of my head and I haven't done complex SQL in a while, so it might be wrong. I think this or something like it should work, though.

MySQL SELECT and return unknown number of related foreign keys

I have two tables in a database that look like this
Course:
ixCourse (primary key)
enType (enum: 3 possible values)
sCourse (string)
sTitle (string)
sWhen (string)
sDescription (string)
Item:
ixItem (primary key)
ixCourse (foreign key references Course.ixCourse)
bLink (boolean)
sAddress (string)
sDescription (string)
In this setup, I can have a given Course and I can have multiple related Items for any Course. There is no uniform number of Items per Course. Do I need to perform a separate query to pull all of the related Items for each Course or can I do this in 1 select statement without returning redundant information?
without returning redundant information? If you mean without repeating the Course Details, then no - not easily in one query. There are some horrendous queries (in terms of both the idea and the code) to return the following: but I wouldn't advice exploring it.
ixCourse, enType .. , ixItem, sDescription
1 1 1 first for course 1
2 Second for course 1 << notice no course details
2 1 3 first for course #2
But the normal practice is to JOIN between the two producing
ixCourse, enType .. , ixItem, sDescription
1 1 1 first for course 1
1 1 2 Second for course 1 << course details repeated per item
2 1 3 first for course #2
The query for such would be
select c.*, i.*
from course c
left join item i on c.ixCourse = i.ixCourse
Here I used LEFT JOIN, which could produce this (e.g. course 2 has no items)
ixCourse, enType .. , ixItem, sDescription
1 1 1 first for course 1
1 2 Second for course 1 << course details repeated
2 1 NULL NULL << nothing for item columns
3 1 4 first for course #3
Changing it to INNER JOIN (or abbreviated JOIN) will remove the courses that have no description.
Just use a join:
SELECT * FROM Course C
JOIN Item I ON C.ixCourse=I.ixCourse
Yes, you will get the course info repeated.. there's no way to just return one row of course data and then multiple item rows without doing two queries. You can return just one course's worth of items in one query if that's what you're asking..
It's not clear what you want as output, but this:
SELECT c.*, i.*
FROM COURSE c
LEFT JOIN ITEM i ON i.ixcourse = c.ixcourse
...will return all courses, and any items related to the course. If a course doesn't have items related to it, the ITEM columns in the output will be NULL. That said, the course columns will hold duplicate values if there is more than one item related to it -- but the ITEM columns will be different.
If you only want a list of courses that have items, use:
SELECT c.*, i.*
FROM COURSE c
JOIN ITEM i ON i.ixcourse = c.ixcourse
What's a JOIN?
This is a good primer on JOINs, and the differences between them.