LEFT JOIN ManyToMany multiple parameters - mysql

I have some problems with a MYSQL Query. I'm trying to do a LEFT JOIN with multiple parameters.
DB Structure:
A soldier can have more than one tag, these tags are assigned to the soldier with a ManyToMany relationship
My query is used in a search function where the user is able to add some tags he/she want to look for. So far its possible to search with one tag but as soon you add more than one tag, the query will not return an result even if it should.
Query used:
SELECT *
FROM soldiers s LEFT JOIN
soldier_tag st
ON s.id = st.soldier_id
WHERE st.tag_id = 5;
When the user enters 2 tags, then both tags should math. The query will become this:
SELECT *
FROM soldiers s LEFT JOIN
soldier_tag st
ON s.id = st.soldier_id
WHERE st.tag_id = 5 AND st.tag_id = 7;
Does anyone have an idea how I can fix this problem?
Thanks in advance

You will want to use 'IN' followed by the parameter as done below, this will consider both parameters when the query is executed.
SELECT *
FROM soldiers s LEFT JOIN
soldier_tag st
ON s.id = st.soldier_id
WHERE st.tag_id IN (5,7);

When a join condition is related to a set of values (eg a list of tag) You should use a in clause
SELECT *
FROM soldiers s
LEFT JOIN soldier_tag st ON s.id=st.soldier_id
WHERE st.tag_id in ( 5 , 7)

If you want all tags to match, I would suggest phrasing the query as something like this:
SELECT s.*
FROM soldiers s
WHERE s.id IN (SELECT st.soldier_id
FROM solder_tag st
WHERE st.tag_id IN (5, 7) -- construct an `IN` list instead of a bunch of boolean expressions
GROUP BY st.soldier_id
HAVING COUNT(*) = 2 -- 2 is the number of tags
);
The subquery returns the soldier_ids where all the tags match. You need to put the list in IN and the count in the HAVING clause.

You should use OR operator in your WHERE clause:
SELECT *
FROM soldiers s
LEFT JOIN soldier_tag st ON s.id=st.soldier_id
WHERE st.tag_id=5 OR st.tag_id=7
If it's possible to have more than 2 tags in query, it's better to use IN operator as #scaisEdge mentioned

Related

SQL query result within IN

SELECT ids_horarios_adicionales from adicionales WHERE id_adicionales=1
So this query returns as result 1,3, the row ids_horarios_adicionales will contain ids separated with commas.
What im intending to do is this:
SELECT *
FROM adicionales a
LEFT JOIN horarios b ON b.id_horarios IN (1,3)
WHERE id_adicionales=1
but when I use this query, results are not the same:
SELECT *
FROM adicionales a
LEFT JOIN horarios b ON b.id_horarios IN (SELECT ids_horarios_adicionales from adicionales WHERE id_adicionales=1)
WHERE id_adicionales=1
any idea how to make SELECT query inside IN() to print as 1,3
This is the schema and sql query result I want, any other approach sugested will be helpful: http://sqlfiddle.com/#!9/714a50/2
You should not be storing values in a comma-separated list. That is the problem you are facing. I would recommend figuring out how to change your data structure.
But, sometimes, you are stuck with other people's really bad designs (such as storing integer values in strings). In that case, you can use find_in_set():
SELECT *
FROM adicionales a LEFT JOIN
horarios h
ON EXISTS (SELECT 1
FROM adicionales a2
WHERE find_in_set(h.id_horarios , a2) > 0 AND
a2.id_adicionales = 1
)
WHERE id_adicionales = 1
you can use FIND_IN_SET by this way
SELECT *
FROM adicionales a
LEFT JOIN horarios b ON
FIND_IN_SET(a.ids_horarios_adicionales,b.id_horarios)
WHERE id_adicionales=1
I'm not sure of your schema but in general when using LEFT JOIN you need to detail how the join between the tables us done. For example, LEFT JOIN horarios b ON b.index = adicionales.index and then use the where statement to limit rows

Duplicate column name SQL - need change alias?

I have written SQL query with a INNER JOIN and Sub-query:
SELECT c.*,
ar.ArticleName,
ar.idArticle,
du.DetailToUsersName,
du.DetailToUsersPhoto,
COUNT(c.idCommentToArticle) AS CNT,
CASE WHEN d.Count IS NULL THEN 0 ELSE d.Count END AS CountLikes
from (select *
from commenttoarticle g
inner join (select distinct(s.idCommentToArticle)
from commenttoarticle s
order by s.CommentToArticlePID limit 3) as gh) as c
LEFT JOIN article ar ON c.CommentToArticleIdArticle = ar.idArticle
LEFT JOIN detailtousers du ON du.idDetailToUsers = c.CommentToArticleIdUser
LEFT JOIN `likes` d ON (d.IdNote = c.idCommentToArticle AND d.LikeType = 6)
WHERE c.CommentToArticleIdArticle = 11
GROUP BY c.idCommentToArticle
ORDER BY c.idCommentToArticle DESC
So, I get error:
Duplicate column name 'idCommentToArticle'
I can not find where the duplication is?
you can specify in the alias table query c
select g.* from commenttoarticle g
instead of
select * from commenttoarticle g
Also you should specify Join condition to limit the rows to 3 as per your intention, with out the ON clause it will be like a cross join.
select g.* from commenttoarticle g
inner join (select distinct(s.idCommentToArticle) from commenttoarticle s order by s.CommentToArticlePID limit 3) as gh
on g.idcommenttoarticle = gh.idcommenttoarticle
As #RADAR has suggested, your inner query joins don't seem to be complete. And I see from comments that once you place the JOIN condition in, then you lose all data. I think this is because neither part of the subqueries were doing what they were supposed to do.
Here is my attempt at a total solution (note, without dataset and table definition I can't show it working). OK, so you have asked the question again over here and provided a SQL-Fiddle, I have updated with a working version, but minus the additional JOIN tables, since they are not defined.
SELECT c.*,
ar.ArticleName,
ar.idArticle,
du.DetailToUsersName,
du.DetailToUsersPhoto,
COUNT(c.idCommentToArticle) AS CNT,
CASE WHEN d.Count IS NULL THEN 0 ELSE d.Count END AS CountLikes
FROM commenttoarticle c -- one layer of subquery not required.
INNER JOIN (select s.idCommentToArticle, s.CommentToArticlePID -- added both the id and the parent id
FROM commenttoarticle s
WHERE s.CommentToArticleIdArticle = 11 -- moved to inner query, instead of outer query
ORDER BY s.idCommentToArticle DESC limit 3) as gh
ON c.idcommenttoarticle = gh.idcommenttoarticle -- add join condition
OR c.idcommenttoarticle = gh.CommentToArticlePID -- which matches id and parent id
LEFT JOIN article ar ON c.CommentToArticleIdArticle = ar.idArticle
LEFT JOIN detailtousers du ON du.idDetailToUsers = c.CommentToArticleIdUser
LEFT JOIN `likes` d ON (d.IdNote = c.idCommentToArticle AND d.LikeType = 6)
GROUP BY c.idCommentToArticle
ORDER BY c.idCommentToArticle DESC
But let me explain a little further, the following code from your original query was selecting the top 3 idCommentToArticlePID,
(select *
from commenttoarticle g
inner join (select distinct(s.idCommentToArticle)
from commenttoarticle s
order by s.CommentToArticlePID limit 3) as gh)
but then because there was no ON specified the 3 records were then joined to every single record from the g reference. This resulted in the full dataset being returned.
And then you you specified WHERE c.CommentToArticleIdArticle = 11 this filtered the result set back down again to something that looked correct.
When you then added the ON (as per #RADAR's suggestion) the inner query did not contain any values that matched the WHERE c.CommentToArticleIdArticle = 11 filter and thus you lost all your results. If you move this filter into the inner query as shown above, then these will work together and not conflict.
Within the JOIN condition, you indicate that you want both the matching articles and their parents, so I added both to the return of the inner query, and checked for either in the join condition.
Also I think the whole g table reference is redundant and can be removed. You should be able to access this table directly as c.
I also have some concerns about the GROUP BY and COUNT (c.idCommentToArticle) - there seem a little strange, but I have no supporting context (ie data examples), so they may be correct. If you still have issues, I would comment the GROUP BY and COUNT statements out, and test to see what data you are getting, before adding these back in.

Debugging MySQL query result. Uses count function, inner join and a sub-query

This is my query. The output looks fine except the COUNT function is returning numbers which seem totally arbitrary (e.g. 7-digit numbers where I'd expect 3-digit numbers):
SELECT tc.tableName, m.fieldName, COUNT(m.fieldName)
FROM apiResult, (
SELECT cc.surveyID, cc.fieldName
FROM apiResult as ar
INNER JOIN columnConversion as cc
ON substring(ar.triggerName,-10)=cc.fieldID
) AS m
INNER JOIN tableConversion as tc
ON m.surveyID=tc.surveyID
GROUP BY tc.tableName, m.fieldName;
I think, for a start, that COUNT(m.fieldName) is probably wrong, since it doesn't correspond with GROUP BY tc.tableName, m.fieldName.
Here's what the query is meant to do: one of the tables in the sub-query, apiResult, has a column called 'triggerName' which contains an ID I call 'fieldID', plus a column called 'surveyID'. The tables columnConversion and tableConversion are tables which match the IDs to human readble names. So, the follow query produces the count that I want, but, I want the IDs replaced by the human readable names, hence the above query:
SELECT cc.surveyID, cc.fieldName, COUNT(ar.triggerName)
FROM apiResult as ar
INNER JOIN columnConversion as cc
ON substring(ar.triggerName,-10)=cc.fieldID
GROUP BY (ar.triggerName)
Any ideas what I've done wrong?
Why are you mixing explicit and implicit joins? You appear to have missed a join condition on the first table. Well, actually, I don't think it is needed. This should work:
SELECT tc.tableName, m.fieldName, COUNT(m.fieldName)
FROM (SELECT cc.surveyID, cc.fieldName
FROM apiResult ar INNER JOIN
columnConversion cc
ON substring(ar.triggerName, -10) = cc.fieldID
) m INNER JOIN
tableConversion as tc
ON m.surveyID = tc.surveyID
GROUP BY tc.tableName, m.fieldName;

Is it possible to convert this subquery into a join?

I want to replace the subquery with a join, if possible.
SELECT `fftenant_farmer`.`person_ptr_id`, `fftenant_surveyanswer`.`text_value`
FROM `fftenant_farmer`
INNER JOIN `fftenant_person`
ON (`fftenant_farmer`.`person_ptr_id` = `fftenant_person`.`id`)
LEFT OUTER JOIN `fftenant_surveyanswer`
ON fftenant_surveyanswer.surveyquestion_id = 1
AND fftenant_surveyanswer.`surveyresult_id` IN (SELECT y.`surveyresult_id` FROM `fftenant_farmer_surveyresults` y WHERE y.farmer_id = `fftenant_farmer`.`person_ptr_id`)
I tried:
SELECT `fftenant_farmer`.`person_ptr_id`, `fftenant_surveyanswer`.`text_value`#, T5.`text_value`
FROM `fftenant_farmer`
INNER JOIN `fftenant_person`
ON (`fftenant_farmer`.`person_ptr_id` = `fftenant_person`.`id`)
LEFT OUTER JOIN `fftenant_farmer_surveyresults`
ON (`fftenant_farmer`.`person_ptr_id` = `fftenant_farmer_surveyresults`.`farmer_id`)
LEFT OUTER JOIN `fftenant_surveyanswer`
ON (`fftenant_farmer_surveyresults`.`surveyresult_id` = `fftenant_surveyanswer`.`surveyresult_id`)
AND fftenant_surveyanswer.surveyquestion_id = 1
But that gave me one record per farmer per survey result for that farmer. I only want one record per farmer as returned by the first query.
A join may be faster on most RDBMs, but the real reason I asked this question is I just can't seem to formulate a join to replace the subquery and I want to know if it's even possible.
You could use DISTINCT or GROUP BY, as mvds and Brilliand suggest, but I think it's closer to the query's design intent if you change the last join to an inner-join, but elevating its precedence:
SELECT farmer.person_ptr_id, surveyanswer.text_value
FROM fftenant_farmer AS farmer
INNER
JOIN fftenant_person AS person
ON person.id = farmer.person_ptr_id
LEFT
OUTER
JOIN
( fftenant_farmer_surveyresults AS farmer_surveyresults
INNER
JOIN fftenant_surveyanswer AS surveyanswer
ON surveyanswer.surveyresult_id = farmer_surveyresults.surveyresult_id
AND surveyanswer.surveyquestion_id = 1
)
ON farmer_surveyresults.farmer_id = farmer.person_ptr_id
Broadly speaking, this will end up giving the same results as the DISTINCT or GROUP BY approach, but in a more principled, less ad hoc way, IMHO.
Use SELECT DISTINCT or GROUP BY to remove the duplicate entries.
Changing your attempt as little as possible:
SELECT DISTINCT `fftenant_farmer`.`person_ptr_id`, `fftenant_surveyanswer`.`text_value`#, T5.`text_value`
FROM `fftenant_farmer`
INNER JOIN `fftenant_person`
ON (`fftenant_farmer`.`person_ptr_id` = `fftenant_person`.`id`)
LEFT OUTER JOIN `fftenant_farmer_surveyresults`
ON (`fftenant_farmer`.`person_ptr_id` = `fftenant_farmer_surveyresults`.`farmer_id`)
LEFT OUTER JOIN `fftenant_surveyanswer`
ON (`fftenant_farmer_surveyresults`.`surveyresult_id` = `fftenant_surveyanswer`.`surveyresult_id`)
AND fftenant_surveyanswer.surveyquestion_id = 1
the real reason I asked this question is I just can't seem to formulate a join to replace the subquery and I want to know if it's even possible
Then consider a much simpler example to begin with e.g.
SELECT *
FROM T1
WHERE id IN (SELECT id FROM T2);
This is known as a semi join and if desired may be re-written using (among other possibilities) a JOIN with a SELECT clause to a) project only from the 'outer' table, and b) return only DISTINCT rows:
SELECT DISTINCT T1.*
FROM T1
JOIN T2 USING (id);

Multiple GROUP_CONCAT on different fields using MySQL

I have a query like this:
SELECT product.id,
GROUP_CONCAT(image.id) AS images_id,
GROUP_CONCAT(image.title) AS images_title,
GROUP_CONCAT(facet.id) AS facets_id
...
GROUP BY product.id
And the query works, but not as expected, because if I have a product with 5 facets and 1 image (suppose an image with id=7), then I get something like this in "images_id":
"7,7,7,7,7"
If I have 2 images (7 and 3) then I get something like:
"7,7,7,7,7,3,3,3,3,3"
and in facets I get something like:
"8,7,6,5,4,8,7,6,5,4"
I think MySQL is making some type of union of the differents rows returned by the query, and then concatenating everything.
My expected result is (for the last example):
images_id = "7,3"
facets_id = "8,7,6,5,4"
I can obtain that using DISTINCT in the GROUP_CONCAT, but then I have another problem:
If I have two images with the same title, one of them is ommited, and then I get something like:
images_id = "7,3,5"
images_title = "Title7and3,Title5"
So I miss the relation between images_id and images_title.
Does somebody know if it's possible to make this query in MySQL?
Maybe I'm complicating everything without any real benefits.
I'm trying to execute only one query because performance, but now I'm not so sure if it's even faster to execute two queries (one for selecting the facets and another for the images for example).
Please explain what do you think is the best solution for this and why.
Thanks !
Just add DISTINCT.
Example:
GROUP_CONCAT(DISTINCT image.id) AS images_id
You'll need to get each group separately:
SELECT
p.id,
images_id,
images_title,
facets_id,
...
FROM PRODUCT p
JOIN (SELECT product.id, GROUP_CONCAT(image.id) AS images_id
FROM PRODUCT GROUP BY product.id) a on a.id = p.id
JOIN (SELECT product.id, GROUP_CONCAT(image.title) AS images_title
FROM PRODUCT GROUP BY product.id) b on b.id = p.id
JOIN (SELECT product.id, GROUP_CONCAT(facet.id) AS facets_id
FROM PRODUCT GROUP BY product.id) b on c.id = p.id
...
You can add just the DISTINCT keyword, you'll get your desire results.
SELECT tb_mod.*, tb_van.*,
GROUP_CONCAT(DISTINCT tb_voil.vt_id) AS voil,
GROUP_CONCAT(DISTINCT tb_other.oa_id) AS other,
GROUP_CONCAT(DISTINCT tb_ref.rp_id) AS referral
FROM cp_modules_record_tbl tb_mod
LEFT JOIN cp_vane_police_tbl tb_van ON tb_van.mr_id= tb_mod.id
LEFT JOIN cp_mod_voilt_tbl tb_voil ON tb_voil.mr_id= tb_mod.id
LEFT JOIN cp_mod_otheraction_tbl tb_other ON tb_other.mr_id= tb_mod.id
LEFT JOIN cp_mod_referral_tbl tb_ref ON tb_ref.mr_id= tb_mod.id
WHERE tb_mod.mod_type = 2 GROUP BY tb_mod.id
If the issue is speed, then it may be a lot faster to simply select all the data you need as separate rows, and do the grouping in the application, i.e.:
SELECT product.id, image.id, image.title, facet.id
Then in the application:
foreach row:
push product_id onto list_of_product_ids
push image_id onto list_of_image_ids
etc.