INNER JOIN with 2 CASE - mysql

I try to do an INNER JOIN depending of the result of CASE.
SELECT *
FROM album
INNER JOIN (
SELECT album_type CASE album.album_type
WHEN 1 THEN "album_int"
WHEN 2 THEN "album_ext"
END FROM album)
AS type ON type.album_id = album.id
WHERE album.id = 6
LIMIT 1;
I have see some examples on this site, but nothing work in my case.
Someone can help me?

I have 3 tables (album, album_int and album_ext).
I want to join album_int or album_ext to album.
if album.album_type = 1 I join album_int,
else if album.album_type = 2 I join album_ext.
In album_int and album_int I have a column name album_id (same unique
id in album)
You do not need any case expressions or even a where clause.
SELECT a.`id`, COALESCE(aint.`something`, aext.`something`) as something, ...
FROM ALBUM A
LEFT OUTER JOIN ALBUM_INT AS AINT ON A.ID = AINT.ALBUM_ID AND A.ALBUM_TYPE = 1
LEFT OUTER JOIN ALBUM_EXT AS AEXT ON A.ID = AEXT.ALBUM_ID AND A.ALBUM_TYPE = 2
AND A.ALBUM_TYPE = 1 as a join condition means that ALBUM_INT will ONLY join to ALBUM rows where album_type = 1
similarly:
AND A.ALBUM_TYPE = 2 as a join condition means that ALBUM_EXT will ONLY join to ALBUM rows where album_type = 2
please note that an answer previously given by Gordon Linoff uses the same join logic, I have just attempted to emphasize how it meets the described requirements.

case doesn't work on tables. You can use left join:
SELECT *
FROM album a LEFT JOIN
album_int ai
ON ai.album_id = a.id AND a.album_type = 1 LEFT JOIN
album_ext ae
ON ae.album_id = a.id AND a.album_type = 2
WHERE a.id = 6 AND a.album_type IN (1, 2)
LIMIT 1;
To get values into the same column, you then need to use coalesce().
Note: This does the right thing if there is at most one match in each table.

To Join to one of two tables conditionally, create a join column on each table and UNION them together as such:
SELECT *
FROM albums a
INNER JOIN (
SELECT 1 AS album_type, album_id, colA, colB
FROM album_int
UNION ALL
SELECT 2 AS album_type, album_id, colA, colB
FROM album_ext ) AS b
ON a.album_type = b.album_type
AND a.album_id = b.album_id

Related

MySQL Inner Join / Max Value / Group

Apologies if this has already been covered but I have checked many other questions and can't seem to get the result I want.
SQL Fiddle
http://www.sqlfiddle.com/#!9/d0a1f
I would want to return the highest level achievements grouped by the category. So the dummy data it would return the rows with 'Newbie Leveller' and 'Amateur Banker'
SELECT
achievementid,
a.title,
a.level
FROM
player_achievements p
INNER JOIN achievements a ON p.achievementid = a.id
I did try and change the tables abit (added category & level to the player_achievements, felt like this was wrong as the data is in the other table) and used this query:
SELECT
achievementid,
a.title
FROM
player_achievements p1
INNER JOIN achievements a ON p1.achievementid = a.id
WHERE
p1.level =(
SELECT
MAX(p2.level)
FROM
player_achievements p2
WHERE
p1.category = p2.category
)
AND playerid = 44
But it only returned one row
One solution is to add achievements into the subquery like this
SELECT
achievementid,
title
FROM player_achievements pa1
INNER JOIN achievements a1 ON pa1.achievementid = a1.id
WHERE
a1.level =(
SELECT MAX(a2.level)
FROM player_achievements pa2
INNER JOIN achievements a2 ON pa2.achievementid = a2.id
WHERE a1.category = a2.category and
pa2.playerid = pa1.playerid
)
AND pa1.playerid = 44
demo
Please look at this:
SELECT id, title
FROM achievements JOIN (
SELECT category, max(level) level
FROM player_achievements JOIN achievements ON achievementid = id
WHERE playerid = 44
GROUP BY category
) t USING (category, level);

Joining 3 Tables Together - One of the tables returns null results. How can I ignore this table in my WHERE clause?

Sorry if the title is a bit confusing. Basically, I have three tables:
MainTable
ItemsTable
ObjectsTable
Where the ID (PK) on MainTable are foreign keys on ItemsTable and ObjectsTable (So MainTable will never return null in my query).
Now the goal is to join them all on ID and then execute a WHERE clause using all three tables. Here is the query:
select * from MainTable as a
left outer join ItemsTable as b
on a.ID = b.ID
left outer join ObjectsTable as c
on a.ID = c.ID
where
a.ID = 9999 AND
(a.StatusOne = 1 and b.StatusOne = 1 and c.StatusOne = 1) AND
(a.StatusTwo != 1 or b.StatusTwo != 1 or c.StatusTwo != 1)
The idea of the query is that if it returns any results, a variable in my code is set to true and false if vice versa.
This works perfectly only if every table has an ID. However, I ran in to a case where ItemsTable doesn't have any records with that ID, and thus the query returned no results, when it should have.
My question is:
How can I ignore a NULL joined table in my WHERE clause? So if ItemsTable is NULL, I still want to execute the condition, just without b.StatusOne and b.StatusTwo
Move the qualifying statement to the Where clause makes your left outer join INNER Joins. Below will work.
select * from MainTable as a
left outer join ItemsTable as b
on a.ID = b.ID and b.StatusOne = 1 and b.StatusTwo != 1
left outer join ObjectsTable as c
on a.ID = c.ID c.StatusOne = 1 and c.StatusTwo != 1
where
a.ID = 9999 AND
(a.StatusOne = 1 and
(a.StatusTwo != 1)
I would move those related conditions to the JOIN ON clause instead in WHERE
left outer join ItemsTable as b
on a.ID = b.ID and ( b.StatusOne = 1 or b.StatusTwo != 1)
left outer join ObjectsTable as c
on a.ID = c.ID and (and c.StatusOne = 1 or c.StatusTwo != 1)
where a.ID = 9999 AND (a.StatusOne = 1 or a.StatusTwo != 1);

mysql query not recieving any data on my left join

I have a query that on the left join part should either return data or null. But even if there is existing records its not returning any data. The twist to the left join portion is that I would like to only retrieve one record if it exist. Thanks for any help.
select a.*,p.thumbnailphotopath as AlbumPicture
from (select * from album_access) ac
inner join (select * from albums) a on a.ID = ac.AlbumID
left join (
select * from photos
where IsProcessed = 1 order by DateUploaded desc limit 1
) p
on a.ID = p.AlbumID #should return one if exist.
where ac.AccessUserID = '35e44a8e-643a-4c4f-8a46-59911a1e7c53'
and ac.FullControl = 1 and a.Private = 1
First you can just join on a table name and don't need to join on a whole SELECT * FROM statement.
Second, you should try not to use SELECT * and instead SELECT the columns you want.
But I think the problem with your LEFT JOIN is that you're joining on a sub-query that will only return one result, which will be the entry in photos that was last uploaded irrespective of which albumID it belongs to. If you want the entry in photos with the last uploaded date for each row, try something like this
SELECT a.*,p.thumbnailphotopath AS AlbumPicture
FROM album_access ac
INNER JOIN albums a ON a.ID = ac.AlbumID
LEFT JOIN (
SELECT albumID,MAX(DateUploaded) FROM photos
WHERE IsProcessed = 1 GROUP BY albumID
) p ON a.ID = p.AlbumID #should return one if exist.
WHERE ac.AccessUserID = '35e44a8e-643a-4c4f-8a46-59911a1e7c53'
AND ac.FullControl = 1
AND a.Private = 1

MySQL syntax: bigger than value, but NOT IN (...)

Tables:
bookings: id, user_id, object_id, date1, ...
booking_status: id, book_id, status
status is int, range from 1 to 9 (request, confirmed, paid, cancelled by user and that sort of stuff), so I need all bookings, where status is at least 4 (which means paid) but no value bigger than 4 (which would mean cancelled etc).
Till now the SELECT looks about like this (I left out some fields (...) to shorten it):
SELECT b.date1, ..., u.name FROM bookings b
LEFT JOIN user u ON (b.user_id = u.id)
LEFT JOIN booking_status bs ON (b.id=bs.book_id)
WHERE ((b.object_id=$object_id) AND (bs.status NOT IN (5,6,7,8,9)));"
...but it still selects those bookings that have booking status bigger than 4 as well. Any ideas how I need to change the query??
Thank you very much in advance!
UPDATE: thank you all again, I am amazed with how many great ideas you have come up with! There is really many ways to do it and I learned a lot from you, so thank you again! I will try all your suggestions and see for the performance, for now I mixed your solutions to this query, which works for now but I need to test it further:
SELECT b.date, ..., u.name FROM bookings b
LEFT JOIN user u ON (b.user_id = u.id)
LEFT JOIN booking_status bs ON (b.id=bs.book_id AND bs.status<=4)
WHERE (b.object_id=$object_id) HAVING MAX(bs.status)=4
it does not return multiple rows, but returns the rows with 4, excludes the rows with more than 4 and has no subqueries...
EDIT 2: I edited the query again... with HAVING MAX(bs.status)=4 it then works...
EDIT 3: sorry, after testing different cases I have to admit I was much too fast by saying it works...
If I'm understanding correctly, what you need is this:
SELECT b.date1, ..., u.name
FROM bookings b
LEFT JOIN user u ON b.user_id = u.id
WHERE b.object_id = ...
AND EXISTS
( SELECT 1
FROM booking_status bs
WHERE bs.book_id = b.id
AND bs.status = 4
)
AND NOT EXISTS
( SELECT 1
FROM booking_status bs
WHERE bs.book_id = b.id
AND bs.status > 4
)
;
The problem with your current query is that it filters out all joined rows where bs.status > 4, but it doesn't filter out joined rows that have the same bookings.id as a joined row where bs.status > 4.
inspired by ruakh's solution without the correlated subqueries:
select ...
from bookings b
join (select book_id from booking_status group by book_id having max(status) = 4
) bs on b.id = bs.book_id
left join user u on b.user_id = u.id
Another way
SELECT id,
user_id,
object_id,
date1
FROM bookings b
INNER JOIN (SELECT book_id
FROM booking_status
GROUP BY book_id
HAVING MAX(CASE
WHEN status >= 4 THEN status
END) = 4) bs
ON bs.book_id = b.id
(or version without subselect in response to comments)
SELECT b.id,
b.user_id,
b.object_id,
b.date1
FROM bookings b
INNER JOIN booking_status bs
ON bs.book_id = b.id
GROUP BY b.id /* Other RDBMSs would require all columns listed*/
HAVING MAX(CASE
WHEN bs.status >= 4 THEN bs.status
END) = 4
Here's a method without subqueries:
SELECT b.date1, ..., u.name
FROM bookings b
LEFT JOIN user u ON u.id = b.user_id
JOIN booking_status bs1
ON bs1.book_id = b.id AND bs1.status = 4
LEFT JOIN booking_status bs2
ON bs2.book_id = b.id AND bs2.status > 4
WHERE b.object_id = $object_id
AND b2.book_id IS NULL
This query eliminates any row where there is a status for the same id that is greater than 4.

MySQL GROUP BY performance issue

This is the query I'm performing (without some Joins that are not relevant):
SELECT a.*, c.id
FROM a
LEFT OUTER JOIN b ON a.id = b.id_anunciante
LEFT OUTER JOIN c ON c.id = b.id_rubro
GROUP BY a.id
Each row of "a" is linked with 1 to 5 rows in "b".
The problem is that GROUP BY has performance issues (it takes 10x or more using GROUP BY than not using it). I need to retrieve only one row of each member in "a".
How can I make this faster?
edit: I need to be able to filter by a.id AND/OR c.id. The resultset I should be getting is only 1 row per "valid" member of "a", meaning the rows that match the constraints. Rows that don't match the filters shouldn't be returned.
In my original query, this would be done this way:
SELECT a.*, c.id
FROM a
LEFT OUTER JOIN b ON a.id = b.id_anunciante
LEFT OUTER JOIN c ON c.id = b.id_rubro
WHERE c.id = 1
OR a.id = 1
GROUP BY a.id
a.id, b.id_anunciante, b.id_rubro, c.id are all indexes.
SELECT a.*,
(
SELECT c.id
FROM b
JOIN с
ON c.id = b.id_rubro
WHERE b.id_anunciante = a.id
-- add the ORDER BY condition to define which row will be selected.
LIMIT 1
)
FROM a
Create the index on b (id_anunciante) for this to work faster.
Update:
You don't need the OUTER JOINs here.
Rewrite your query as this:
SELECT a.*, c.id
FROM a
JOIN b
ON b.id_anunciante = a.id
JOIN c
ON c.id = b.id_rubro
WHERE a.id = 1
UNION ALL
SELECT a.*, 1
FROM a
WHERE EXISTS
(
SELECT NULL
FROM c
JOIN b
ON b.id_rubro = c.id
WHERE c.id = 1
AND b.id_anunciante = a.id
)
Add ORDER BY NULL to avoid the implicit sorting MySQL does when doing a group by.
I suppose you have indexes/PKs on a.id, b.id_anunciante, b.id_rubro and c.id ? I guess you could try adding a composite index on (b.id_anunciante, b.id_rubro) if your mysql version is not able to do an index merge.