MYSQL Join Left Condition does not work - mysql

I have this example of my problem:
http://sqlfiddle.com/#!9/0cc41/1/0
Description:
There are three tables (events, persons and user). The table persons connects events and user. persons.type_id is the events.id and persons.user_id is user.id. I created two events (id 1 and 2). There are one entry in person for each event.
My Sql:
SELECT events.*,
coalesce(part_person.Accept_Participants_LJ, 0) AS Accept_Participants
FROM events
LEFT JOIN (
SELECT GROUP_CONCAT(CONCAT(part_user.forname, ' ', part_user.surname) SEPARATOR ', ') AS Accept_Participants_LJ,
part_person.type_id
FROM persons AS part_person
LEFT JOIN user AS part_user ON part_user.id = part_person.user_id
WHERE part_person.type = 'event_participant'
) part_person ON events.id = part_person.type_id
GROUP BY events.id
My expectation was:
------------------------
|id|Accept_Participants|
------------------------
|1 | Carl Habicht |
------------------------
|2 | Peter Zwegert |
------------------------
As you can see, the result is:
----------------------------------
|id| Accept_Participants |
----------------------------------
|1 | Carl Habicht, Peter Zwegert|
----------------------------------
|2 | 0 |
----------------------------------
It seems, that he ignores the ON-Condition of the Left Join.
But, where is my mistake?

Group_concat should (almost) always have a group by and group by without any aggregation is not useful
Maybe this is what you need
SELECT events.*,
coalesce(part_person.Accept_Participants_LJ, 0) AS Accept_Participants
FROM events
LEFT JOIN (
SELECT GROUP_CONCAT(CONCAT(part_user.forname, ' ', part_user.surname) SEPARATOR ', ') AS Accept_Participants_LJ,
part_person.type_id
FROM persons AS part_person
LEFT JOIN user AS part_user ON part_user.id = part_person.user_id
WHERE part_person.type = 'event_participant'
group by part_person.type_id
) part_person ON events.id = part_person.type_id
order BY events.id

Related

Query result null or 0

I am trying to make an appointment to return all the municipalities and all the specialties .... being that even in that municipality does not have any provider for such specialty it should be listed with value 0 or null .... at the moment I am with this Query, I need some help.
select
cid.txt_cidade, esp.txt_especialidade, count(*) as QTD
from
tb_associadoespecialidade as assesp
left join
tb_especialidade as esp on esp.id_especialidade = assesp.id_especialidade
left join
tb_associado as ass on ass.id_associado = assesp.id_associado
left join
tb_cidade as cid on cid.id_cidade = ass.id_cidade
where
ass.id_categoria = 1 and txt_cidade like 'Tupã'
group by
cid.txt_cidade, esp.txt_especialidade
order by
cid.txt_cidade desc;
Diagram
Result expectancy
TXT_CIDADE ------ TXT_ESPECIALIDADE --------- count QTD
SP -------------- ESPEC01 ------------------- 10
SP -------------- ESPEC02 ------------------- 5
SP -------------- ESPEC03 ------------------- 15
RJ -------------- ESPEC01 ------------------- NULL
RJ -------------- ESPEC02 ------------------- 5
RJ -------------- ESPEC03 ------------------- NULL
Try this:
select
cid.txt_cidade, esp.txt_especialidade, count(*) as QTD
from
tb_associadoespecialidade as assesp
left join
tb_especialidade as esp on esp.id_especialidade = assesp.id_especialidade
left join
tb_associado as ass on ass.id_associado = assesp.id_associado
and ass.id_categoria = 1
left join
tb_cidade as cid on cid.id_cidade = ass.id_cidade
where
txt_cidade like 'Tupã'
group by
cid.txt_cidade, esp.txt_especialidade
order by
cid.txt_cidade desc;
When you apply a where clause on a field from a table that is outer joined, you risk unintentionally throwing away records. By putting your desired filtering in the join condition, you still get null values where that criteria is not met. See my answer here for more details.

MySQL JOIN+COUNT+WHERE+AND

I have table of regions:
table region:
id | title
Region has many adverts:
table advert:
id | region_id | ...
Then advert has many uses (many-many through table adv_use):
table use:
use | slug | ...
----------------
1 | slug_1 | ...
2 | slug_2 | ...
..................
table adv_use:
adv_id | use_id
I want select all regions with count(*) of adverts, which have uses with slug_1 AND slug_2. If advert has no use with slug_1 or with slug_2 (or both), it's must not be counted.
What i have now:
SELECT COUNT(DISTINCT advert.id) as count
FROM region
JOIN advert ON region.id = advert.region_id
JOIN adv_use ON advert.id = adv_use.adv_id
JOIN use ON adv_use.use_id = use.id
WHERE use.slug IN ('slug_1', 'slug_2')
GROUP BY region.id
HAVING COUNT(DISTINCT adv_use.use_id) = 2
But it's working not as i want.
SQLFiddle: http://sqlfiddle.com/#!2/5b4d4/1
Thanks for help and sorry for bad english.
You need to use a subquery to select the adv_id that use both slugs:
SELECT COUNT(DISTINCT advert.id) as count, region.id as reg_id
FROM region
JOIN advert ON region.id = advert.region_id
JOIN (SELECT adv_use.adv_id
FROM adv_use
JOIN tbl_use ON adv_use.use_id = tbl_use.id
WHERE tbl_use.slug IN ('slug1', 'slug2')
GROUP BY adv_use.adv_id
HAVING COUNT(DISTINCT adv_use.use_id) = 2) adv_use
ON advert.id = adv_use.adv_id
GROUP BY region.id
;
SQLFIDDLE

How would I execute this complex conditional multi-table MySQL join (queries provided)?

Ok I have a few tables tables. I am only showing relevant fields:
items:
----------------------------------------------------------------
name | owner_id | location_id | cab_id | description |
----------------------------------------------------------------
itm_A | 11 | 23 | 100 | Blah |
----------------------------------------------------------------
.
.
.
users:
-------------------------
id | name |
-------------------------
11 | John |
-------------------------
.
.
.
locations
-------------------------
id | name |
-------------------------
23 | Seattle |
-------------------------
.
.
.
cabs
id | location_id | name
-----------------------------------
100 | 23 | Cool |
-----------------------------------
101 | 24 | Cool |
-----------------------------------
102 | 24 |thecab |
-----------------------------------
I am trying to SELECT all items (and their owner info) that are from Seattle OR Denver, but if they are in Seattle they can only be in the cab NAMED Cool and if they are in Denver they can only be in the cab named 'thecab' (not Denver AND cool).
This query doesn't work but I hope it explains what I am trying to accomplish:
SELECT DISTINCT
`item`.`name`,
`item`.`owner_id`,
`item`.`description`,
`user`.`name`,
IF(`loc`.`name` = 'Seattle' AND `cab`.`name` = 'Cool',1,0) AS `cab_test_1`,
IF(`loc`.`name` = 'Denver' AND `cab`.`name` = 'thecab',1,0) AS `cab_test_2`,
FROM `items` AS `item`
LEFT JOIN `users` AS `user` ON `item`.`owner_id` = `user`.`id`
LEFT JOIN `locations` AS `loc` ON `item`.`location_id` = `loc`.`location_id`
LEFT JOIN `cabs` AS `cab` ON `item`.`cab_id` = `cabs`.`id`
WHERE (`loc`.`name` IN ("Seattle","Denver")) AND `cab_test_1` = 1 AND `cab_test_2` = 1
I'd rather get rid of the IFs is possible. It seems inefficent, looks clunky, and is not scalable if I have a lot of location\name pairs
Try this:
SELECT DISTINCT
item.name,
item.owner_id,
item.description,
user.name
FROM items AS item
LEFT JOIN users AS user ON item.owner_id = user.id
LEFT JOIN locations AS loc ON item.location_id = loc.id
LEFT JOIN cabs AS cab ON item.cab_id = cabs.id
WHERE ((loc.name = 'Seattle' AND cab.name = 'Cool')
OR (loc.name = 'Denver' AND cab.name = 'thecab'))
My first thought is to store the pairs of locations and cab names in a separate table. Well not quite a table, but a derived table generated by a subquery.
You still have the problem of pivoting the test results into separate columns. The code can be simplified by making use of mysql boolean expressions, which get rid of the need for a case or if.
So, the approach is to use the same joins you have (although left join is not needed because the comparison on cab.name turns them in to inner joins). Then add a table of the pairs you are looking for, along with the "test name" for the pair. The final step is an explicit group by and a check whether conditions are met for each test:
SELECT i.`name`, i.`owner_id`, i.`description`, u.`name`,
max(pairs.test_name = 'test_1') as cab_test_1,
max(pairs.test_name = 'test_2') as cab_test_2
FROM `items` i LEFT JOIN
`users` u
ON i.`owner_id` = u.`id` LEFT JOIN
`locations` l`
ON i.`location_id` = l.`location_id` left join
`cabs` c
ON i.`cab_id` = c.`id` join
(select 'test_1' as testname, 'Seattle' as loc, 'cool' as cabname union all
select 'test_2', 'Denver', 'thecab'
) pairs
on l.name = pairs.name and
l.cabname = c.name
group by i.`name`, i.`owner_id`, i.`description`, u.`name`;
To add in additional pairs, add them into the pairs table along, and add an appropriate line in the select for the test flag.

MySQL: Creating Multiple Variables with COALESCE

By using COALESCE, I can create a temporary variable called comment_votes like so:
SELECT comments.*, COALESCE(rs_reputations.value, 0) AS comment_votes FROM `comments`
LEFT JOIN rs_reputations ON comments.id = rs_reputations.target_id AND
rs_reputations.target_type = 'Comment' AND rs_reputations.reputation_name =
'comment_votes' AND rs_reputations.active = 1 WHERE (impression_id = 1)
I want to create a second variable called impression_votes in the came query. I attempted to do this with:
SELECT comments.*, COALESCE(rs_reputations.value, 0) AS comment_votes
FROM 'comments'
LEFT JOIN rs_reputations ON
comments.id = rs_reputations.target_id AND
rs_reputations.target_type = 'Comment' AND
rs_reputations.reputation_name = 'comment_votes' AND
rs_reputations.active = 1
SELECT comments.*, COALESCE(rs_reputations.value, 0) AS impression_votes
FROM 'comments'
LEFT JOIN rs_reputations ON
comments.id = rs_reputations.target_id AND
rs_reputations.target_type = 'Comment' AND
rs_reputations.reputation_name = 'impression_votes' AND
rs_reputations.active = 1
WHERE
This leads to the error:
You have an error in your SQL syntax
Is what I'm attempting even possible? If so, I seem to be bridging the two SELECT/COALESCE statements improperly. How should I write this?
The MySQL COALESCE function is actually an inbuilt function that returns the first non-null value - it's not a variable, it's a function that is actually supported across a wide variety of database systems.
For example, with the following table:
| Id | Name | Counter |
| 1 | lolcat | NULL |
| 2 | codez | 1 |
The sql statement:
SELECT Id, Name, COALESCE(counter, 0) AS NonNullCounter FROM table
will return the results:
| Id | Name | NonNullCounter |
| 1 | lolcat | 0 |
| 2 | codez | 1 |
In this instance, the NULL value has been replaced by 0.
This is useful for you as, if you don't yet have any matching rows in rs_reputations for the row in comments, the LEFT JOIN will return NULL for the column rs_repuations.value, which is then replaced by 0 by COALESCE.
If you are new to JOINs then there is a great visual guide by Jeff Atwood.
Your first query can is actually:
SELECT comments.*,
COALESCE(rs_reputations.value, 0) AS comment_votes
FROM comments
LEFT JOIN rs_reputations ON comments.id = rs_reputations.target_id
AND rs_reputations.reputation_name = 'comment_votes'
WHERE impression_id = 1;
CHOICE 1 - UNION
You have a couple of choices - you can either UNION your results together like this:
SELECT comments.*,
COALESCE(rs_reputations.value, 0) AS votes,
'comment_votes' AS vote_type
FROM comments
LEFT JOIN rs_reputations ON comments.id = rs_reputations.target_id
AND rs_reputations.reputation_name = 'comment_votes'
WHERE impression_id = 1
UNION
SELECT comments.*,
COALESCE(rs_reputations.value, 0) AS votes,
'impression_votes' as vote_type
FROM comments
LEFT JOIN rs_reputations ON comments.id = rs_reputations.target_id
AND rs_reputations.reputation_name = 'impression_votes'
WHERE impression_id = 1;
In this instance your results will look like this:
|comments_columns|votes|vote_type |
| * |12 |comment_vote |
| * |2 |impression_vote |
CHOICE 2 - JOIN ON TO THE SAME TABLE TWICE
Or you can self join onto the same table twice by using the same table name but a different alias:
SELECT comments.*,
COALESCE(CommentRep.value, 0) AS comment_votes,
COALESCE(ImpressionRep.value, 0) AS impression_votes,
FROM comments
LEFT JOIN rs_reputations AS CommentRep ON comments.id = CommentRep.target_id
AND CommentRep.reputation_name = 'comment_votes'
LEFT JOIN rs_reputations AS ImpressionRep ON comments.id = ImpressionRep.target_id
AND ImpressionRep.reputation_name = 'impression_votes'
WHERE CommentRep.impression_id = 1
AND ImpressionRep.impression_id = 1
In this instance your results will look like this:
|comments_columns|comment_votes|impression_votes|
| * |12 |0 |
| * |2 |6 |
Finally (phew) the reason you have an error in your original SQL is that you are chaining two SELECT statements together without actually relating them - the SQL doesn't really make sense in this instance as you need to logically relate them (either via a UNION or a repeated join as per above.

Complex Query with related tables - Optimal Solution

The Schema:
I have 3 Tables:
User
Feature
User_has_Feature:
initially all users has no features
Example data:
User:
| id | name |
| 1 | Rex |
| 2 | Job |
Feature:
| id | name |
| 1 | Eat |
| 2 | Walk |
User_has_Feature:
| id | user_id | feature_id | have_feature |
| 1 | 1 | 1 | true |
| 2 | 1 | 1 | true |
| 3 | 2 | 2 | true |
| 4 | 2 | 2 | false |
The questions are:
¿How to get only the records that have all features? (explicitly)
Example:
| user_name | feature_name | feature_status |
| Rex | Eat | true |
| Rex | Walk | true |
How to get records that do not have all the features? (again explicitly)
Example:
| user_name | feature_name | feature_status |
| Job | Eat | true |
| Job | Walk | false |
Some conditions have to be attended
I need the Users list with all features (true or false) in both queries like examples
User have 650k records (for now)
Feature have 45 records (for now)
Is one time query.
The idea is to export the result to a CSV file
Early Solution
thanks to the answers of (#RolandoMySQLDBA, #Tom Ingram, #DRapp) I found a solution:
SELECT u.name, f.name, IF(uhf.status=1,'YES','NO') as status
FROM user u
JOIN user_has_feature uhf ON u.id = uhf.user_id
JOIN feature f ON f.id = uhf.feature_id
JOIN
(
SELECT u.id as id
FROM user u
JOIN user_has_feature uhf ON uhf.user_id = u.id
WHERE uhf.status = 1
GROUP BY u.id
HAVING count(u.id) <= (SELECT COUNT(1) FROM feature)
) as `condition` ON `condition`.id = u.id
ORDER BY u.name, f.id, uhf.status
For get records that do not have all the features and for get all record that have all features change:
WHERE uhf.status = 1 by WHERE uhf.status = 2
HAVING count(u.id) <= (SELECT COUNT(1) FROM feature) by HAVING count(u.id) = (SELECT COUNT(1) FROM feature)
but I want to know if this is an optimal solution?
SELECT
UNF.*,
IF(
(LENGTH(UNF.FeatureList) - LENGTH(REPLACE(UNF.FeatureList,',','')))
= (FC.FeatureCount - 1),'Has All Features','Does Not Have All Features'
) HasAllFeatures
FROM
(SELECT
U.name user_name
GROUP_CONCAT(F.name) Features
FROM
(SELECT user_id,feature_id FROM User_has_Feature
WHERE feature_status = true) UHF
INNER JOIN User U ON UHF.user_id = U.id
INNER JOIN Feature F ON UHF.feature_id = F.id
GROUP BY
U.name
) UNF,
(SELECT COUNT(1) FeatureCount FROM Feature) FC
;
The UNF subquery returns with all users listed in User_has_Feature and a comma-separated list of the features. The column HasAllFeatures is determined by the number of columns in UNF.FeatureList. In your case, there are two features. If the number of commas in UNF.FeatureList is FeatureCount - 1, then the user has all features. Otherwise, user does not have all features.
Here is a better version that shows all users and whether or not they have all, some or no features
SELECT
U.name user_name,
IFNULL(UsersAndFeatures.HasAllFeatures,
'Does Not Have Any Features')
WhatFeaturesDoesThisUserHave
FROM
User U LEFT JOIN
(
SELECT
UHF.user_id id,
IF(
(LENGTH(UHF.FeatureList) - LENGTH(REPLACE(UHF.FeatureList,',','')))
= (FC.FeatureCount - 1),
'Has All Features',
'Does Not Have All Features'
) HasAllFeatures
FROM
(
SELECT user_id,GROUP_CONCAT(Feature.name) FeatureList
FROM User_has_Feature INNER JOIN Feature
ON User_has_Feature.feature_id = Feature.id
GROUP BY user_id
) UHF,
(SELECT COUNT(1) FeatureCount FROM Feature) FC
) UsersAndFeatures
USING (id);
select
u.id,
u.name as User_Name,
f.name as Feature_Name,
uhf.feature_Status
from
( select uhf.user_id,
sum( if( uhf.feature_status, 1, 0 ) ) as UserFeatureCount
from user_has_feature uhf
group by uhf.user_id ) AllUsersWithCounts
join
( select count(*) as AllFeaturesCount
from Feature ) AllFeatures
on AllUsersWithCounts.UserFeatureCount = AllFeatures.AllFeaturesCount
join user u
on AllUsersWithCounts.user_id = u.ID
join user_has_feature uhf
on AllUsersWithCounts.User_id = uhf.user_id
join feature f
on uhf.feature_id = f.id
The above query should get all people that explicitly have ALL features. In order to get those that do NOT have all features, just change the one join from = to <
on AllUsersWithCounts.UserFeatureCount < AllFeatures.AllFeaturesCount
Here's my bash at it
create a view of the general information
CREATE VIEW v_users_have_features AS
SELECT usr.id, usr.name, feature.name, has_feature.status
FROM usr
JOIN has_feature ON usr.id = has_feature.user_id
JOIN feature ON has_feature.feature_id = feature.id;
use the view for other queries
SELECT v_users_have_features.id, v_users_have_features.u_name, v_users_have_features.f_name
FROM v_users_have_features
GROUP BY v_users_have_features.id
HAVING COUNT( v_users_have_features.id ) = (SELECT COUNT( feature.id )
FROM feature
WHERE feature.name = v_users_have_features.f_name )
p.s. you may need to adapt (particularly the latter) to your exact requirements you could also omit creating the view and nest it in the FROM clause like in another answer it just seemed handier to create the view
Count the number of features. Write a query over users that uses a correlated subquery to find all the features a user has and count them. Make the restriction criterion in the top query the equality of that count and the global number of features.
Can MySQL do correlated subqueries? If not, you might need to use a better database.