How to find all products with some properties? - mysql

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)

Related

MySQL query - select from one, count from another table

I have database with two tables - 'Warehouses' and 'Boxes'.
Each box has field with warehouse code, each Warehouse - 'capacity' field.
The purpose is to find only Warehouses that are "overfilled" (capacity of warehouse is less then number of all boxes with this warehouse code).
So, I count all boxes and join warehouse capacity by this query:
SELECT Warehouses.Code, Warehouses.Capacity, COUNT(Boxes.Code)
FROM `Warehouses` RIGHT JOIN
`Boxes`
on Warehouses.Code = Boxes.Warehouse
GROUP BY Boxes.Warehouse
Result:
------------------------------
Code | Capacity | COUNT
------------------------------
1 | 3 | 4
------------------------------
2 | 4 | 2
------------------------------
3 | 7 | 2
------------------------------
4 | 2 | 1
------------------------------
That returns me warehouse's capacity and counts boxes in it, but I don't know how and where to compare these numbers.
You do this in a HAVING clause:
SELECT w.Code, w.Capacity, COUNT(b.Code)
FROM `Warehouses` w LEFT JOIN
`Boxes` b
on w.Code = b.Warehouse
GROUP BY w.Code, w.Capacity
HAVING w.Capacity < COUNT(b.Code);
Notes:
LEFT JOIN is generally much easier to understand than RIGHT JOIN ("Keep all rows in the first table" versus "keep all rows in the last table, which I haven't read yet"). However, this query probably only needs an INNER JOIN.
Presumably, Warehouses should be the first table, because your question is about this entity.
The HAVING clause does the comparison after the aggregation.

MySQL intersection of two tables

I need to implement a function which returns all the networks the installation is not part of.
Following is my table and for example if my installation id is 1 and I need all the network ids where the installation is not part of then the result will be only [9].
network_id | installation_id
-------------------------------
1 | 1
3 | 1
2 | 1
2 | 2
9 | 2
2 | 3
I know this could be solved with a join query but I'm not sure how to implement it for the same table. This is what I've tried so far.
select * from network_installations where installation_id = 1;
network_id | installation_id
-------------------------------
1 | 1
2 | 1
3 | 1
select * from network_installations where installation_id != 1;
network_id | installation_id
-------------------------------
9 | 2
2 | 2
2 | 3
The intersection of the two tables will result the expected answer, i.e. [9]. But though we have union, intersect is not present in mysql. A solution to find the intersection of the above two queries or a tip to implement it with a single query using join will be much appreciated.
The best way to do this is to use a network table (which I presume exists):
select n.*
from network n
where not exists (select 1
from network_installation ni
where ni.network_id = n.network_id and
ni.installation_id = 1
);
If, somehow, you don't have a network table, you can replace the from clause with:
from (select distinct network_id from network_installation) n
EDIT:
You can do this in a single query with no subqueries, but a join is superfluous. Just use group by:
select ni.network_id
from network_installation ni
group by ni.network_id
having sum(ni.installation_id = 1) = 0;
The having clause counts the number of matches for the given installation for each network id. The = 0 is saying that there are none.
Another solution using OUTER JOIN:
SELECT t1.network_id, t1.installation_id, t2.network_id, t2.installation_id
FROM tab t1 LEFT JOIN tab t2
ON t1.network_id = t2.network_id AND t2.installation_id = 1
WHERE t2.network_id IS NULL
You can check at http://www.sqlfiddle.com/#!9/4798d/2
select *
from network_installations
where network_id in
(select network_id
from network_installations
where installation_id = 1
group by network_id )

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).

How can I join these MYSQL tables?

I'm having 2 tables. Table A contains a list of people who booked for an event, table B has a list of people the booker from table A brings with him/her. Both tables have many colums with unique data that I need to do certain calculations on in PHP , and as of now I do so by doing queries on the tables with a recursive PHP function to resolve it. I want to simplify the PHP and reduce the amount of queries that come from this recursive function by doing better MYSQL queries but I'm kind of stuck.
Because the table has way to many columns I will give an Excerpt of table A instead:
booking_id | A_customer | A_insurance
1 | 134 | 4
Excerpt of table B:
id | booking_id | B_insurance
1 | 1 | 0
2 | 1 | 1
3 | 1 | 1
4 | 1 | 3
The booking_id in table A is unique and set to auto increment, the booking_id in table b can occur many times (depending on how many guests the client from table A brings with him). Lets say I want to know every selected insurance from customer 134 and his guests, then I want the output like this:
booking_id | insurance
1 | 4
1 | 0
1 | 1
1 | 1
1 | 3
I have tried a couple of joins and this is the closest I've came yet, unfortunately this fails to show the row from A and only shows the matching rows in B.
SELECT a.booking_id,a.A_customer,a.A_insurance,b.booking_id,b.insurance FROM b INNER JOIN a ON (b.booking_id = a.booking_id) WHERE a.booking_id = 134
Can someone point me into the right direction ?
Please note: I have altered the table and column names for stackoverflow so it's easy for you guys to read, so it's possible that there is a typo that would break the query in it right now.
I think you need a union all for this:
select a.booking_id, a.insurance
from a
where a.a_customer = 134
union all
select b.booking_id, b.insurance
from a join
b
on a.booking_id = b.booking_id
where a.a_customer = 134;
The simplest way I can think of to achieve this is to use a UNION:
SELECT booking_id, A_insurance insurance
FROM A
WHERE booking_id = 134
UNION
SELECT booking_id, B_insurance insurance
FROM B
WHERE booking_id = 134
As my understanging of your isso is right, that should give you the result you need:
SELECT a.booking_id,a.insurance FROM a WHERE a.booking_id = 134
union
SELECT a.booking_id,b.insurance FROM b INNER JOIN a ON (b.booking_id = a.booking_id) WHERE a.booking_id = 134

SQL use of conditional AND on a join table to ensure at least two or multiple values are matched

I have a join table named languages_services that basically joins the table services and languages.
I need to find a service that is able to serve both ENGLISH (language_id=1) and ESPANOL (language_id=2).
table languages_services
------------------------
service_id | language_id
------------------------
1 | 1
1 | 2
1 | 3
2 | 1
2 | 3
With the data provided above, I want to test for language_id=1 AND language_id=2 where the result would look like this
QUERY RESULT
------------
service_id
------------
1
Obviously it doesn't return the one with service_id=2 because it doesn't service Espanol.
Any tips on this is greatly appreciated!
SELECT
service_id
FROM
language_services
WHERE
language_id = 1
OR language_id = 2
GROUP BY
service_id
HAVING
COUNT(*) = 2
Or...
WHERE
lanaguage_id IN (1,2)
GROUP BY
service_id
HAVING
COUNT(*) = 2
If you're always looking at 2 languages you could do it with joins, but the aggregate version is easier to adapt to differing numbers of language_ids. (Add an OR, or add an item to the IN list, and change the COUNT(*) = 2 to COUNT(*) = 3, etc, etc).
Be aware, however, that this scales very poorly. And with this table structure there isn't much you can do about that.
EDIT Example using a join for 2 languages
SELECT
lang1.service_id
FROM
language_services AS lang1
INNER JOIN
language_services AS lang2
ON lang1.service_id = lang2.service_id
WHERE
lang1.language_id = 1
AND lang2.language_id = 2