Simple MySQL joining - mysql

Having trouble understanding how to filter an images table by tag information in a second table.
So I have an images table with all the basic fields:
id |
title |
userid |
timestamp |
status
and I have a tags table:
id |
imageid |
text (contains a single tag)
What i'd like to do is simple: take a list of tags and get a list of images that have all of the tags in the list. I just can't wrap my head around it.
Any help is greatly appreciated, thanks!

SELECT
a.*
FROM
images a
INNER JOIN
tags b ON a.id = b.imageid
WHERE
b.text IN ('tag1', 'tag2', 'tag3')
GROUP BY
a.id
HAVING
COUNT(*) = 3
'tag1', 'tag2', 'tag3' is your list of input tags
The 3 in the HAVING clause is the input of the count of tags in your tag list.

Assuming you don't have duplicate tags per imageid, you can join against a query that counts the tags per id, limiting the tags with an IN() clause to the list you want to search.
Then using a HAVING clause, reject the items for which the count doesn't equal the number of tags in your list (those which have fewer than the complete list):
SELECT
images.*
FROM
images
JOIN (
SELECT
imageid,
COUNT(*) AS numtags
/* could also be COUNT(DISTINCT text) */
FROM tags
/* Put only the tags you want into an IN() clause */
WHERE text IN ('tag1,'tag2','tag3')
GROUP BY imageid
/* If an image has all 3 tags, COUNT(*) = 3 */
HAVING numtags = 3
) tlist ON images.id = tlist.imageid

Related

SQL: many to many relationships select where multiple criteria

given these tables :
id_article | title
1 | super article
2 | another article
id_tag | title
1 | great
2 | awesome
id_relation | id_article | id_tag
1 | 1 | 1
2 | 1 | 2
3 | 2 | 1
I'd like to be able to select all articles that are "great" AND "awesome" (eventually, I'll probably have to implement OR too)
And basically, if I do a select on articles the relation table joining on id_article: of course, I cant join two different values of id_tag. Only lead I had with concatenating IDs to test as a string, but that seems so lame, there has to be a prettier solution.
Oh and if it matters, I use a MySQL server.
EDIT: for ByWaleed, the typical sql select that would surely fail that I cited in my original question:
SELECT
a.id_article,
a.title
FROM articles a, relations r
WHERE
r.id_article = a.id_article and r.id_tag = 1 and r.id_tag = 2
wouldnt work because r.id_tag cant obviously be 1 and 2 on the same line. I doubt w3schools has an article on that. My search on google didnt yield any result, probably because I searched with the wrong keyword.
If you do all the joins as normal, then aggregate the rows to one group by article, then you can assert that they must have at least two different tags.
(Having already filtered to great and/or awesome, that means they have both.)
SELECT
a.id_article,
a.title
FROM
articles a
INNER JOIN
relations r
ON r.id_article = a.id_article
INNER JOIN
tags t
ON t.id_tag = r.id_tag
WHERE
t.title IN ('great', 'awesome')
GROUP BY
a.id_article,
a.title
HAVING
COUNT(DISTINCT t.id_tag) = 2
(The DISTINCT is to avoid the possibility of one article having 'great' twice, for example.)
To do OR, you just remove the HAVING clause.
One approach is to aggregate by article, and then assert that the article both the "great" and "awesome" tags:
SELECT
a.id_article,
a.title
FROM articles a
INNER JOIN relations r
ON a.id_article = r.id_article
INNER JOIN tags t
ON r.id_tag = t.id_tag
WHERE
t.title IN ('great', 'awesome')
GROUP BY
a.id_article,
a.title
HAVING
MIN(t.title) <> MAX(t.title);
Demo
The logic here is that we first limit records, for each article, to only those of the two targets tags. Then we assert, in the HAVING clause, that both tags appear. I use a MIN/MAX trick here, because if the min and max differ, then it implies that there are two distinct tags.
Step 1: Use a temp table to get all articles with titles.
Step 2: If an article occurs multiple times in your temp table, that means it has great and awesome as titles.
Try:
CREATE TEMPORARY TABLE MyTempTable (
select t1.id_article, t2.title
from table1 t1
inner join table3 t3 on t3.id_article = t1.id_article
inner join table2 t2 on t2.id_tag = t3.id_tag
)
select m.id_article
from MyTempTable m
group by m.id_article
having count(*)>1
Edit: This solution assumes there are two possible tags, great and awesome. If more, please add a "where" clause to the select query for creating the temp table like where t2.title in ('great','awesome')

Many-To-Many select only rows with exactly same tags

I have 3 tables: tags, products and relation table between them.
Relation table looks for example like this:
tagId | ProductId
1 | 1
2 | 1
2 | 9
The user can pick two options "All of these" or "One of these".
So if user picks All of these, it's means that the product must have exactly all of tags which the user chose.
So if user pick tags with id 1 and 2, it should select only product with id 1, because this product has exactly the same tags the user chose. (Another way is if the user picks the tag with id 2, it should select only product with id 9.)
So, the product has to have all tags which the user chose (no more, no less).
SQL that I already have for Any/One of these:
SELECT DISTINCT s.SKU
FROM SKUToEAN as s
LEFT JOIN ProductDetails as p ON s.ProductDetailID=p.id
JOIN ProductTagRelation as ptr ON (ptr.productId=p.id and ptr.tagId IN(Ids of selected tags))
Example behavior:
TagId = 1 it should select => None
TagId = 2 it should select => 9
TagId = 1,2 it should select = 1,9
So probably I need two queries. One for any/one of these ( I already have this one ) and the second for all of these.
With PHP I decide which query to use.
You can GROUP BY on the ProductID and use conditional aggregation based filtering inside the Having clause. MySQL automatically casts boolean values to 0/1 when using in numeric context. So, in order to have a specific tagID value available against a ProductID, its SUM(tagId = ..) should be 1.
All of these:
SELECT ptr.productId, s.SKU
FROM SKUToEAN AS s
LEFT JOIN ProductDetails AS p
ON p.id = s.ProductDetailID
JOIN ProductTagRelation AS ptr
ON ptr.productId = p.id
GROUP BY ptr.productId, s.SKU
HAVING SUM(ptr.tagID = 1) AND -- 1 should be there
SUM(ptr.tagID = 2) AND -- 2 should be there
NOT SUM(ptr.tagID NOT IN (1,2)) -- other than 1,2 should not be there
Is this you are looking for (for all condition)?
select product.id
from products
inner join <table> on products.id = <table>.productId
group by product.id
having group_concat(<table>.tagId order by <table>.tagId separator ',') = '1,2';

Selecting rows that are included in a set from another table

I have a table "Products" with a product name and id:
id | title
1 product 1
2 product 2
Each product can have a series of tags. Tags are identified in table "Attributes":
id | name | handle
1 Tag One tag-one
2 Tag Two tag-two
3 Tag Three tag-three
4 Tag Four tag-four
etc
The product to tag relationship is another table "Tags":
id | AttId | OwnerId
1 1 1
2 2 1
3 1 2
4 3 2
etc
Ok, so I am trying to select a set of products that all have at least one specific tag, and a possible selection of other tags. Here is what I am working with now:
SELECT products.id
FROM products
WHERE
EXISTS
(
SELECT 1
FROM Tags
INNER JOIN Attributes ON tags.AttId = Attributes.id
WHERE Attributes.handle = 'tag-one'
AND (
Attributes.handle = 'tag-two'
OR
Attributes.handle = 'tag-four'
)
AND products.id = Tags.OwnerId
)
If I remove the AND (OR) section, the query works. As above, it shows no errors, but also no results; How should I write this so I can get a set of products that have one tag for sure, AND have either/or other specified tags by the tag handle?
I like to approach this type of problem using group by and having -- because I find that this method works very well for expressing many different conditions. For your conditions:
select p.*
from products p join
tags t
on t.OwnerId = p.id join
attributes a
on t.attid = a.id
group by p.id
having sum(a.handle = 'tag-one') > 0 and
sum(a.handle in ('tag-two', 'tag-four')) > 0;
Each condition in the having clause counts the number of rows (for a product) that match a condition. The first says there is at least one row with 'tag-one' handle. The second says that there is at least one row with the other two handles.
I think if you perform two separate queries and take the intersection, that will give you what you want.
-- Get all the owner ids that have 'tag-one'
select OwnerId
from Tags t1
where AttId in
(
select id
from Attributes a1
where a1.handle = 'tag-one'
)
intersect
-- Get all the owner ids that have tag-two and tag-four
select OwnerId
from Tags t2
where AttId in
(
select id
from Attributes a2
where a2.handle in ('tag-two', 'tag-four')
)
;

mySQL: Selecting data from multiple tables

I need help building a mysql query to select from multiple tables.
I have three database tables related to images: images, tags, tag_asc. I want to fetch an image data and its tag names by providing image_id.
For example, following is my tables structure:
images:
image_id image_name path date
1 test.jpg dir 1311054433
tags:
tag_id image_id
1 1
2 1
tag_asc:
tag_id tag_name
1 "first"
2 "second"
3 "third"
I want to fetch the data of an image with image_id = 1 from images table and all tag names associated with image_id=1 from tag_asc table.
I'm using CodeIgniter Active records, but I just need idea of joining the tables.
Thanks for any help.
select *
from images i
left join tags t on t.image_id = i.image_id
left join tag_asc ta on ta.tag_id = t.tag_id
where i.image_id = 1;
Using LEFT JOIN means that rows will be returned even if there are no joining rows in the other tables (but you'll get null values in the columns for the missing rows), which is typically desirable.
If you want one row (not stated in question, but in comments), use this:
select i.image_id, group_concat(tag_name) as tag_names
from images i
left join tags t on t.image_id = i.image_id
left join tag_asc ta on ta.tag_id = t.tag_id
where i.image_id = 1
group by 1;

Merging MySQL row entries into a single row

I've got two tables, one for listings and another representing a list of tags for the listings table.
In the listings table the tag ids are stored in a field called tags as 1-2-3-. This has worked out very well for me (regular expressions and joins to separate and display the data), but I now need to pull the titles of those tags into a single row. See below.
listings table
id tags
1 1-2-3-
2 4-5-6-
tags table
id title
1 pig
2 dog
3 cat
4 mouse
5 elephant
6 duck
And what I need to produce out of the listings table is:
id tags
2 mouse, elephant, duck
Here is a query that could help. But since it is doing some string operations, it may not be as good as a regular join:
select l.id, group_concat( t.title )
from listings l, tags t
where concat( '-', l.tags ) like concat( '%-', t.id, '-%' ) group by l.id ;
Unfortunately, with your tags stored in this denormalized format, there's no easy way to go from 1-2-3 to the corresponding tags. In other words, there's no simple way to split out the ids, join to another table and then recombine. Your best option would be to create a listing_tag table with two columns
listing_id tag_id
1 1
1 2
1 3
2 4
2 5
2 6
and then it's just a simple join:
SELECT listing_id, GROUP_CONCAT(title SEPARATOR ', ') FROM listing_tag JOIN tags ON tags.id = tag_id
GROUP_CONCAT() + INNER JOIN + GROUP BY