I am stuck with one query where I want to do several things:
- I have a table with some events, each event has a limited amount of people that can apply to it
- In other table I keep record of who applied to which event
- On the website, to the user, I want to display only the events that he can apply to (to be more precise, the events he hasn't applied to; once he applies, the event should no longer be visible to the user) AND/OR that number of applied people is less than max_no_people (when an event is full, no need to show it to the user
I got help from here in another thread, but only for part of the problem.
Tables look like this:
other_events (eventID, max_no_people, active)
event_applied (eventID, userID)
Here is the query:
SELECT count(event_applied.eventid) AS no_applied,
e.eventID, e.max_no_people, e.active
FROM other_events e
INNER JOIN
event_applied ON event_applied.eventID = e.eventID
LEFT JOIN
event_applied ea ON ea.eventID
AND event_applied.userID = :userID
WHERE ea.eventID IS NULL
AND e.active = 1
GROUP BY event_applied.eventID
HAVING (no_applied < max_no_people)
This query works fine - BUT - for every other user than the currently selected one.
Let's say you have userID = 42 (which is not in events_applied table paired with eventID = 123):
other_events
eventID | max_no_people | active
--------|---------------|-------
21 | 5 | 1
and in events_applied
eventID | userID
--------|-------
123 | 10
123 | 11
123 | 12
123 | 13
123 | 14
The Query will return empty row (because event is maxed out and you cannot apply anymore), but if you are userID = 12 (which is in the events_applied table and has eventID pair), the result will look like this:
no_applied | e.eventID | e.max_no_people | e.active
-----------|-----------|-----------------|---------
**4** | 123 | 5 | 1
and hence, the event will be visible and you will be able to apply again and again (I know I have to add some additional controls, but I'll tackle that later).
So, the problem is that this query somehow discards the row of a current user and doesn't add it into final count.
Anybody? :)
The main issue with your query is that you are not correlating the left joined query on a specific eventID.
Consider the following code:
SELECT
COUNT(*) AS no_applied,
e.eventID,
e.max_no_people,
e.active
FROM
other_events e
INNER JOIN event_applied ea1 ON ea1.eventID = e.eventID
LEFT JOIN event_applied ea2 ON ea1.eventID = ea2.eventID AND ea2.userID = :userID
WHERE ea2.eventID IS NULL AND e.active = 1
GROUP BY e.eventID, e.max_no_people, e.active
HAVING (no_applied < e.max_no_people)
If you run this query in this DB Fiddle with your sample data:
with userID = 42, it returns no record: this is because there is no event available (there is only one event, which is already full)
with userID = 12, it returns no record: this is because this user already applied to the only available event
I would suggest creating a more representative dataset, for example by creating another event that is not full, so you can thouroughly validate the query.
The problem is that in your current query you are only explicitly excluding the entries where the User has applied, and therefore the count is reduced by one. You instead need to exclude all the events where they applied.
Since you're using a LEFT JOIN you'll need to use a subquery to select all those events and then only join on the events (and not join on the user).
SELECT count(event_applied.eventid) AS no_applied, e.eventID, e.max_no_people, e.active
FROM other_events e
INNER JOIN event_applied ON event_applied.eventID = e.eventID
LEFT JOIN (SELECT * FROM event_applied WHERE event_applied.userID = :userID ) ea
ON ea.eventID = e.eventID
WHERE ea.eventID IS NULL AND e.active = 1
GROUP BY e.eventID, e.max_no_people, e.active
HAVING (no_applied < max_no_people)
Thank you both Graeme Tate and GMB, both approaches work perfectly!
I learned something new today :)
Related
sorry about the title, very bad at titles!
I have these relevant tables:
Times
+--------+----------+-------------+------+---------+
| TimeID | PlayerID | MapCourseID | Mode | RunTime |
+--------+----------+-------------+------+---------+
Checkpoints
+--------------+--------+------------+---------+
| CheckpointID | TimeID | Checkpoint | RunTime |
+--------------+--------+------------+---------+
Maps
+-------+------+
| MapID | Name |
+-------+------+
MapCourses
+-----------+-------+--------+
| MapCourse | MapID | Course |
+-----------+-------+--------+
RunTime stores their "time" as an int
I have a query that selects the fastest times for a specific map MapID on all courses and all modes
SELECT MIN(Times.RunTime), MapCourses.Course, Times.Mode
FROM Times
INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID
INNER JOIN Players ON Players.PlayerID=Times.PlayerID
WHERE Players.Cheater=0 AND MapCourses.MapID=%d
GROUP BY MapCourses.Course, Times.Mode;
Which works fine, but now I want to make another query that selects the checkpoints of the fastest times, since checkpoints are associated with a TimeID
I've tried doing something like this
SELECT * FROM Checkpoints
INNER JOIN (
SELECT
MIN(Times.RunTime),
MapCourses.Course,
Times.Mode,
MapCourses.MapID,
Players.SteamID32
FROM
Times
INNER JOIN MapCourses ON MapCourses.MapCourseID = Times.MapCourseID
INNER JOIN Players ON Players.PlayerID = Times.PlayerID
WHERE
Players.Cheater = 0
AND MapCourses.MapID = %d
GROUP BY
MapCourses.Course,
Times.Mode
) AS wrs ON Checkpoints.TimeID = wrs.TimeID
Also tried something like this:
SELECT
Checkpoints.RunTime,
Checkpoints.Checkpoint,
MapCourses.Course,
Times.Mode,
Times.TimeID,
Players.Alias
FROM
Checkpoints
INNER JOIN Times ON Times.TimeID = Checkpoints.TimeID
INNER JOIN MapCourses ON MapCourses.MapCourseID = Times.MapCourseID
INNER JOIN Players ON Players.PlayerID = Times.PlayerID
WHERE
Players.Cheater = 0
AND MapCourses.MapID = %d
AND Times.RunTime = (
SELECT
MIN(Times.RunTime)
FROM
Times
WHERE
Times.MapCourseID = MapCourses.MapCourseID
AND Mode = Times.Mode
)
Neither seem to really work, any help would be great, thanks!
Basically, if i'm working on map id 50, I already have a query that gets the fastest time on map id 50 for all modes & courses, the query i'm trying to build is a query that gets the checkpoints of the fastest times for every course and mode on map id 50
We could do something like this:
SELECT c.checkpointid
, c.timeid
, c.checkpoint
, c.runtime
, ...
FROM ( SELECT t.mapcourseid
, t.mode
, MIN(r.runtime) AS min_runtime
FROM Times r
JOIN Players p
ON p.playerid = r.playerid
AND p.cheater = 0
JOIN MapCourses s
ON s.mapcourseid = t.mapcourse_id
AND s.mapid = ?
GROUP
BY t.mapcourseid
, t.mode
) q
JOIN Times t
ON t.runtime = q.min_runtime
AND t.mapcourseid = q.mapcourseid
JOIN Checkpoints c
ON c.timeid = t.timeid
The trick here is using a query as an inline view. The result returned from the query inside the parens gets returned as a resultset, which is then used like a table by the outer query. MySQL calls it a "derived table".
In the outer SELECT list, we can include references to columns from q and t as well as c.
Note: If there are two or more rows from t that match the minimumn runtime (returned by q), the query will return all of the matching rows.
The specification is a bit unclear. The query in this answer satisfies one particular interpretation.
If I'm following this correctly, you want the shortest Checkpoints.TimeID for each Checkpoint in a similar format to the above?
SELECT MIN(Checkpoints.TimeID), MapCourses.Course, Checkpoints.Checkpoint, Times.Mode
FROM Checkpoints
INNER JOIN Times ON Checkpoints.TimeID=Times.TimeID
INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID
INNER JOIN Players ON Players.PlayerID=Times.PlayerID
WHERE Players.Cheater=0 AND MapCourses.MapID=%d
GROUP BY MapCourses.Course, Checkpoints.Checkpoint, Times.Mode;
Obviously not having access to your Database I can't test this, but, seems to be what you're asking?
[edit]
Ahh, so you want to keep the min times, but would like to add checkpoints into the returned values:
SELECT MIN(Times.RunTime), MapCourses.Course, Checkpoints.Checkpoint, Times.Mode
FROM Times
INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID
INNER JOIN Players ON Players.PlayerID=Times.PlayerID
INNER JOIN Checkpoints ON Checkpoints.TimeID=Times.TimeID
WHERE Players.Cheater=0 AND MapCourses.MapID=%d
GROUP BY MapCourses.Course, Checkpoints.Checkpoint, Times.Mode;
So this should return your fastest time, the map, checkpoints and then mode.
We have a Mariadb table with users details in (users)
We have a 2nd table for review dates (reviewdates)
| reviewID |USERID |A review date |
| 001 | 123 |2017-01-08 09:02:10 |
etc...
That records review meeting dates against each user.
We have a 3rd table (userdata) with multiple types of user data in. Field id 101 is new targets for the review. Field id 98 is old targets from the previous review.
|dataID|Userid |Field ID |FieldValue |UpdatedOn |UpdatedBy|
-------------------------------------------------------------
|0001 |123 | 101 |my new target|2017-01-10|145 |
|0002 |123 | 98 |my old target|2017-01-10|0 |
New Target (field ID 101) gets copied to old targets (field id 98) when the review is completed.
Either field can be updated at any time.
Each user has many review dates. I need to compare the first value of the old field after the review is complete with the last value before the review date to make sure they have copied over correctly. As users can change either field it has to a comparison of immediately before and after the completion process.
so I join users and reviewdates
select users.userid,users.username,reviewdates.meetingdate
from companyusers users
join reviewdates on reviewdates.userid = users.userid
and this gives me all the review dates for all users
I then tried to find the most recent entry for the 101 field :
select users.userid,users.username, reviewdates.meetingdate, latest101.fielddetails,latest101.updatedon
from users
join reviewdates on reviewdates.userid = users.userid
left join (select userdata.* from userdata u1
where u1.fieldid = 101
and u1.updatedOn = (select max(u2.updatedon)
from userdata u2
where u1.userid = u2.userid
and u2.fieldid = 101)
) as latest101 on (latest101.userid = users.userid)
and this works OK too but when I try to find the most recent entry before each review date:
select users.userid,users.username,reviewdates.meetingdate,latest101.fielddetails,latest101.updatedon
from users
join reviewdates on reviewdates.userid = users.userid
left join (select userdata.* from userdata u1
where u1.fieldid = 101
and u1.updatedOn = (select max(u2.updatedon)
from userdata u2
where u1.userid = u2.userid
and u2.fieldid = 101
#date limit
and u2.updatedOn < reviewdates.meetingdate)
) as latest101 on (latest101.userid = users.userid)
I get an
"unknown column reviewdates.meetingdate in where clause"
error. I've found loads of statements saying I can't refer to an outer join in a subquery but none that provide possible answers that apply to these date constraints.
Any help or pointers would be appreciated.
I do not see that you are filtering the most outer 'reveiwdates' table with anything specific, it is just used to display the 'meetingdate' based on the inner queries,
in that case firstly, there is no reason to use the same reference inside subquery.
Secondly, there are so many 'meetingdates', which specific meeting date are we comparing against the 'updatedOn' ?
You cannot join two tables on an in-equality condition.
Either a constant review date filter needs be applied or a procedure needs be written to loop through each meeting date for a user in the 'reviewdates' table.
If you just care about the latest review for that user then you could fetch and compare the latest 'reviewdate' just like how you are comparing the latest updated on, 'updatedOn' does not seem to have time component and to avoid other issues use date() or equivalent while comparing.
select users.userid,users.username,reviewdates.meetingdate,latest101.fielddetails,latest101.updatedon
from users
join reviewdates on reviewdates.userid = users.userid
left join (select userdata.* from userdata u1
where u1.fieldid = 101
and u1.updatedOn = (select max(u2.updatedon)
from userdata u2
where u1.userid = u2.userid
and u2.fieldid = 101
#date limit
and u2.updatedOn < (select date(max(reviewdates.meetingdate)) from reviewdates where u2.userid = reviewdates.userid))
) as latest101 on (latest101.userid = users.userid)
There is a query which select some data. I'm creating a suppression table which would ignore the rows containing certain data.
suppression: occasion_id | days_before
reminder: id | days_before | occasion_id
I'm making use of NOT EXIST to ignore selection of certain reminders.
The query is
SELECT id
from Reminder AS r
WHERE NOT EXIST (SELECT 1
FROM Suppression s
WHERE s.occasion_id = r.occasion_id
AND s.days_before = r.days_before)
Reminder:1) 101| 1 |18
2) 102| 7| 18
Suppresion: 18 | 1
The 1st reminder should be ignored and the 2nd one should be included.
As an example, if suppression table contain occasion_id - 18 and days_before- 1 the select should ignore reminder containing those data.
The sub query returns '1' in the case of 2nd reminder also. Why does it happens even if the statement after WHERE clause yields no result?
You are joining by the wrong condition.
Change the joining condition from s.id = r.id to s.occasion_id = r.occasion_id
SELECT r.*
FROM Reminder AS r
inner JOIN Supression s ON s.occasion_id = r.occasion_id
AND s.days_before <> r.days_before
Fiddle
Your query also works.. Just change the joining condition
SELECT id
from Reminder AS r
WHERE NOT EXISTs (SELECT 1
FROM Supression s
WHERE s.occasion_id = r.occasion_id
AND s.days_before = r.days_before)
I believe what you're trying to accomplish could be more easily done with a simple LEFT JOIN and an IS NULL in your where clause:
SELECT r.id
FROM Reminder AS r
LEFT JOIN Supression s ON s.id = r.id AND s.days_before = r.days_before
WHERE s.id IS NULL
account
act_id | act_name | grp_id | grp_id_2
2 | test | 4 | 10
promotion
pml_id | act_id | grp_id
2 | 2 | null
3 | null | 4
4 | null | 10
I have two tables, shown above (trimmed down). Account has about 15000 records, promotion about 20000.
Customer basically wants it so that they could search for an account name, say 'test'. And it would show the promotions 2, 3 and 4.
Promotion 3 would show because the account 'test' has grp_id = 4.
Promotion 4 would show because account 'test' has grp_id_2 = 10.
I originally did this with a couple of joins
SELECT pml_id FROM promotion
LEFT JOIN account AS account1 ON promotion.act_id = account1.act_id
LEFT JOIN account AS account2 ON promotion.grp_id = account2.grp_id
LEFT JOIN account AS account3 ON promotion.grp_id = account3.grp_id_2
WHERE account1.act_name LIKE 'test%' or account2.act_name LIKE 'test%'
or account3.act_name LIKE 'test%'
GROUP BY pml_id
The problem with this is this ended up taking a long time when I started having to join 5 times to the account table. It also gave me about 10000000 records (without the group by). Most of the promotions use a grp_id, rarely do they use an act_id.
Is there any way to search the act_name column quickly in this scenario? Without having to do so many joins?
I have single indexes on act_id, pml_id, grp_id, grp_id_2
Note: This is part of a query where the user may not search by account. I.E the where clause may not always be there
Use an INNER JOIN instead to avoid scanning the entire table :
SELECT p.pml_id
FROM account a
INNER JOIN promotion p
ON (p.act_id = a.act_id OR p.grp_id = a.grp_id OR p.grp_id = a.grp_id_2)
WHERE a.act_name LIKE "test%";
Is this any faster?
SELECT pml_id FROM promotion p
LEFT JOIN account a
ON (p.act_id = a.act_id OR p.grp_id = a.grp_id OR p.grp_id = a.grp_id_2)
WHERE a.act_name LIKE "test%";
Try this one as well, if you have an index on act_id, this should be quite a bit faster:
SELECT * FROM promotion p
LEFT JOIN account a
ON (p.act_id = a.act_id OR p.grp_id = a.grp_id OR p.grp_id = a.grp_id_2)
WHERE a.act_id IN (
SELECT act_id FROM account WHERE act_name LIKE "test%"
)
I have two tables reports & viewedids
the first one stores data and the second one stores user IDs that viewed data:
viewedids:
+-------+------+
| uid | rid |
+-------+------+
| 2 | 5 |
+-------+------+
each (uid,rid) means the uid has viewd rid
I want to select * from reports table and add view state (0 or 1) for current user to it. (A JOIN statement)
How can I do this?
You can use a LEFT JOIN:
SELECT reports.*, viewedids.uid IS NOT NULL as view_state
FROM
reports LEFT JOIN viewedids
ON reports.id = viewedids.rid
AND viewedids.uid = #current_user
This will return all reports, and will try to join reports table with viewedids ON reports.id = viewedids.rid AND viewedids.uid = #current_user. If the join succedes, viewedids.uid will be not null, and viewedids.uid IS NOT NULL will be evaluated to 1. It will be evaluated 0 otherwise.
Please see fiddle here.