LEFT JOIN with a condition - mysql

I need to join two tables. I only want to show matches where users.private = 0
feeds
id user_id
100 1
101 NULL
102 2
users
id private
1 1
2 0
I have seen many related questions, and the answer is always to move the WHERE condition into ON instead. But if i do this:
SELECT `feeds`.`id`, `feeds`.`user_id` AS `feed_user_id`, `users`.`id` AS `user_id`, `users`.`private`
FROM `feeds`
LEFT JOIN `users` ON `feeds`.`user_id` = `users`.`id`
AND `users`.`private` = 0
This returns
id feed_user_id user_id private
100 1 NULL NULL
101 NULL NULL NULL
102 2 2 0
What I WANT it to do is exclude the first row id 100. (So I guess that is not really a LEFT JOIN -> I want it to LEFT JOIN if the condition is met, otherwise exclude) How can I do this?

You need two separate condition clauses, one on the JOIN and one on the SELECT, like so:
SELECT `feeds`.`id`, `feeds`.`user_id` AS `feed_user_id`, `users`.`id` AS `user_id`, `users`.`private`
FROM `feeds`
LEFT JOIN `users` ON `feeds`.`user_id` = `users`.`id`
WHERE `users`.`private` = 0 OR `feeds`.`user_id' IS NULL
The problem with your current approach is that the join is treating private users as if they don't exist, rather than saying "this person exists and I should exclude this row from the results".

Related

Get Questions if Not Answered

Basically, there are three tables with the following structure
First table: tournament_questions, having all the questions
id tournament_id question active created_at updated_at
1 5 Text question 1 2018-12-08 20:28:49 NULL
Second table: tournament_options, having all the options
id question_id option correct active created_at updated_at
1 1 1 1 1 2018-12-08 20:29:02 NULL
2 1 26 0 1 2018-12-08 20:29:02 NULL
Third Table: tournament_user_answers, having all the user answers.
id option_id user_id score active created_at updated_at
This table has no data at this time.
I want to achieve all the questions that are not answered by the users and hence the query should return the first question. Here is the query that I tried, but it always returns null
SELECT * FROM tournament_user_answers
INNER JOIN tournament_options ON tournament_user_answers.option_id =
tournament_options.id AND tournament_options.active = 1
LEFT JOIN tournament_questions ON tournament_questions.id =
tournament_options.question_id AND tournament_questions.active = 1
WHERE tournament_questions.tournament_id = 5 AND
tournament_questions.active = 1
AND tournament_questions.id IS NULL AND tournament_user_answers.user_id = 1
LIMIT 1
You start with FROM tournament_user_answers (which is empty) and you do a LEFT JOIN which includes all rows on the left hand (which was empty) and appending on those the data on the right hand if available. empty LEFT JOIN data will be empty.
SELECT
tournament_questions.*
FROM tournament_questions
JOIN tournament_options
ON tournament_options.question_id = tournament_questions.id
AND tournament_options.active = 1
LEFT JOIN tournament_user_answers
ON tournament_user_answers.option_id = tournament_options.id
AND tournament_user_answers.user_id = 1
WHERE
tournament_questions.tournament_id = 5
AND tournament_questions.active = 1
GROUP BY tournament_questions.id
HAVING MAX(tournament_user_answers.id) IS NULL
ORDER BY tournament_questions.id ASC
In this case the left part (questions + options) has data, and the answers are appended if available. By including a MAX(tournament_user_answers.id) IS NULL in your HAVING you get all questions where there is NO answer.
Maybe this would work for your case (reduced version):
SELECT q.id, q.question, ua.id ua_id FROM tournament_questions q
INNER JOIN tournament_options o ON q.id = o.question_id
LEFT JOIN tournament_user_answers ua ON o.id = ua.option_id
GROUP BY q.id
HAVING ua_id IS NULL

How do I delete LEFT JOIN rows that are NULL in mySQL?

I am doing a LEFT JOIN like this:
SELECT mt_order_delivery_address.id,
mt_order.order_id,
mt_order_delivery_address.client_id
FROM mt_order_delivery_address
LEFT JOIN mt_order ON mt_order.order_id = mt_order_delivery_address.order_id
which then gives me a list like:
id order_id client_id
----------------------------
1032 NULL 382
1028 NULL 282
1020 784344 920
1002 232496 490
I want to get rid of the rows in mt_order_delivery_address where there is no corresponding order_id in mt_order. How would I do this? I can't seem to wrap my head around it.
UPDATE:
I tried this but doesn't seem to work:
DELETE mt_order_delivery_address.id
FROM mt_order_delivery_address
LEFT JOIN mt_order ON mt_order.order_id = mt_order_delivery_address.order_id
WHERE mt_order.order_id IS NULL
If by "delete" you mean "don't show them in this query result" you can use an INNER JOIN instead of a LEFT JOIN. It's worth learning the different types of joins and how to use them.
If by "delete" you mean "DELETE the rows in my_order_delivery_address that have no matching my_order", you can do a multi-table DELETE:
DELETE a FROM mt_order_delivery_address AS a
LEFT JOIN mt_order AS o USING (order_id)
WHERE o.order_id IS NULL;

Optimize mysql query with subqueries

Below is MySQl query with which I am able to get disired result
But is there any way i could optimize the query
SELECT users.*,
(SELECT country_name FROM country WHERE country_code = users.country_code)
AS country_name,
(SELECT zone_name FROM timezone WHERE timezone_id = users.timezone_id)
AS zone_name,
(SELECT GROUP_CONCAT(list_name)
FROM list LEFT JOIN user_list ON user_list.list_id = list.list_id
WHERE user_list.user_id = users.user_id AND user_list.status = "active")
AS groups,
(SELECT GROUP_CONCAT(promotion_name)
FROM promotion LEFT JOIN promotion_user ON promotion_user.promotion_id = promotion.promotion_id
WHERE promotion_user.user_id = users.user_id AND promotion_user.status = "active")
AS promotions,
(SELECT GROUP_CONCAT(full_name)
FROM users u LEFT JOIN promotion_user ON promotion_user.promotor_id = u.user_id
WHERE promotion_user.user_id = users.user_id AND promotion_user.status = "active")
AS promotors
FROM users WHERE client_id = '2' AND status != 'deleted'
ORDER BY user_id desc
LIMIT 50 OFFSET 0
The Explain Output is
possible key
id select_type table type _keys key _len ref rows Extra
1 PRIMARY users index NULL PRIMARY 4 NULL 1045612 Using where
6 DEPENDENT SUBQUERY promotion_user ALL NULL NULL NULL NULL 16159 Using where
6 DEPENDENT SUBQUERY u eq_ref PRIMARY PRIMARY 4 [1] 1 NULL
5 DEPENDENT SUBQUERY promotion_user ALL NULL NULL NULL NULL 16895 Using where
5 DEPENDENT SUBQUERY promotion ALL PRIMARY NULL NULL NULL 4 Using where; Using join buffer (Block Nested Loop)
4 DEPENDENT SUBQUERY list ALL PRIMARY NULL NULL NULL 1592 NULL
4 DEPENDENT SUBQUERY user_list ALL NULL NULL NULL NULL 159852 Using where; Using join buffer (Block Nested Loop)
3 DEPENDENT SUBQUERY timezone eq_ref PRIMARY PRIMARY 4 [2] 1 NULL
2 DEPENDENT SUBQUERY country ALL NULL NULL NULL NULL 239 Using where
[1] test.promotion_user.promoter_id
[2] test.promotion_user.promoter_id
I would try using non correlated sub queries. However as you are only bringing back the details for a single user (hence a single row probably) this might not help. Beyond probably eliminating one sub query.
Something like this (untested as no data definitions or data examples)
SELECT `users`.*,
country.country_name,
timezone.zone_name,
sub_groups.groups,
sub_promotors.promotions,
sub_promotors.promotors
FROM `users`
INNER JOIN country
ON country.country_code = users.country_code
INNER JOIN timezone
ON timezone.timezone_id = users.timezone_id
INNER JOIN
(
SELECT promotion_user.user_id, GROUP_CONCAT(full_name) AS promotors, GROUP_CONCAT(promotion_name) AS promotions
FROM users u
LEFT JOIN promotion_user ON promotion_user.promotor_id = u.user_id
WHERE promotion_user.status = "active"
GROUP BY promotion_user.user_id
) AS sub_promotors
ON sub_promotors.user_id = users.user_id
INNER JOIN
(
SELECT user_list.user_id, GROUP_CONCAT(list_name) AS groups
FROM list
LEFT JOIN user_list ON user_list.list_id = list.list_id
WHERE user_list.status = "active"
GROUP BY user_list.user_id
) AS sub_groups
ON sub_groups.user_id = users.user_id
WHERE users.client_id = '2'
AND users.status != 'deleted'
ORDER BY users.user_id
DESC LIMIT 50 OFFSET 0
Correlated sub queries effectively forces MySQL to perform themselves once for each returned row. Changing these to non correlated sub queries which are joined means they can be performed once for all returned rows. Down side is that joining onto a sub query is poorly optimised as far as indexes in MySQL.
You might be able to remove the sub queries if the promotor full name, etc are unique.

sql multiple join and count

I have tables like this. but it seems MySQL doesn't count my second join currently. i want to know what i missed for process count of reports for my comment list.
and i want to have average of rates also count of reports
SELECT *, avg(rate.score), count(report.id) FROM `comment`
left join rate on (comment.id = rate.comment_id)
left join report on (comment.id = report.comment_id)
group by comment.id
id text id comment_id score id comment_id type avg(rate.score) count(report.comment_id)
1 good article 1 1 2 1 1 1 4.0000 20
2 bad article NULL NULL NULL NULL NULL NULL NULL 0
good article have 2 reports.
count(report.id) give me wrong value. what's my mistake?
SELECT
*,
avg(rate.score),
(SELECT
count(report.comment_id)
FROM
report
WHERE
comment.id = report.comment_id) AS num_reports
FROM
comment
left join
rate ON (comment.id = rate.comment_id)
group by comment.id
Here's the example:
http://sqlfiddle.com/#!2/cf313/15
You dont need *. Try this
SELECT comment.id, avg(rate.score), count(report.id) FROM `comment`
left join rate on (comment.id = rate.comment_id)
left join report on (comment.id = report.comment_id)
group by comment.id

How to left join or inner join a table itself

I have this data in a table, for instance,
id name parent parent_id
1 add self 100
2 manage null 100
3 add 10 200
4 manage null 200
5 add 20 300
6 manage null 300
How can I left join or inner join this table itself so I get this result below?
id name parent
2 manage self
4 manage 10
6 manage 20
As you can I that I just want to query the row with the keyword of 'manage' but I want the column parent's data in add's row as the as in manage's row in the result.
Is it possible?
EDIT:
the simplified version of my actual table - system,
system_id parent_id type function_name name main_parent make_accessible sort
31 30 left main Main NULL 0 1
32 31 left page_main_add Add self 0 1
33 31 left page_main_manage Manage NULL 0 2
my actual query and it is quite messy already...
SELECT
a.system_id,
a.main_parent,
b.name,
b.make_accessible,
b.sort
FROM system AS a
INNER JOIN -- self --
(
SELECT system_id, name, make_accessible, sort
FROM system AS s2
LEFT JOIN -- search --
(
SELECT system_id AS parent_id
FROM system AS s1
WHERE s1.function_name = 'page'
) AS s1
ON s1.parent_id = s2.parent_id
WHERE s2.parent_id = s1.parent_id
AND s2.system_id != s1.parent_id
ORDER BY s2.sort ASC
) b
ON b.system_id = a.parent_id
WHERE a.function_name LIKE '%manage%'
ORDER BY b.sort ASC
result I get currently,
system_id main_parent name make_accessible sort
33 NULL Main 0 1
but I am after this,
system_id main_parent name make_accessible sort
33 self Main 0 1
You just need to reference the table twice:
select t1.id, t1.name, t2.id, t2.name
from TableA t1
inner join TableA t2
on t1.parent_id = t2.Id
Replace inner with left join if you want to see roots in the list.
UPDATE:
I misread your question. It seems to me that you always have two rows, manage one and add one. To get to "Add" from manage:
select system.*, (select parent
from system s2
where s2.parent_id = system.parent_id
and s2.name = 'add')
AS parent
from system
where name = 'manage'
Or, you might split the table into two derived tables and join them by parent_id:
select *
from system
inner join
(
select * from system where name = 'add'
) s2
on system.parent_id = s2.parent_id
where system.name = 'manage'
This will allow you to use all the columns from s2.
Your data does not abide to a child-parent hierarchical structure. For example, your column parent holds the value 10, which is not the value of any id, so a child-parent association is not possible.
In other words, there's nothing that relates the record 2,manage,null to the record 1,add,self, or the record 4,manage,null to 3,add,10, as you intend to do in your query.
To represent hierarchical data, you usually need a table that has a foreign key referencing it's own primary key. So your column parent must reference the column id, then you can express a child-parent relationship between manage and add. Currently, that's not possible.
UPDATED: Joining by parent_id, try:
select m.id, m.name, a.parent
from myTable m
join myTable a on m.parent_id = a.parent_id and a.name = 'add'
where m.name = 'manage'
Change the inner join to a left join if there may not be a corresponding add row.