Complex search problem in mysql query - mysql

I have models(tables) in my database with table and fields name like
tags (id, name)
taggings (id, tag_id, taggable_id, taggable_type, context)
employment_histories (id, user_id, grades, subjects, my_interests )
users (id)
taggable_id is actually employment_histories_id and context can either be grade or subjects or my_interests
now I have array of tags e.g. g={"9th","10th"}
and I want to get users, only whose tags are all matching to the above g array.
I've written the query below:
SELECT DISTINCT users.* FROM `users`
LEFT OUTER JOIN `employment_histories`
ON `employment_histories`.`user_id` = `users`.`id`
LEFT OUTER JOIN `taggings`
ON `employment_histories`.`id` = `taggings`.`taggable_id`
AND `taggings`.`taggable_type` = 'EmploymentHistory'
LEFT OUTER JOIN `tags` ON taggings.context = 'subjects'
WHERE tags.name='9th' OR tags.name='10th'
but it gives me those users too, which match any of the tags, however I want that it will return only that user who match all the two tags
Suppose that tags 9th and 10th have tag id 9 and 10 then what i want that it will only return the taggable_id(which is employmenthistories.id) who has common taggable_id for these two tag_id (that is 9 and 10) in taggings table
for example i have two user tariq and kamal and both of these users have 9th tag common but kamal dont have tag 10th so want query which if passed these two tags should return only tariq whose tags are all macthing these two tags but users like kamal which match any of the tags should be filtered too

From php chat room:
SELECT
users.* ,
count(*) AS count
FROM users
LEFT JOIN employment_histories ON users.id = employment_histories.user_id
LEFT JOIN tagging ON tagging.taggable_id = employment_histories.id
LEFT JOIN tags ON tags.id = tagging.tag_id
WHERE tags.name = "9th"
OR tags.name = "10th"
GROUP BY users.id
HAVING count = 2

SELECT users.* FROM users
INNER JOIN employment_histories
ON employment_histories.user_id = users.id
INNER JOIN taggings
ON employment_histories.id = taggings.taggable_id
AND taggings.taggable_type = 'EmploymentHistory'
AND taggings.context = 'subjects'
INNER JOIN tags ON tags.id = taggings.tag_id
WHERE tags.name IN ('9th','10th')
GROUP BY users.id
HAVING COUNT(DISTINCT(tags.name)) = 2;

I have re-wrote the query.
Few changes:
Joining Tags on tags.id = taggings.tag_id
Remove OR from where clause, and use in, improves the performance.
SELECT DISTINCT users.*, count(*) as totRow FROM `users`
LEFT OUTER JOIN `employment_histories`
ON `employment_histories`.`user_id` =
`users`.`id` LEFT OUTER JOIN
`taggings` ON
`employment_histories`.`id` =
`taggings`.`taggable_id` AND
`taggings`.`taggable_type` =
'EmploymentHistory' AND
`taggings`.`context` = 'subjects'
LEFT OUTER JOIN `tags` ON `tags`.`id` = `taggings`.`tag_id`
WHERE tags.name = '9th' or tags.name = '10th'
GROUP BY `users`.`id`

Related

MySql: Check where clause for all row returned by an inner join

I have this query
SELECT *
FROM posts
INNER JOIN categories ON categories.post_id = posts.id
INNER JOIN tags ON tags.category_id = categories.id
WHERE tags.title = 'week_trend'
Each posts has multiple categories and also each category has multiple tags and I need the posts that have the categories with the specified tag but all the post categories should have this condition and even if one of those categories failed the condition the post shouldn't be included. My query returns the posts even if one of their categories has the specified tag.
I almost have no idea how to do it can someone help me tnx
This query:
SELECT c.post_id
FROM categories c INNER JOIN tags t
ON t.category_id = c.id
GROUP BY c.post_id
HAVING COUNT(DISTINCT c.id) = SUM(t.title = 'week_trend')
returns all the post_ids with categories that are all related to the tag with title 'week_trend'.
Use it with an IN clause:
SELECT *
FROM posts
WHERE id IN (
SELECT c.post_id
FROM categories c INNER JOIN tags t
ON t.category_id = c.id
GROUP BY c.post_id
HAVING COUNT(DISTINCT c.id) = SUM(t.title = 'week_trend')
)

mysql SELECT column only if exists

I have 5 tables I want to select data from, but sometimes the 5th table (named comment) will be empty. When that happens I want my query to return null for the values from that table, and only return the other values (the 5th table includes user comments, sometimes there are none).
Here is my query:
SELECT articles.title, articles.posted, articles.body, authors.name, authors.img, authors.bio, comment.user_id, comment.text, comment.article_id, GROUP_CONCAT(categories.cat_name) AS cat_name
FROM articles, authors, categories, article_categories, comment
WHERE articles.author_id = authors.id
AND articles.id = article_categories.article_id
AND article_categories.category_id = categories.id
AND articles.id = comment.article_id
AND title LIKE :title;
the :title comes from PDO and is not relevant here.
The problem comes from the AND articles.id = comment.article_id. I don't know how to write it in a way to only check for this, if it's there and ignore it otherwise.
Thanks for the help
You should use proper join syntax, and in this case instead of INNER JOIN use LEFT JOIN for comment table:
SELECT articles.title, articles.posted, articles.body, authors.name, authors.img, authors.bio, comment.user_id, comment.text, comment.article_id, GROUP_CONCAT(categories.cat_name) AS cat_name
FROM articles INNER JOIN authors ON articles.author_id = authors.id
INNER JOIN article_categories ON articles.id = article_categories.article_id
INNER JOIN categories ON article_categories.category_id = categories.id
LEFT JOIN comment ON articles.id = comment.article_id
WHERE title LIKE :title;
You should use left/right joins to do that.
[...]
select *
from articles a
left join comments c
on a.id = c.article_id
[...]
To learn more about joins:
http://blog.codinghorror.com/a-visual-explanation-of-sql-joins

SQl queries left join not giving accurate results

I have been trying to join two tables (USERS AND USERS_ROLES) based on their role id I put the left join on following query
users.id = users_roles.fk_user_id
but the output is not correct of users_roles.fk_role_id coulmun and shows NULL where it should display the id of users.id = users_roles.fk_user_id that is 4 (at most places) because on users.id = users_roles.fk_user_id the value of users_roles.fk_role_id = 4
Kindly let me know how can i fix that so my query should result the exact vlaues of ids where they match,
Thanks
SELECT users.id, users.v_first_name, users.v_last_name, user_facility.fk_facility_id,users.fk_tenant_id, marital_status.v_marital_status,
users.v_blood_type, NOW(),users_roles.fk_role_id
FROM users
LEFT JOIN (user_facility, marital_status, users_roles) ON
users.id = user_facility.fk_user_id AND users.fk_marital_status_id=marital_status.id AND users.id = users_roles.fk_user_id
Usage of AND operator when used with Left or Right join gives different result. You should be clear what you are trying to accomplish..See this
well it is what you get by first implicitly inner-joining 3 tables and then explicitly left-joining the result to a 4th table only if 3 conditions relevant to all of the 3 inner-joinded tables are matched (i.e. when 3rd condition is false, nothing is joined from either of the 2 remaining tables)
i strongly suggest not to combine implicit and explicit joins, i personally use explicit joins all the time:
if you need an outer join:
SELECT ...
FROM users
LEFT JOIN user_facility ON users.id = user_facility.fk_user_id
LEFT JOIN marital_status ON users.fk_marital_status_id=marital_status.id
LEFT JOIN users_roles ON users.id = users_roles.fk_user_id
if you need an inner join:
SELECT ...
FROM users
JOIN user_facility ON users.id = user_facility.fk_user_id
JOIN marital_status ON users.fk_marital_status_id=marital_status.id
JOIN users_roles ON users.id = users_roles.fk_user_id
or if you prefere implicit inner joins for some obscure reason:
SELECT ...
FROM users,
user_facility,
marital_status,
users_roles
WHERE users.id = user_facility.fk_user_id
AND users.fk_marital_status_id=marital_status.id
AND users.id = users_roles.fk_user_id
(implicit outer joins are getting deprecated in all RDBMS as far as i know)
When it shows NULL it means there isn't a correspondency (relation) between all tables in the JOIN clause.
If you want to show only the ones that have relations in all tables, use INNER JOIN instead.
SELECT u.id,
u.v_first_name,
u.v_last_name,
uf.fk_facility_id,
u.fk_tenant_id,
ms.v_marital_status,
u.v_blood_type,
NOW(),
ur.fk_role_id
FROM users u
INNER JOIN user_facility uf ON u.id = uf.fk_user_id
INNER JOIN marital_status ms ON u.fk_marital_status_id=ms.id
INNER JOIN users_roles ur ON u.id = ur.fk_user_id

Nested query on join?

SELECT adminTags.Tag, URL, Votes
FROM `adminTags`
LEFT JOIN `Tags` ON adminTags.Tag = Tags.Tag
I'm joining table adminTags with table tags.
How can I first
SELECT *
FROM `Tags`
WHERE URL = "$site"
and then join this to adminTags?
You mean...
SELECT adminTags.Tag, URL, Votes FROM `adminTags`
LEFT JOIN `Tags` ON adminTags.Tag = Tags.Tag AND Tags.URL = "$site"
I suspect you tried first to add WHERE URL = "$site" which will effectively make this an INNER JOIN (since the WHERE would not be satisfied without the URL also being satisfied.
Putting the condition in the ON resolves that — you're joining only when there's a match.
SELECT adminTags.Tag, URL, Votes
FROM `adminTags` LEFT JOIN `Tags`
ON adminTags.Tag = Tags.Tag
WHERE `Tags`.URL = "$site"
SELECT adminTags.Tag, adminTags.URL, adminTags.Votes FROM `Tags`
LEFT JOIN `adminTags` ON adminTags.Tag = Tags.Tag
WHERE adminTags.URL = "$site"
There are two options when using an OUTER join, and the results can be very different so details matter.
SELECT at.Tag,
t.url, t.votes
FROM adminTags at
LEFT JOIN tags t ON t.tag = at.tag
AND t.url = $site
This query will apply the t.url = $site before the join is made, giving you pre-filtered results.
SELECT at.Tag,
t.url, t.votes
FROM adminTags at
LEFT JOIN tags t ON t.tag = at.tag
WHERE t.url = $site
The query above applies the criteria after the join has been made. It does not "convert an OUTER JOIN into an INNER JOIN" -- it filters the result set after the OUTER JOIN has been made.
In this example, there's no difference between the result sets. However, if the URL column say was not nullable (value could not be NULL) you could check for NULLs (which would tell you what ADMINTAGS don't have corresponding TAGS records:
SELECT at.Tag,
t.url, t.votes
FROM adminTags at
LEFT JOIN tags t ON t.tag = at.tag
WHERE t.url IS NULL
...whereas the following would return NULL for all references to TAGS (while not accurately demonstrating ADMINTAGS rows without related TAGS rows:
SELECT at.Tag,
t.url, t.votes
FROM adminTags at
LEFT JOIN tags t ON t.tag = at.tag
AND t.url IS NULL

Searching multiple rows in select with left join

I've got 3 tables, products, products_tags and tags. A product can be connected to multiple tags via the products_tags table.
But if i would like to search on a product now with multiple tags, i do a query like this:
SELECT
*
FROM
products
LEFT JOIN
products_tags
ON
products_tags.product_id = products.id
LEFT JOIN
tags
ON
products_tags.tag_id = tags.id
WHERE
tags.name = 'test'
AND
tags.name = 'test2'
Which doesn't work :(.
If i remove the AND tags.name = 'test2' it works. So i can only search by one tag, i explained the query and it said impossible where.
How can i search on multiple tags using a single query?
Thanks!
Have you tried something like:
WHERE
(tags.name = 'test'
OR
tags.name = 'test2')
Or
WHERE
tags.name in( 'test', 'test2')
Because even if you join one product to multiple tags, each tag record only has one value for name.
you need to join twice for test and test2:
select products.*
from products
join product_tags as product_tag1 on ...
join tags as tag1 on ...
join product_tags as product_tag2 on ...
join tags as tag2 on ...
where tag1.name = 'test'
and tag2.name = 'test2'
for test or test2, you need one join and an in clause and a distinct:
select distinct products.*
from products
join product_tags on ...
join tags as tags on ...
where tags.name IN('test', 'test2')
You'll have to do a group by and COUNT(*) to ensure BOTH (or however many) are ALL found.
The first query (PreQuery) joins the products tags table to tags and looks for same with matching count of tags to find... THEN uses that to join to products for finalized list
SELECT STRAIGHT_JOIN
p.*
FROM
( select pt.product_id
from products_tags pt
join tags on pt.tag_id = tags.id
where tags.name in ('test1', 'test2' )
group by pt.product_id
having count(*) = 2
) PreQuery
join products on PreQuery.Product_ID = Products.ID
If you are searching for products that have BOTH the "test" and "test2" tags, then you will need to join to the product_tag and tag table twice each.
Also, use inner joins since you only want the products that have these tags.
Example:
SELECT products.*
FROM products
INNER JOIN products_tags pt1 ON pt1.product_id = products.id
INNER JOIN products_tags pt2 ON pt2.product_id = products.id
INNER JOIN tags t1 ON t1.id = pt1.tag_id
INNER JOIN tags t2 ON t2.id = pt2.tag_id
WHERE t1.name = 'test'
AND t2.name = 'test2'