mysql querying a many-many relationship table - 'relational divison'? - mysql

given the following table (describing a many-to-many relation):
ID | PageID | TagID
--------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
4 | 2 | 3
how do i select 'all PageIDs having all of a list of TagIDs'?
in other words: i want all pages tagged with TagIDs 1 and 2 (so it's only PageID 1 in this example)
after some googling i found the term 'relational division' which might be relevant here, but i didn't quite get it. anyone having a simple query for my problem?

If you have the list of tagids in a table, then it is a simple join and group by:
select pageId
from t join
list l
on t.tagId = l.tagId cross join
(select count(*) cnt from list l) as const
group by pageId
having count(*) = max(cnt)
The having clause checks that all tags are present. If there might be duplicates, then you would want to replace the "count(*)" with "count(distinct tagid)" in both cases.

Related

How to get the column value based on multiple rows?

I want to check rows where uid equal both 1 and 2 and if they do, return cid. In this example there will only ever be 2, but if you know a way to return the CID for more than 2, that would be great too.
How can I most easily get the value where cid = 5 when I know both uid values? (1,2).
cid | uid |
------------
5 | 1 |
5 | 2 |
6 | 1 |
6 | 3 |
7 | 1 |
7 | 4 |
For pseudo sql, I am thinking something like SELECT cid WHERE uid = 1 or uid = 2
This returns all rows where uid has a 1 or a 2. How can I limit to an OR statement and an AND?
SELECT cid WHERE uid = 1 AND uid = 2 (but in multiple rows)
Any ideas?
As far as i understand you're looking for a way to apply a condition to multiple rows, a way to do that is through agrupation functions. try this:
Select CID
from YourTable where uid IN (1,2)
group by cid
having count(uid) = 2
in this example i'm using IN instead of two OR and i'm grouping the rows by CID, and after that i'm limiting the results to those rows that match with UID equals to 1 and 2.
There are many tricky ways of achieve the same result, for example you can also do something like:
Select CID
from YourTable where uid IN (1,2)
group by cid
having sum(uid) = 3
in this example i'm suming the UID column, if UID is 1 and 2 the sum of both will result on 3, I assume that you can't have 3 rows with the UID 1 and the same CID
According to given details, Try this. Let's say you have a table called docs;
SELECT d1.cid
FROM docs AS d1
LEFT JOIN docs AS d2 ON d1.cid = d2.cid
WHERE d1.uid = 1
AND d2.uid = 2

MYSQL - Order a table by another table

I have a problem
I have two tables
The table "Memes"
id imglink name
----------------------------------
1 img.Png Polite cat
2 img2.png Crying cat
And the table "Vote"
id idmeme vote
---------------------
1 1 5
2 1 2
3 2 4
So basically the table "meme" contains memes with their image and their name
And the table "votes" contains the notes on 5 that users assign to the memes
I would like my sql query to rank by the same the highest rated with the highest rating
I already look at other topic but the problem is that for each vote with the id of the same it duplicates in the result of the SELECT *
thank you in advance
One method is to use a subquery right in the order by:
select m.*
from memes m
order by (select max(v.vote) from vote v where v.idmeme = m.id);
Of course, you can also include this in the from clause (as an aggregation query) and use a join.
The most efficient way is to use a query that returns all the maximum votes from Vote and join it to the table:
select m.*
from Memes m left join (
select idmeme, max(vote) vote
from Vote
group by idmeme
)v on v.idmeme = m.id
order by v.vote desc, m.name
See the demo.
Results:
| id | imglink | name |
| --- | -------- | ---------- |
| 1 | img.Png | Polite cat |
| 2 | img2.png | Crying cat |

Many To Many join with additional where

I think I have a somewhat trivial question but I can't figure out how this works. I have the following Companies and Products tables with a simple Many-To-Many relationship.
How would I have to extend this query, so that the results just contains let's say all companies which have products with id 1 AND 2?
I tried adding wheres and havings wherever I could imagine but all i could get was all companies which have products with id x (without the additional and)
Companies Table
id | name
-----------------
1 | Company 1
2 | Company 2
3 | Company 3
Companies_Products Table
id | product_id | company_id
----------------------------
1 | 1 | 1
2 | 2 | 1
3 | 3 | 1
4 | 1 | 2
5 | 1 | 3
6 | 2 | 3
Products Table
id | name
-----------------
1 | Product A
2 | Product B
3 | Product C
Statement
SELECT companies.name,
companies.id AS company_id,
products.id AS product_id
FROM companies
LEFT JOIN company_products
ON companies.id = company_products.company_id
INNER JOIN products
ON company_products.product_id = products.id
If you want ALL companies with associated products 1 and 2, you can write this query:
SELECT c.name,
c.id AS company_id
FROM companies c
WHERE (SELECT COUNT(*)
FROM company_products cp
WHERE cp.company_id = c.id
AND cp.product_id in ('1', '2')
) = 2
Go to Sql Fiddle
If you want to know informations about associated product in the main query so you must use a join in addition of existing query.
Maybe you could using the following subquery in your query:
SELECT company_id, count(*) as no_companies
FROM Companies_Products
WHERE product_id IN (1, 2)
HAVING count(*) = 2
(In this case company an product must be coupled only once.) It returns all the company_ids with product 1 and 2.
There always some discussion about subquery's and performance, but I don't think you will notice.
You could make this function flexible by using a array.
pseudo code:
$parameter = array(1, 2);
...
WHERE product_id IN $parameter
HAVING count(*) = count($parameter)
Please say so if you need more help.

group mysql count values in comma separated field

I have a users table with columns: user_id, mechanic_id
and
mechanics table with id
I would like to count how many users have the same mechanic.
Users table
+-------------------------+
| user_Id mechanic_id |
+-------------------------+
| 1 1,2 |
| 2 2,1 |
| 3 2,1,8,16 |
| 4 1,16,3 |
+-------------------------+
mechanics table
+------+
| id |
+------+
| 1 |
| 2 |
| 3 |
...
Count for $id1 is: 4
Count for $id2 is: 3
Count for $id3 is: 1
Count for $id8 is: 1
Count for $id16 is: 2
Best solution: scrap this table design and rebuild with a properly normalized once. Then a simple join + group by + count query will work.
Worst solution: use MySQL's find_in_set() function:
SELECT mechanics.id, COUNT(user_ID)
FROM mechanics
LEFT JOIN users ON (FIND_IN_SET(mechanics.id, users.mechanic_id) > 0)
GROUP BY mechanics.id
I don't know why I am violating the basic principles of database normalization...Each user has usually one mechanic or max 2 or 3, so that's why I decided to store data in users table.
I found solution based on #Marc B:
SELECT count(*) FROM users a
INNER JOIN mechanics b
ON (FIND_IN_SET(b.id, a.mechanic_id) > 0)
WHERE b.id = '{$id}'
group by b.id
SELECT COUNT(user_Id)
FROM users, mechanics
WHERE mechanics.id IN (users.mechanic_id)

mysql query for items with multiple conditions (item options)

What I like to achieve is
a: display all Items that are in all of the selected category's
b: return / update the category list with category's available based on selection
I like items to be stored and be found by use of the adjacency list model or nested sets.
I've experimented with both and may use advice what would be the best for this case.
Currently I'm using (testing with) the adjacency list model like this:
items:
ID | item_name
====================
1 | car
2 | boat
3 | bike
items_cats: (many to many)
iid | cid
====================
1 | 1
1 | 2
1 | 4
1 | 7
2 | 1
2 | 3
2 | 4
2 | 7
3 | 1
3 | 3
3 | 4
3 | 8
categorys:
ID | cat_name | parent_id
========================
1 | safety: | 0 (0 = no parent)
2 | safe | 1
3 | dangerous | 1
4 | fun: | 0
5 | a bit | 5
6 | boring | 5
7 | funny | 5
8 | cool | 5
So its no problem to get items based on cid but how would you:
1st: selection:
1- Display all items who have cat id: cid 7 (funny)?
2- return (array/object) of all category's who have items that also contain cid 7?
Would you all do this in one query or would two be more efficient?
2nd: selection:
3- Display all items who have cat id: cid 7 and also contain cat id '3' (dangerous)
4- return (array/object) of all category's who have items that contain cid 7 and cid 3?
For selecting on multiple category's I found the flowing solution. Is this a good one and would there be to gain any performance especially when the number of category's grow?
SELECT
DISTINCT t1.product_id, t1.category_id
FROM
items_cats t1
INNER JOIN
items_cats t1b
ON t1.iid =t1b.iid
WHERE
t1.cid=3 AND
t1b.cid=7
To get a list of all items that have category ID = 7, start with your many:many table
select
i.item_name
from
items_cat ic
join items i
on ic.iid = i.id
where
ic.cid = 7
to get all categories associated with any item that has the category ID of 7, you can expand from the first and get categories associate for those item IDs
select DISTINCT
ic2.cid,
c.cat_name,
coalesce( CatParent.cat_name, "" ) as ParentCategoryName
from
( select distinct ic.iid
from items_cat ic
where ic.cid = 7 ) QualifiedItems
JOIN items_cat ic2
on QualifiedItems.iid = ic2.iid
JOIN categorys c
on ic2.cid = c.id
LEFT JOIN categorys CatParent
on c.parent_id = CatParent.ID
For 3 and 4, it would be similar, but to qualify BOTH (or anytime, more than one), you need to apply an OR, a GROUP BY and make sure that the final count matches those you were trying to qualify
select
i.item_name
from
items_cat ic
join items i
on ic.iid = i.id
where
ic.cid in( 3, 7 )
group by
i.item_name
having
count(*) = 2
So you can better understand and apply these principles, I'll leave the last one for you to try and implement... If you really get stuck, let me know... :)