Many To Many join with additional where - mysql

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.

Related

MySql Join and count number of records

I have two tables
tbl_groups:
id | name
----------------
1 | BSCS
2 | BSIT
3 | BBA
tbl_students:
id | name | group_id
-------------------------------
1 | Student Name | 1
2 | Student 2 | 1
3 | Student 3 | 2
I want to show groups details: group name and number of students in a particular group,
I am using this query but it shows groups that has students. it does not show group with 0 students.
select tb2.id, tb2.name, count(*) from tbl_students tb1 JOIN tbl_groups tb2 ON tb1.group_id = tb2.id
How do I show all groups, please give me some idea
EDIT:
if I use above query I get following result:
id | name | count(*)
-------------------------------
1 | Student Name | 2
2 | BSIT | 1
(it doest show 3rd group because there are 0 students, I want to show this groups also).
Just use a left join:
select tb2.id, tb2.name, count(tb1.id) as no_std
from tbl_groups tb2
LEFT JOIN tbl_students tb1 ON tb2.id = tb1.group_id
group by tb2.id, tb2.name
See it working live here: http://sqlfiddle.com/#!9/2282a3/5
I would just use a correlated subquery to get the count of students in each group, like so:
select
g.*,
(select count(*) from tbl_students s where s.group_id = g.id) no_students
from tbl_groups g
This does not filter out groups that have no students (it will give a count of 0 instead). And with an index on tbl_students(group_id), this should be as efficient as it gets (this index is already there if you set up a foreign key constraint on that column - as you should have).

Join with Group By & Order by using Case in MySQL

Why this query wont work? Is it beacause combinaton of order by and group by?
One table is with adverts, other with subscriptions, third is with services, and fourth is many to many relation between services and locations (location is position where advert should be shown).
What i want is to order adverts stored in adverts table having location 2 first, then those who don't have location defined and then with location 1 (this order is generated programmicaly)
adverts table:
id, name, subscription_id
subscriptions table:
subscription_id, service_id, date, paid etc...
service_locations table:
service_id, location_id
as you can se there is fourth table in this case, but it is unimportant
The query:
select adverts.id, GROUP_CONCAT(service_locations.location_id) AS locations from adverts
left join subscriptions
on adverts.subscription_id = subscriptions.id
left join service_locations
on service_locations.service_id = subscriptions.service_id
group by adverts.id
order by case service_locations.location_id
when 2 then 1
when 1 then 3
else 2
end
Expected results:
+----+-----------+
| id | locations |
+----+-----------+
| 1 | 2 |
| 3 | 1,2 |
| 2 | null |
+----+-----------+
What i actually get (the third in row has location 2 but it is placed after null):
+----+-----------+
| id | locations |
+----+-----------+
| 1 | 2 |
| 2 | null |
| 3 | 1,2 |
+----+-----------+
When you use group by, all columns not in the group by should have aggregation functions. So, I think you intend something like this:
select a.id, GROUP_CONCAT(sl.location_id) AS locations
from adverts a left join
subscriptions s
on a.subscription_id = s.id left join
service_locations sl
on sl.service_id = s.service_id
group by a.id
order by max(case sl.location_id
when 2 then 1
when 1 then 3
else 2
end);
I'm not sure if max() is what you really need, but you do need an aggregation function. This specifically produces the output in the question:
order by (case min(sl.location_id)
when 2 then 1
when 1 then 2
else 3
end);
I have found a solution, order by must be executed before group by, which is not a default behaivor, more about that behaivour here: https://stackoverflow.com/a/14771322/4329156) (a subquery must be used)
So, query should look like
select *, GROUP_CONCAT(location_id) as locations from (
select adverts.id AS id, service_locations.location_id AS location_id from adverts
left join subscriptions
on adverts.subscription_id = subscriptions.id
left join service_locations
on service_locations.service_id = subscriptions.service_id
order by case service_locations.location_id
when 2 then 1
when 1 then 3
else 2
end
) as table
group by table.id
order by case table.location_id
when 2 then 1
when 1 then 3
else 2
end

Select from 2 table SQL WHERE Clause by ID

I have 2 tables in MySQL, in both of these tables I have merchant_id, merchant, branch and some another fields, the name of one table is merchant and another table is product.
tbl_merchant :
id | merchant_id | merchant_name | branch | ...
------+---------------+--------------------+----------------+
1 | 1001 | McDonalds | branch 1 mcd | ...
2 | 2002 | KFC | branch 1 kfc | ...
tbl_product :
id | product_id | product_name | price | merchant_id
------+---------------+-----------------+---------+-------------
1 | 100101 | Chicken | 10 | 1001
2 | 100102 | Potato | 5 | 1001
3 | 100101 | Burger | 10 | 2002
4 | 100102 | Fish Fillet | 10 | 2002
I want to know how can to show merchant_name, branch from both tables using SQL WHERE Clause by product_id = 100101 and merchant_id = 1001 ?
Like this :
Result :
id | merchant | branch | product_name | price
------+-------------+----------------+---------------+-------
1 | McDonalds | branch 1 mcd | Chicken | 10
Thank You
First, I'll show you the query, then I'll explain each part line by line to help you understand:
SELECT
merchant_name, branch
FROM
tbl_merchant INNER JOIN tbl_product ON (tbl_product.merchant_id = tbl_merchant.merchant_id)
WHERE
product_id = 100101 AND merchant_id = 1001
Alright, so if we look at the first part following the select, it should be clear that the two columns that will be printed out are merchant_name and branch. Based on your output, you can print out any field from either table just by adding its name to the list. If the field has the same name in both tables, then you need to qualify it like this:
SELECT
tbl_merchant.id, tbl_product.id
FROM
tbl_merchant INNER JOIN tbl_product USING(merchant_id)
The tricky part of this query is the line that joins the two tables together. Essentially what you have as of now is two tables that are linked together by a merchant id, which makes sense because 1 merchant can have many products (i.e. a 1 to many relationship). I'm assuming that the merchant ID is unique. The join then pairs together all the rows that have the same merchant_id (which is unique in one of the tables and therefore guaranteed to be correct). More specially you can think of it as a qualified cross product where each tuple from tbl_product is joined with each tuple from tbl_merchant and then qualified based on the condition tbl_product.merchant_id = tbl_merchant.merchant_id.
The last part of the query (WHERE clause) simply eliminates rows based on the conditions provided.
The query for this is:
SELECT merchant_name, branch
FROM
tbl_merchant
INNER JOIN
tbl_product
ON (tbl_product.merchant_id = tbl_merchant.merchant_id)
WHERE
product_id = 100101 AND merchant_id = 1001
SELECT merchants.id, merchants.merchant_id, merchants.branch, products.product_name, products.price
FROM merchants
INNER JOIN products ON products.merchant_id = merchants.merchant_id
WHERE merchants.merchant_id = 1001 AND products.product_id = 100101
you can use JOIN to solve this type of query
there are some good article to learn more about JOIN with visula explanation::
1) http://blog.codinghorror.com/a-visual-explanation-of-sql-joins/ "A Visual Explanation of SQL Joins"
2) http://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins "Visual Representation of SQL Joins"
Try this:
select * from marchant join product on marchant.id=product.merchant_id where merchant_id = 1001
This statement will join both tables together where the primary key form merchant is equals the merchant_id in product.

Produce a list of people that have more than one item

Say I have a table of people...
person:
-----------
id | person
---+-------
1 | Jim
2 | Bob
3 | Frank
...and I have a table of items...
item:
----------------
id | item | type
---+------+-----
1 | 21 | 2
2 | 10 | 5
3 | 11 | 1
4 | 9 | 1
...and I also have a table describing who has what...
person_item:
-------------
item | person
-----+-------
1 | 2
2 | 1
3 | 1
How can I create a single query that will tell me when an individual has more than one item of a particular type? I only want the query to concern itself with items of type (1, 2, 3).
The results from the query should be in the following format:
---------------
person | item
| item
--------+------
person | item
| item
| item
--------+------
... etc.
This is what I have tried... but it produces garbage...
SELECT person.id, item.id FROM person_item AS pi
JOIN item AS i ON i.id = pi.item
JOIN person AS p ON p.id = pi.item
WHERE item.type IN (1,2,3)
HAVING COUNT(pi.person) > 1;
The query is suspect because you have a having clause but not a group by clause. Also, you are using table names when you have very reasonable aliases. And, you want to count distinct items within a person/type combination, not just for a person.
Taking these into account, try this query:
SELECT p.id, i.type, group_concat(i.item) as items
FROM person_item pi join
item i
ON i.id = pi.item join
person p
ON p.id = pi.person
WHERE i.type IN (1,2,3)
group by p.id, i.type
HAVING COUNT(distinct i.id) > 1;
This also provides the list of items as the third things returned.
If you only want to see person and item id's, you don't need to join to person - just access person_item (with a link to item for item_type). However, if you want each combination on a separate line, you will have to access person_item twice - like so:
select pi.person, pi.item
from person_item pi
join (select p.person
from person_item p
join item i on p.item = i.item_id and i.type in (1,2,3)
group by p.person
having count(*) > 1) c
on pi.person = c.person

Exclusive mysql select query, two tables

I have the following tables (they all got more columns but I'm just showing the ones of interest):
Product Order details Orders
---------------------------- ---------------------------- --------------
| id_product | id_supplier | | id_order | id_product | | id_order |
| 12 | 2 | | 1 | 56 | | 1 |
| 32 | 4 | | 2 | 32 | | 2 |
| 56 | 2 | | 2 | 56 | | 3 |
| 10 | 1 | | 4 | 56 | | 4 |
---------------------------- | 3 | 12 | --------------
----------------------------
What I want to do is select all orders which have products from ONLY one or more suppliers. So lets say I want all orders that only have products from the supplier with id 2 (id_supplier = 2) I should get the orders with id 1, 3 and 4.
If I want all orders that ONLY have products from the supplier with id 4 (id_supplier = 4) I should get an empty result.
If I want all orders that ONLY have products from the suppliers with id 2 AND 4 I should get the order with id 2.
I've read the following question: mySQL exclusive records but I can't get a grip of that query to work when I have two tables like I have. I just need another pair of eyes to help me out here! :)
Do you have any idea on how I'll do this?
EDIT: To clearify, I want to fetch all orders that ONLY contains products from one or more specified suppliers. Orders with products from other suppliers than is specified, should not be included.
per the questions I've listed, I think THIS is what you want, and can be done with a LEFT join.
select
od.id_order,
sum( if( p.id_supplier in ( 2, 4 ), 1, 0 )) as HasSupplierLookingFor,
sum( if( p.id_supplier in ( 2, 4 ), 0, 1 )) as HasOtherSuppliers
from
order_Details od
join product p
on od.id_product = p.id_product
group by
od.id_order
having
HasSupplierLookingFor > 0
AND HasOtherSuppliers = 0
Sometimes, just answering a question that can be somewhat ambiguous as presented leads to misrepresented answers. This query will by a per order basis, join to the products to find the suppliers and group by the order id.
For each product ordered, the first SUM() asks if its one of the suppliers you ARE looking for, if so, sum a value of 1, otherwise 0... The next SUM() asks the same thing... but if it IS the supplier, use zero, thus all OTHER suppliers gets the 1.
So, now, the HAVING clause is looking for any order that at a minimum of 1 of your suppliers qualified AND it had no other suppliers represented.
So you could have an order with 30 items, and 20 from supplier 2, and 10 from supplier 4. The HasSupplierLookingFor would = 30, and HasOtherSuppliers = 0, the order would be included.
Another order could have 5 items. One from supplier 2, and 4 others from supplier 9. This would have HasSupplierLookingFor = 1, and HasOtherSuppliers = 4, thus exclude this as a qualified order.
You should inner join all those tables, like this:
SELECT o.* from Orders o
INNER JOIN Details d ON o.id_order = d.id_order
INNER JOIN Products p ON d.id_product = p.id_product
WHERE p.id_supplier = 4
That will give you the orders which include products from that supplier.
SELECT o.id_order
FROM Orders o
INNER JOIN `Order details` od
ON o.id_order = od.id_order
INNER JOIN Product p
ON p.id_product = od.id_product
WHERE p.id_supplier IN (2,4)
the (2,4) are the suppliers you want to fetch. you can also ask for only 1 by saying (2)