Selecting rows that are included in a set from another table - mysql

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')
)
;

Related

How to return records which don't have a match in a tag relation table in MySQL?

Given the tag (ie: "animal"), how can I return the records from the objects table which don't have the tag "animal"?
Objects Table:
object_id
object_name
1
cat
2
dog
3
truck
4
car
Tags Table:
tag_id
tag
1
animal
2
vehicle
3
red
Object Tags Table:
tag_id
object_id
1
1
1
2
3
2
2
3
2
4
3
4
I'm doing something like this. However, 'dog' is still returning because it has a matching tag for 'red'.
SELECT o.*
FROM objects o
LEFT JOIN object_tags ot
ON ot.object_id = o.object_id
LEFT JOIN tags t
ON ot.tag_id = t.tag_id
AND LOWER(t.tag) = LOWER('animal')
WHERE t.label IS NULL
GROUP BY o.object_id
I'm doing something like this. However, 'dog' is still returning
because it has a matching tag for 'red'.
Based on the data example the only excluded record should be object_id=1
You could use NOT EXISTS which will exclude the records which have the tag='animal' :
select o.*
from objects o
where not exists (select 1 from object_tags ot
inner join tags t on ot.tag_id=t.tag_id
where o.object_id=ot.object_id
and t.tag='animal'
);
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=b96e6ab628cb2347838c2e8f253d0475

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';

How to find all children from another table?

I have two tables
CATEGORY
id category parent_id
1 Electronic
2 Furniture
3 Phone 1
4 LCD 1
5 Watch 1
6 Desk 2
ORDER
id customer product category_id
1 John Smartphone 3
2 Marry Montior 4
3 King Wood-Desk 6
I want to find all of electronic result by child_id.
Like this..
SELECT product FROM order WHERE category_id = (category.id = 1)
RESULT
product
Smartphone
Monitor
Is there any expression like this in MySQL?
You can use a join for this. You also will need to encapsulate the order table name with backticks because order is reserved (alternatively you could rename that table to save yourself from encapsulating everytime).
SELECT product FROM `order` as o
join category as c
on o.category_id = c.id
WHERE c.parent_id = 1
The on tells the DB what to data to join on. The as creates an alias so the full table name doesn't need to be written out everytime. (The as also is optional, I find it easier to read, FROM `order` o would be the same)
An alternative approach could be using a sub-query:
SELECT product
FROM `order`
WHERE category_id in (SELECT id FROM CATEGORY where parent_id = 1)
You have to use INNER JOIN
SELECT order.product
FROM order
INNER JOIN category
ON order.category_id = category.id
WHERE category.parent_id = 1
The ON keyword shows what columns will be compare between these tables. When you make a JOIN you need to put the table name before the column name separated with "." because it is possible to exist a column with the same name in both tables.

How to query this table?

I have the following two tables.
nodes
attributes
nodes
id title
1 test
2 test2
attributes
id node_id title value
1 1 featured 1
2 1 age 13
3 2 featured 2
I would like query nodes with attribute title 'featured' along with its all attributes.
I tried to join, but I don't know how to query other attributes at the same time.
Is it possible to make a single query to do this?
You could use a subquery to get the ID's of all nodes with the attribute 'featured'. The outer query would be the JOIN to get the rest of the attributes.
Like:
SELECT n.*, a.*
FROM nodes n JOIN attributes a ON a.node_id=n.id
WHERE n.id IN
(SELECT DISTINCT no.id
FROM nodes no JOIN attributes at ON at.node_id=no.id AND at.title='featured')
I think this is a simple join
SELECT b.title as NodeTitle, a.title, a.value
FROM attributes a
INNER JOIN nodes b
ON a.node_id = b.id

Simple MySQL joining

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