MySQL exclusion join when join condition is in other table - mysql

I'm trying to order a grouped resultset. Problem is that one of the join conditions references another table. Specifically, I need to select records from table_a that have the highest value within a group, but the group id is a field in a different table.
Below are some of the things I tried, in different variations. Whenever I add GROUP BY pr.id, some results that know should be in the top 3, are excluded. When I do not add it I get more than 1 record per group.
Any help much appreciated.
SELECT * FROM ivalues AS iv
INNER JOIN mprod AS p ON (p.id = iv.p_id)
INNER JOIN contact AS pr ON (pr.id = p.pr_id)
LEFT OUTER JOIN ivalues i2
ON i2.p_id = p2.id
AND i2.period = iv.period
AND i2.current = iv.current
AND i2.cert = iv.cert
AND i2.exists = iv.exists
AND iv.value > i2.value
WHERE
iv.period = 0
AND iv.current = 1
AND iv.cert = 1
AND p.type_id = 15747
AND iv.exists = 1
AND i2.id IS NULL
GROUP BY pr.id
ORDER BY iv.value ASC
LIMIT 10;
SELECT * FROM ivalues AS iv
INNER JOIN mprod AS p ON (p.id = iv.p_id)
INNER JOIN contact AS pr ON (pr.id = p.pr_id)
LEFT OUTER JOIN contact pr2
ON pr2.id = p.pr_id
LEFT OUTER JOIN mprod p2
ON p2.type_id = p.type_id
LEFT OUTER JOIN ivalues i2
ON i2.p_id = p2.id
AND i2.period = iv.period
AND i2.current = iv.current
AND i2.cert = iv.cert
AND i2.exists = iv.exists
AND iv.value > i2.value
WHERE
iv.period = 0
AND iv.current = 1
AND iv.cert = 1
AND p.type_id = 15747
AND iv.exists = 1
AND i2.id IS NULL
ORDER BY iv.value ASC
LIMIT 10;
SELECT * FROM ivalues AS iv
INNER JOIN mprod AS p ON (p.id = iv.p_id)
INNER JOIN contact AS pr ON (pr.id = p.pr_id)
LEFT OUTER JOIN contact p2
ON p2.pr_id = p.pr_id
AND p2.type_id = p.type_id
INNER JOIN mprod p2
ON p2.type_id = p.type_id
INNER JOIN ivalues i2
ON i2.p_id = p2.id
AND i2.period = iv.period
AND i2.current = iv.current
AND i2.cert = iv.cert
AND i2.exists = iv.exists
AND iv.value > i2.value
WHERE
iv.period = 0
AND iv.current = 1
AND iv.cert = 1
AND p.type_id = 15747
AND iv.exists = 1
ORDER BY iv.value ASC
LIMIT 3;

I think you will need to join ivalues and mprod as a subselect, either as an outer join to exclude non-maximum values, or as an inner group to find the maximum value. Hopefully it looks something like this:
SELECT * FROM contact
INNER JOIN mprod ON mprod.contact_id = contact.id
INNER JOIN ivalues ON ivalues.mprod_id = mprod.id
LEFT OUTER JOIN (
SELECT mprod.contact_id, ivalues.*
FROM mprod
INNER JOIN ivalues ON mprod.id = ivalues.mprod_id
WHERE ivalues.period = 0
AND ivalues.current = 1
AND ivalues.cert = 1
AND mprod.type_id = 15747
AND ivalues.exists = 1
) AS pv ON pv.contact_id = contact.id AND pv.value > ivalues.value
WHERE ivalues.period = 0
AND ivalues.current = 1
AND ivalues.cert = 1
AND mprod.type_id = 15747
AND ivalues.exists = 1
AND pv.id IS NULL
GROUP BY pr.id
ORDER BY iv.value ASC
LIMIT 10;

Related

Transpile mysql query to laravel style

How would I transpile this query to Laravel style
SELECT
gr.name,
p.ma_status AS status,
COUNT(p.ma_status) AS total
FROM accounts u
LEFT JOIN accounts_prop p ON p.account_id = u.account_id
AND (
SELECT j.iid
FROM accounts_prop AS j
WHERE u.account_id = j.account_id
AND j.ma_status IS NOT NULL
ORDER BY j.von DESC LIMIT 1
) = p.iid
LEFT JOIN `deprecators` gr ON gr.id = p.group_id
LEFT JOIN `deprecators` unit ON unit.id = p.unit_id
LEFT JOIN `deprecators` team ON team.id = p.team_id
WHERE p.group_id IS NOT NULL
AND u.account_status = 'A'
GROUP BY p.ma_status, gr.id
I do not have any idea how would I go with the following statement
LEFT JOIN accounts_prop p ON p.account_id = u.account_id
AND (
SELECT j.iid
FROM accounts_prop AS j
WHERE u.account_id = j.account_id
AND j.ma_status IS NOT NULL
ORDER BY j.von DESC LIMIT 1
) = p.iid

MySQL subquery does not give me the highest value with MAX or ORDER BY LIMIT 1

I have problems with my MySQL query. Its subquery does not give the highest value of last.id.
SELECT rounds.winners, rounds.losers
FROM players
INNER JOIN teams ON teams.id = players.ilmo_id
INNER JOIN status AS first ON first.id = players.status_id
LEFT JOIN matches ON matches.chart_id = 12
AND matches.id = (
SELECT last.id
FROM matches AS last
WHERE (last.player1_id = players.id
OR last.player2_id = players.id
OR last.player3_id = players.id
OR last.player4_id = players.id)
ORDER BY last.id DESC
LIMIT 1
)
JOIN charts ON charts.id = matches.chart_id
JOIN places ON charts.template_id = places.template_id AND places.id = matches.place_id
JOIN templates ON places.template_id = templates.id
JOIN rounds ON places.round_id = rounds.id
WHERE players.comp_id = 12
I also have tried this way, but this does not work:
SELECT rounds.winners, rounds.losers
FROM players
INNER JOIN teams ON teams.id = players.ilmo_id
INNER JOIN status AS first ON first.id = players.status_id
LEFT JOIN matches ON matches.chart_id = 12
AND matches.id = (
SELECT MAX(last.id)
FROM matches AS last
WHERE (last.player1_id = players.id
OR last.player2_id = players.id
OR last.player3_id = players.id
OR last.player4_id = players.id)
)
JOIN charts ON charts.id = matches.chart_id
JOIN places ON charts.template_id = places.template_id AND places.id = matches.place_id
JOIN templates ON places.template_id = templates.id
JOIN rounds ON places.round_id = rounds.id
WHERE players.comp_id = 12
Edit:
Here is the latest version which seems to work OK.
SELECT rounds.winners, rounds.losers
FROM players
INNER JOIN teams ON teams.id = players.ilmo_id
INNER JOIN status AS first ON first.id = players.status_id
LEFT JOIN matches ON matches.chart_id = 12
AND matches.id = (
SELECT last.id
FROM matches AS last
WHERE last.chart_id = 12 /* modified */
AND (last.player1_id = players.id
OR last.player2_id = players.id
OR last.player3_id = players.id
OR last.player4_id = players.id)
ORDER BY last.place_id DESC /* modified */
LIMIT 1
)
JOIN charts ON charts.id = matches.chart_id
JOIN places ON charts.template_id = places.template_id AND places.id = matches.place_id
JOIN templates ON places.template_id = templates.id
JOIN rounds ON places.round_id = rounds.id
WHERE players.comp_id = 12
SELECT rounds.winners, rounds.losers
FROM players
INNER JOIN teams ON teams.id = players.ilmo_id
INNER JOIN status AS first ON first.id = players.status_id
LEFT JOIN matches ON matches.id = (
SELECT max(last.id)
FROM matches AS last
WHERE last.chart_id = 12 /* modified */
AND (last.player1_id = players.id
OR last.player2_id = players.id
OR last.player3_id = players.id
OR last.player4_id = players.id)
)
JOIN charts ON charts.id = matches.chart_id
JOIN places ON charts.template_id = places.template_id AND places.id = matches.place_id
JOIN templates ON places.template_id = templates.id
JOIN rounds ON places.round_id = rounds.id
WHERE players.comp_id = 12

Select threads from forum based on comments

I've got the following sql that will return a list of forums. Under each forum it will select the thread with the latest comment. This works fine but when a new thread hasn't got any comments, nothing is returned.
How to tackle this problem?
SELECT f.Id AS forum_id,
f.name AS forum_name,
f.slug AS forum_slug,
f.image AS forum_image,
t.Id AS thread_id,
t.title AS thread_topic,
t.unixtime AS thread_timestamp,
p.Id AS post_id,
p.content AS post_content,
p.unixtime AS post_timestamp,
(SELECT COUNT(*) FROM a_comments o WHERE o.forumID=f.Id AND o.teamId = {$teamId}) comments_count,
(SELECT COUNT(*) FROM a_threads w WHERE w.forumID=f.Id AND w.teamId = {$teamId}) threads_count
FROM a_forums f
LEFT JOIN (SELECT t2.forumID, max(COALESCE(p2.unixtime, t2.unixtime)) as ts, COUNT(p2.unixtime) as post_count
FROM a_threads t2
LEFT JOIN a_comments p2 ON p2.threadId = t2.id
GROUP BY t2.forumId) max_p ON f.id = max_p.forumId
LEFT JOIN a_comments p ON max_p.ts = p.unixtime AND p.teamId = {$teamId} AND p.deleted = 0
LEFT JOIN a_threads t ON f.Id = t.forumID AND (max_p.post_count = 0 OR p.threadId = t.ID) AND t.teamId = {$teamId} AND t.deleted = 0
ORDER BY f.id
I think you just have to change the LEFT JOIN in the first subquery to a JOIN. With the LEFT JOIN, you'll get NULL or a non-valid time for the comment. This then throws off the rest of the logic -- I think.
SELECT f.Id AS forum_id, f.name AS forum_name, f.slug AS forum_slug, f.image AS forum_image,
t.Id AS thread_id, t.title AS thread_topic, t.unixtime AS thread_timestamp,
p.Id AS post_id, p.content AS post_content, p.unixtime AS post_timestamp,
(SELECT COUNT(*) FROM a_comments o WHERE o.forumID=f.Id AND o.teamId = {$teamId}) as comments_count,
(SELECT COUNT(*) FROM a_threads w WHERE w.forumID=f.Id AND w.teamId = {$teamId}) as threads_count
FROM a_forums f LEFT JOIN
(SELECT t2.forumID, max(p2.unixtime) as ts,
COUNT(p2.unixtime) as post_count
FROM a_threads t2 JOIN
a_comments p2
ON p2.threadId = t2.id
GROUP BY t2.forumId
) max_p
ON f.id = max_p.forumId LEFT JOIN
a_comments p
ON max_p.ts = p.unixtime AND p.teamId = {$teamId} AND
p.deleted = 0 LEFT JOIN
a_threads t
ON f.Id = t.forumID AND (max_p.post_count = 0 OR p.threadId = t.ID) AND t.teamId = {$teamId} AND t.deleted = 0
ORDER BY f.id

How to do a IFNULL(count(*),0) in MySQL 5.5

I tried to do a IFNULL(count(),0) , or IF (count() > 0, count(*),0),
but it doesn't work, my row "counter" display "NULL" instead of 0 :/
Here is my query:
SELECT IF(fc.counter > 0, fc.counter, 0) counter, b.*, fc.* FROM client_branche cb INNER JOIN branche b On b.id = cb.branche_id LEFT OUTER JOIN (
SELECT count(*) as counter, ctn_b.branche_id as b_id
FROM `historique` h
INNER JOIN contenu_branche ctn_b ON ctn_b.contenu_id = h.contenu_id
INNER JOIN utilisateur u ON u.id = h.utilisateur_id
WHERE h.h_fini = 1 AND ( u.client_id = 1 OR u.client_id = 0 ) AND h.h_dateheure BETWEEN '2015-12-24' AND '2015-12-30'
group by ctn_b.`branche_id`)
fc ON fc.b_id = cb.branche_id WHERE cb.client_id = 1
So i tried to do that :
SELECT IF(fc.counter > 0, fc.counter, 0) counter, b.*, fc.* FROM client_branche cb INNER JOIN branche b On b.id = cb.branche_id LEFT OUTER JOIN (
SELECT IFNULL(count(*),0) as counter, ctn_b.branche_id as b_id
FROM `historique` h
INNER JOIN contenu_branche ctn_b ON ctn_b.contenu_id = h.contenu_id
INNER JOIN utilisateur u ON u.id = h.utilisateur_id
WHERE h.h_fini = 1 AND ( u.client_id = 1 OR u.client_id = 0 ) AND h.h_dateheure BETWEEN '2015-12-24' AND '2015-12-30'
group by ctn_b.`branche_id`)
fc ON fc.b_id = cb.branche_id WHERE cb.client_id = 1
Annnnd i failed. I hope someone will help me. Thanks a lot in advance, and sorry for my bad english, it's not my native language :)
The problem is probably that you have is multiple columns in the select have the same name -- counter. The best way to solve this is not to use * in the query, but to list the columns you want. Perhaps a simpler way is to rename the column:
SELECT (CASE WHEN fc.counter > 0 THEN fc.counter ELSE 0 END) as fc_counter,
b.*, fc.*
FROM client_branche cb INNER JOIN
branche b
On b.id = cb.branche_id LEFT OUTER JOIN
(SELECT count(*) as counter, ctn_b.branche_id as b_id
FROM `historique` h INNER JOIN
contenu_branche ctn_b
ON ctn_b.contenu_id = h.contenu_id INNER JOIN
utilisateur u
ON u.id = h.utilisateur_id
WHERE h.h_fini = 1 AND
u.client_id IN (0, 1) AND
h.h_dateheure BETWEEN '2015-12-24' AND '2015-12-30'
GROUP BY ctn_b.branche_id
) fc
ON fc.b_id = cb.branche_id
WHERE cb.client_id = 1;
I made a couple other small changes as well, such as using IN instead of OR and CASE (ANSI standard) instead of IF().
Note: If counter is never negative, the most colloquial way of writing the logic is COALESCE(counter, 0) rather than CASE or IF().
It will work. You are use counter alias in subquery and also main query. So try it
SELECT IF(fc.cou > 0, fc.cou, 0) counter, b., fc.FROM client_branche cb INNER JOIN branche b On b.id = cb.branche_id LEFT OUTER JOIN ( SELECT IFNULL(count(*),0) as cou, ctn_b.branche_id as b_id FROM historiqueh INNER JOIN contenu_branche ctn_b ON ctn_b.contenu_id = h.contenu_id INNER JOIN utilisateur u ON u.id = h.utilisateur_id WHERE h.h_fini = 1 AND ( u.client_id = 1 OR u.client_id = 0 ) AND h.h_dateheure BETWEEN '2015-12-24' AND '2015-12-30' group by ctn_b.branche_id) fc ON fc.b_id = cb.branche_id WHERE cb.client_id = 1
You need the IFNULL on the outer query (due to the LEFT JOIN), like so:
SELECT IF(IFNULL(fc.counter,0) > 0, IFNULL(fc.counter,0), 0) AS counter
, b.*, fc.*
FROM client_branche AS cb
INNER JOIN branche AS b ON b.id = cb.branche_id
LEFT JOIN (
SELECT count(*) AS counter, ctn_b.branche_id AS b_id
FROM `historique` AS h
INNER JOIN contenu_branche AS ctn_b ON ctn_b.contenu_id = h.contenu_id
INNER JOIN utilisateur AS u ON u.id = h.utilisateur_id
WHERE h.h_fini = 1 AND ( u.client_id = 1 OR u.client_id = 0 )
AND h.h_dateheure BETWEEN '2015-12-24' AND '2015-12-30'
GROUP BY ctn_b.`branche_id`
) AS fc ON fc.b_id = cb.branche_id
WHERE cb.client_id = 1
;
It is effectively a "no op" on the inner query, since count(*) there will never be null.

The design of a query

SELECT u.username,
r.position,
r.score,
r.winner,
t.team
FROM ".TBL_FOOT_TOUR_ROUNDS." r
LEFT JOIN ".TBL_USERS." u
ON u.id = r.winner
LEFT JOIN ".TBL_FOOT_TOUR_PLAYERS." pl
ON pl.userid = r.winner
LEFT JOIN ".TBL_FOOT_TEAMS." t
ON t.id = pl.team
WHERE pl.tourid = '$tour_id' && r.tourid = '$tour_id' && r.round = '$i'
ORDER BY r.position
I have one problem with this query. The WHERE pl.tourid = '$tour_id' is reliant on the LEFT JOIN ".TBL_FOOT_TOUR_PLAYERS." pl ON pl.userid = r.winner. As it's a left join, how can I make that WHERE only function if the LEFT JOIN does?
Can't think of a solution!
Thanks
I guess that you only want rows with no matching winner, or where the winner has the specified tourid. In this case, you would use:
WHERE (pl.tourid IS NULL OR pl.tourid = '$tour_id')
Alternatively, if you only want to link to the player if (s)he has the right tourid, then add it to the ON clause:
ON pl.userid = r.winner AND pl.tourid = '$tour_id'
The results will be different, either might be what you are looking for.
SELECT u.username,
r.position,
r.score,
r.winner,
t.team
FROM ".TBL_FOOT_TOUR_ROUNDS." r
LEFT JOIN ".TBL_USERS." u
ON u.id = r.winner
LEFT JOIN ".TBL_FOOT_TOUR_PLAYERS." pl
ON pl.userid = r.winner
AND pl.tourid = '$tour_id'
AND r.tourid = '$tour_id'
AND r.round = '$i'
LEFT JOIN ".TBL_FOOT_TEAMS." t
ON t.id = pl.team
ORDER BY r.position