sql many to many select with join - mysql

I am trying to make a select statement and I just cant get it work.
I have 3 tables:
places, tags, places_tags
Places:
- id
- name
Tags:
- id
- name
Places_tags:
- place_id
- tag_id
- order
I am trying to select places and join the first tag that inserted (using order)
SELECT p.*, t.tag_id AS tag
FROM `places` as p
LEFT JOIN places_tags t ON (t.place_id = p.id)
group by p.id
That's what I have right now.
I need to add somthing like ORDER BY order DESC...
I think that I'm not doing it right.

Something like the following should work:
SELECT
p.name AS "place",
t.name AS "firstTag"
FROM
places p
LEFT JOIN
places_tags pt1
ON pt1.place_id = p.id
LEFT JOIN
places_tags pt2
ON pt2.place_id = p.id AND pt2.tag_id < pt1.tag_id
LEFT JOIN
tags t
ON t.id = pt1.tag_id
WHERE
pt2.tag_id IS NULL

Related

Select rows that matches multiple and/or conditions

So I created a sql fiddle to explain my problem much clearer:
http://sqlfiddle.com/#!9/3122282/1
As you can see I have 3 tables and 1 of them links the 2 others.
I want to make it so if I say "give me the products that is (color green OR red) and PET (dog)"?
I tried doing:
select `ptl`.`product_id`
from `tags` inner join `tags` as `ptl`
on `tags`.`id` = `ptl`.`tag_id`
where ((`tags`.`tag` = "color" and `tags.value` in ("green", "red"))
or (`tags`.`tag` = "pet" and `tags.value` in ("dog")))
having count(distinct `ptl.tag_id`) = 2
// 2 in that case is the number of tag "category".
but this doesn't seem to work. since having is just checking the count, it will also return the products with 2 color tags without any pet.
You can join the 3 tables, group by product and set the conditions in the HAVING clause:
SELECT p.id, p.name
FROM products p
INNER JOIN product_tags_link pt ON pt.product_id = p.id
INNER JOIN tags t ON pt.tag_id = t.id
GROUP BY p.id, p.name
HAVING SUM(t.tag = 'color' AND t.value IN ('green', 'red')) > 0
AND SUM(t.tag = 'pet' AND t.value IN ('dog')) > 0
See the demo.
You are not joining tags table with product_tags_link and products.
Take this query as a base and add the conditions on the where clause
select *
from products p
inner join product_tags_link ptl on ptl.product_id = p.id
inner join tags t on t.id = ptl.tag_id
where CONDITIONS
a CONDITIONS that can be taken as example
p.id = 1 and t.tag = 'color' and t.value = 'green'

GROUP BY and ORDER BY issues

I have the following query:
SELECT DISTINCT (
s.styleTitle
), COUNT(p.id) AS `PictureCount`
FROM `style` s
LEFT JOIN `instagram_picture_style` ps ON s.id = ps.style_id
LEFT JOIN `instagram_shop_picture` p ON ps.picture_id = p.id
LEFT JOIN `instagram_picture_category` c ON c.picture_id = p.id
LEFT JOIN `instagram_second_level_category` sl ON c.second_level_category_id = sl.id
WHERE sl.id =25
GROUP BY p.id
ORDER BY PictureCount
however this query gives me:
I basically wanted the list to be ordered by the style that has the most pictures in it. What did I do wrong? Why is it giving me 1 on all of the styles, I am pretty sure it has more pictures for that style
ORDER BY doesn't have underscores. But equally important, you are using DISTINCT in a way where you seem to think that it is a function. It is not. It is a modifies on the SELECT and it applies to all columns.
You should group by the same column you have in the distinct. Something like this:
SELECT s.styleTitle, COUNT(p.id) AS `PictureCount`
FROM `style` s
LEFT JOIN `instagram_picture_style` ps ON s.id = ps.style_id
LEFT JOIN `instagram_shop_picture` p ON ps.picture_id = p.id
LEFT JOIN `instagram_picture_category` c ON c.picture_id = p.id
LEFT JOIN `instagram_second_level_category` sl ON c.second_level_category_id = sl.id
WHERE sl.id = 25
GROUP BY s.styleTitle
ORDER BY PictureCount DESC;
In fact, you almost never need distinct with group by. If you are using, you need to think why it would be necessary.

SQL - Ambiguous Column

I can't see what is wrong with this query. I get an error saying:
"column article_id in from clause is ambiguous"
I understand that it may have something to do with table name aliases but not sure of how to fix. If the query was smaller I may be able to work something out but it's pretty confusing to me and every time I change something to try and fix it, something else stops - so I thought I'd ask first.
SELECT bt.topic_title, f.article_id, p.photo_id, ba.title, ba.slug,
IFNULL(c.cnt,0) comments, IFNULL(ph.cnt,0) photos, IFNULL(v.cnt,0) videos
FROM blog_article_followers AS f
LEFT OUTER JOIN (
SELECT article_id, COUNT(comment_id) as cnt
FROM blog_comments
GROUP BY article_id) c
ON f.article_id = c.article_id
LEFT OUTER JOIN (" _
SELECT article_id, COUNT(photo_id) as cnt
FROM photos
GROUP BY article_id) ph
ON f.article_id = ph.article_id
LEFT OUTER JOIN (
SELECT article_id, COUNT(video_id) as cnt
FROM videos
GROUP BY article_id) v
ON f.article_id = v.article_id
LEFT JOIN blog_topics bt ON f.topic_id = bt.topic_id
LEFT JOIN blog_articles AS ba USING (article_id)
LEFT JOIN photos AS p USING (article_id)
WHERE f.member_id = 100 AND p.cover = 1
ORDER BY f.follow_date DESC;
Try replacing this:
LEFT JOIN blog_articles AS ba USING (article_id)
LEFT JOIN photos AS p USING (article_id)
With this
LEFT JOIN blog_articles AS ba ON f.article_id = ba.article_id
LEFT JOIN photos AS p ON f.article_id = photos.article_id
you have to rename the column
LEFT JOIN photos AS p USING (p.article_id)
or to whichever table article_id belongs to

How to search on MySQL using JOINs?

Can anyone tell me ways to do this kind of search in a database?
I got these tables:
posts (id, tags_cache)
tags (id, name)
posts_tags (post_id, tag_id)
The user enters a search query (say "water blue") and I want to show the posts that have both tags.
The only way I can think of to search is using FIND_IN_SET, this way:
SELECT p.*, GROUP_CONCAT(t.name) AS tags_search
FROM posts p
LEFT JOIN posts_tags pt ON p.id = pt.post_id
LEFT JOIN tags t ON pt.tag_id = t.id
GROUP BY p.id
HAVING FIND_IN_SET('water', tags_search) > 0
AND FIND_IN_SET('blue', tags_search) > 0
The posts.tags_cache text column stores the names and id of the tags it belongs to (this way: water:15 blue:20).
To avoid JOINs by using this column for search, I've tried LIKE and INSTR but these will give inexact results since you can search for "ter" and you'll gets posts tagged 'water' and 'termal' for example. I've also tried REGEXP which gives exact results, but it's a slow process.
I can't use MATCH as tables use InnoDB.
So... is or are there other ways to accomplish this?
[Edit]
I forgot to mention that the user could search for many tags (not just 2), and even exclude tags: search posts tagged 'water' but not 'blue'. With FIND_IN_SET this works for me:
HAVING FIND_IN_SET('water', tags_search) > 0
AND NOT FIND_IN_SET('blue', tags_search) > 0
[Edit2]
I did some performance test (i.e. only checked how long the queries took, cached) as ypercube suggested, and these are the results:
muists | Bill K | ypercu | includes:excludes
--------------------------
0.0137 | 0.0009 | 0.0029 | 2:0
0.0096 | 0.0081 | 0.0033 | 2:1
0.0111 | 0.0174 | 0.0033 | 2:2
0.0281 | 0.0081 | 0.0025 | 5:1
0.0014 | 0.0013 | 0.0015 | 0:2
I don't know if this info is valid resource... But it shows that ypercube's method with a JOIN per tag is the quickest.
I don't understand why you don't want to use JOINs nor why you're trying to use LEFT JOINs. You're looking for things that are there (rather than might be there) so get rid of the LEFT JOINs and just JOIN. And get rid of the tags_cache column, you're only asking for trouble with that sort of thing.
Something like this is what you're looking for:
select p.id
from posts p
join posts_tags pt on p.id = pt.post_id
join tags t on pt.tag_id = t.id
where t.name in ('water', 'blue')
group by p.id
having count(t.id) = 2
The 2 in the HAVING clause is the number of tags you're looking for.
And if you want to exclude certain tags, you could just add that to the WHERE clause like this:
select p.id
from posts p
join posts_tags pt on p.id = pt.post_id
join tags t on pt.tag_id = t.id
where t.name in ('water', 'blue')
and p.id not in (
select pt.post_id
from posts_tags pt
join tags t on pt.tag_id = t.id
where t.name in ('pancakes', 'eggs') -- Exclude these
)
group by p.id
having count(t.id) = 2
Finding posts that match all of several conditions on different rows is a common problem.
Here are two ways to do it:
SELECT p.*
FROM posts p
INNER JOIN posts_tags pt ON p.id = pt.post_id
INNER JOIN tags t ON pt.tag_id = t.id
WHERE t.name IN ('water', 'blue')
GROUP BY p.id
HAVING COUNT(DISTINCT t.name) = 2;
Or:
SELECT p.*
FROM posts p
INNER JOIN posts_tags pt1 ON p.id = pt1.post_id
INNER JOIN tags t1 ON pt1.tag_id = t1.id
INNER JOIN posts_tags pt2 ON p.id = pt2.post_id
INNER JOIN tags t2 ON pt2.tag_id = t2.id
WHERE (t1.name, t2.name) = ('water', 'blue');
Re comment and edit:
The problem with the HAVING solution is that it must perform a table-scan, searching every row in the tables. This is often much slower than a JOIN (when you have appropriate indexes).
To support tag exclusion conditions, here's how I'd write it:
SELECT p.*
FROM posts p
INNER JOIN posts_tags pt1 ON p.id = pt1.post_id
INNER JOIN tags t1 ON pt1.tag_id = t1.id AND t1.name = 'water'
LEFT OUTER JOIN (posts_tags pt2
INNER JOIN tags t2 ON pt2.tag_id = t2.id AND t2.name = 'blue')
ON p.id = pt2.post_id
WHERE t2.id IS NULL;
Avoiding using JOINs because you read it somewhere that they are bad is senseless. You must understand that a JOIN is a basic operation in relational databases, and you should use it where the job calls for it.
For your additional request, excluding some tags, you could use the next approach. It will give you all posts that have both water and blue tags but neither black, white or red:
SELECT p.*
FROM posts p
INNER JOIN posts_tags pt1 ON p.id = pt1.post_id
INNER JOIN tags t1 ON pt1.tag_id = t1.id
INNER JOIN posts_tags pt2 ON p.id = pt2.post_id
INNER JOIN tags t2 ON pt2.tag_id = t2.id
WHERE (t1.name, t2.name) = ('water', 'blue') --- include
AND NOT EXISTS
( SELECT *
FROM posts_tags pt
INNER JOIN tags t ON pt.tag_id = t.id
WHERE p.id = pt.post_id
AND t.name IN ('black', 'white', 'red') --- exclude
)

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'