MySQL: relational division - use case - mysql

Can't seem to puzzle different kind of information together to get what I want so here goes..
Three different tables:
Entities
id name
1 A
2 B
3 C
Tags
id name
1 tag_1
2 tag_2
3 tag_3
Users
id name
1 user_1
2 user_2
Both Entities:Tags and Users:Tags are 1:n relationships with both entities and users having any number of tags. There are two tables to handle those relations; entities_tags and users_tags. For example:
entities_tags
entity_id tag_id
1 1
1 2
2 1
2 2
2 3
3 1
users_tags
user_id tag_id
1 1
1 2
Now, given a user, I'd like to fetch all entities for which its tags are all included in the users tags. In my example user_1 should fetch entities A and C. I can fetch the user tags separately if needed, as shown below.
I've tried
SELECT entities.*
(SELECT GROUP_CONCAT(entities_tags.name SEPARATOR ', ')) AS tags
FROM entities
LEFT OUTER JOIN entities_tags ON entities_tags.entity_id = entities.id
LEFT OUTER JOIN tags ON tags.id = entities_tags.tag_id
WHERE tags.name IN ('tag_1','tag_2')
GROUP BY entities.id
HAVING COUNT(tags.name) = 2
But this would only give me both A and B and not C. I shouldn't get B, but I should get C.

Related

Count rows in table with certain value in other table

I need a quick way to find the Number of items in a table. The items are linked to an other table. Table 1 is products and table 2 is orders.
Orders contains a paid status (1 or 0).
Orders table example:
id paid
1 0
2 1
Products table example:
id orderid type
1 1 5
2 1 5
3 1 3
4 2 5
5 2 5
6 2 3
Products contains a id (orderid) that refers to the order and a type. So i need the number of products where type = 5 and paid = 1 in the orders table.
What is the best and fastest way to archieve this?
So I need all the paid products with type 5. The result should be '2'.
you can use join like this,
SELECT COUNT(*) AS num_rows
FROM products
LEFT JOIN orders ON orders.id = products.orderid
WHERE type = 5 AND paid = 1
One way is to use a join statement. Making some assumptions about your schema, the following should work:
SELECT COUNT(p.`id`) FROM `products_table` p
LEFT JOIN `orders_table` o ON p.`orderid` = o.`id`
WHERE o.`paid` = 1
AND p.`type` = 5

Inner join within table

I have a table categories with the following fields:
id, content, is_subcategory, topic_id
Here comes an examplatory set of data:
id content is_subcategory reference_id
=============================================
1 Games 0 989898989
2 Xbox 1 1
3 Playstation 1 1
4 Furniture 0 121212121
5 Sofa 1 4
6 Closet 1 4
7 Music 0 989898989
8 Pop 1 7
9 Reggae 1 7
Explanation:
If the category is a subcategory, its reference_id is the id of the parent category. For example, Sofa has 4 as its reference_id as 4 is the id of Furniture.
If the category is a parent category, its reference_id is the id of another table topics. For example, Music is a parent category and has 989898989 as its reference_id which is the id for the topic "entertainment".
How do I achieve that I can select only those subcategories whose parent category has 989898989 as its reference_id?
You can do just like it was 2 different tables:
select * from categories c, categories parent
where c.reference_id=parent.id and parent.reference_id=989898989 and c.is_subcategory = 1
SELECT * FROM category sub, category super WHERE
sub.reference_id = super.id AND super.reference_id = 989898989
AND sub.is_subcategory = 1
Since you alredy figured out that you need a self join for this question I am sure you can understand my solution. If not, just let me know.
Give different aliases and try
SELECT id,content FROM `categories` Child INNER JOIN
`categories` Parent ON Child.`reference_id` = Parent.`id`
WHERE Parent.reference_id=989898989 and Child.is_subcategory = 1
I like to suggest inner join for better perfomance.

Select all values from table B where matches table A

I have this schema:
items | taxonomy | subjects
| |
ID headline | item_id subject_id | subject_id subject
-------------------------------------------------------------------------
1 information | 1 1 | 1 cities
2 here we are | 2 1 | 2 towns
3 more things | 3 2 | 3 water
4 doo dah | 3 4 | 4 telephones
| 4 1 |
| 4 3 |
I would like to select a single row from "items" and with it, include all the rows from "subjects" which are joined by the "taxonomy" table. So for example, getting item.ID=3 would result in something like:
items.ID = 3
items.headline = "more things"
subjects.subject = "towns"
subjects.subject = "telephones"
I've started with this query
SELECT
i.ID,
i.headline,
s.subject_name
FROM items i
JOIN taxonomy t
on i.ID=t.item_id
JOIN subjects s
on t.subject_id=s.subject_id
WHERE i.ID = 3
But this only returns a single value from subject_name even if there are multiple values associated with that item_id.
EDIT
I actually had a LIMIT 1 on the query which was causing (as #Gordon Linoff said) only one row to be returned, even though there were multiple rows in the result set corresponding to the multiple subjects. His solution still does nicely, because I only want to return a single row.
Your query returns the subjects on multiple rows. If you want the subjects on a single row, then you need some form of concatenation:
SELECT i.ID, i.headline, GROUP_CONCAT(s.subject_name) as subjects
FROM items i JOIN
taxonomy t
ON i.ID = t.item_id JOIN
subjects s
ON t.subject_id = s.subject_id
WHERE i.ID = 3
GROUP BY i.ID, i.headline;
For one item, the GROUP BY is optional, but it is good form in case you modify the query to handle multiple items.
I would suggest you the "union all" clause (or "union", if you are not needing the duplicates).
(SELECT
"taxonomy" As Name,
i.headline As Value
FROM items i
JOIN taxonomy t
on i.ID=t.item_id
WHERE i.ID = 3)
Union All
(SELECT
"subject" As Name,
s.subject_name As Value
FROM items i
JOIN subjects s
on t.subject_id=s.subject_id
WHERE i.ID = 3)
You can add a 2nd field in each select to indicate type of item selected ("headline", "subjects", etc).

What MySQL query can return a result set that meets all criteria for example return all photos that have these 'x' keywords?

I have 3 tables:
photos: id, name
keywords: id, keyword
photo_keyword relationship table: photo_id, keyword_id
In other words one photo may be associated to multiple keywords describing the photo
photos:
id | name
1 | photo 1
2 | photo 2
3 | photo 3
keywords:
id | keyword
1 | NHL
2 | Toronto
3 | Montreal
4 | Boston
5 | Chicago
6 | Canadiens
7 | Leafs
photo_keyword:
photo_id | keyword_id
1 | 2
1 | 7
1 | 1
2 | 1
2 | 3
2 | 6
3 | 2
3 | 7
I would like to write/build a query dynamically where i can get a list of photos that meet all criteria asked for.
example: I want all photos that have Toronto (2) and Leafs (7) as keywords
ie) I want to retrieve photo 1 and photo 3 as they are the two that have both keywords
if I asked for photos that have NHL (1) Toronto (2) and Leafs (7) as criteria, only photo 1 would be returned.
The obvious first attempt involved joining the tables:
SELECT photo.id, photo.name
FROM photo
LEFT JOIN photo_keyword ON photo_keyword.photo_id = photo.id
LEFT JOIN keyword ON keyword.id = photo_keyword.keyword_id
WHERE keyword.keyword = 'Toronto'
returns photos 1 and 2
This type of query can be used whenever i am searching for all photos meeting 1 keyword criteria. However,
trying to get photos that meet the criteria of two keywords is a different story.
I tried
WHERE keyword.keyword IN ('NHL, 'Toronto', 'Leafs')
but that returns photos that meet either criteria but no necessarily both. In this case photo 1 and 2 as well.
So how do I build a query that only returns only the photos that meets all keyword criteria?
Any help on this would be immensely appreciated!
Update:
With everyone's comments this is what I put together and seems to do what I require:
SELECT
photos.id
FROM photos
LEFT JOIN photo_keyword pk ON pk.photo_id = photos.id
LEFT JOIN keywords kw ON kw.id = pk.keyword_id
WHERE kw.keyword IN ('NHL', 'Toronto', 'Leafs')
GROUP BY photos.id
HAVING COUNT(kw.keyword) = 3;
If you know keywords cannot be repeated for a given photo, I think you could accomplish this with something like:
SELECT photo.name from photo where photo.id IN (
SELECT photo.id
FROM photo
LEFT JOIN photo_keyword ON photo_keyword.photo_id = photo.id
LEFT JOIN keyword ON keyword.id = photo_keyword.keyword_id
WHERE keyword.keyword IN ('NHL, 'Toronto', 'Leafs')
GROUP BY (photo.id)
HAVING COUNT (photo.name) = *Number of passed keywords*)
Could also do this with UNIONS, etc. but a lot of this depends on what you know about your keywords and how you are getting them.
Try this query -
SELECT p.id, p.name FROM photo_keyword pk
JOIN keywords k
ON k.id = pk.keyword_id
JOIN photos p
ON p.id = pk.photo_id
GROUP BY
pk.photo_id
HAVING
COUNT(IF(k.keyword = 'Toronto', 1, NULL)) > 0 AND
COUNT(IF(k.keyword = 'Leafs', 1, NULL)) > 0

How to find all products with some properties?

I'm listing product properties in a MySQL table where each row contains a product ID prod and a property ID prop. If a product has three properties, this results in three rows for that product. Example table:
prod | prop
-----+-----
1 | 1
2 | 1
2 | 2
2 | 3
3 | 2
3 | 4
How can I find which products have both properties #1 and #2 (product #2)?
The only way that I can think of is one select and inner join per property, but I think that would be very inefficient. It's a search function for a website and has to work for 10k lines in the table and 10 requested properties.
SELECT prod
FROM tbl
WHERE prop IN (1, 2)
GROUP BY prod
HAVING COUNT(*) = 2
And if there will be always 2 properties to find - then INNER JOIN would be a bit more efficient:
SELECT t1.p
FROM tbl t1
INNER JOIN tbl.t2 ON t2.prod = t1.prod
AND t2.prop = 2
WHERE t1.prop = 1
The recommended index for this query to be efficient as much as possible is a compound one (prop, prod)