I have 2 simple mysql tables. The first 1 called mail and has 2 rows:
sender | receiver
Marley | Bob
Saget | Bob
The second one called block and has 1 row:
blocker | blocked
Bob | Marley
I want to select sender(s) from the first table who sent Bob emails but aren't blocked in the block table. So the results should be:
sender
saget
I tried the following query but it's not returning results:
SELECT * FROM mail
LEFT JOIN block ON (block.blocker = 'Bob')
WHERE (block.blocked <> mail.sender)
The left join will produce null rows for the mismatches.
It's those null rows that you need to filter on.
SELECT * FROM mail
LEFT JOIN block ON (block.blocker = 'Bob')
WHERE block.blocker IS NULL
It's kind of strangle to be joining on a fixed value however, a more common join (given your tables) would be:
SELECT * FROM mail
LEFT JOIN block ON (block.blocker = mail.receiver
and block.blocked = mail.sender)<<-- these should match
WHERE block.blocker IS NULL <<-- select only mismatches
AND mail.receiver like 'bob';
Try this:
SELECT sender
FROM mail m
WHERE NOT EXISTS (SELECT 1 FROM block
WHERE blocker = m.receiver
AND blocked = m.sender)
Related
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 :)
I have a couple of tables, one with source data which I'll call SourceData and another which defines overridden values for a given user if they exist called OverriddenSourceData.
The basic table format looks something this like:
SourceData
| source_id | payload |
--------------------------------
| 1 | 'some json' |
| 2 | 'some more json' |
--------------------------------
OverriddenSourceData
| id | source_id | user_id | overrides
| 1 | 2 | 4 | 'a change' |
------------------------------------------
For a given user, I'd like to return all the Source data rows with the overrides column included. If the user has overridden the source then the column is populated, else it is null.
I started by executing a left join and then including a condition for checking the user like so:
SELECT A.source_id, A.payload, B.overrides from SourceData A
LEFT JOIN OverriddenSourceData B
ON A.source_id = B.source_id
WHERE user_id = 4
but then source rows that weren't overridden wouldn't be included ( it was acting like an inner join) (e.g source id 1)
I then relaxed the query and used a strict left join on source_id.
SELECT A.source_id, A.payload, B.overrides from SourceData A
LEFT JOIN OverriddenSourceData B
ON A.source_id = B.source_id
# WHERE user_id = 4
This can return more data than I need though (e.g other users who have overridden the same source data) and then I have to filter programatically.
It seems like I should be able to craft a query that does this all the DB level and gives me what I need. Any help?
You should add your condition on LEFT JOIN clause, if you use WHERE, mysql will do it with INNER JOIN, so try this;)
SELECT A.source_id, A.payload, B.overrides from SourceData A
LEFT JOIN OverriddenSourceData B
ON A.source_id = B.source_id
AND B.user_id = 4
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.