SQL - Get rows with one result and selected option - Relation Table - mysql

I have a table that has the relations of products and colors. Each product has one or multiple colors. Is it possible to do a query that returns only the products that have one color only and the color wanted ?
Value from the api : color_slug = white ;
Sample table :
color_table
+----------+------------+
| color_id | color_slug |
+----------+------------+
| 1 | white |
| 2 | blue |
| 3 | black |
| 4 | green |
| 5 | red |
| 6 | yellow |
+----------+------------+
product_table
+------------+--------------+
| product_id | product_name |
+------------+--------------+
| 1 | shoes |
| 2 | shorts |
| 3 | t-shirt |
| 4 | jacket |
| 5 | watch |
| 6 | glasses |
+------------+--------------+
pc_relation
+----+------------+----------+
| id | product_id | color_id |
+----+------------+----------+
| 1 | 1 | 5 |
| 2 | 1 | 1 |
| 3 | 2 | 1 |
| 4 | 2 | 4 |
| 5 | 2 | 3 |
| 6 | 3 | 2 |
| 7 | 4 | 1 |
| 8 | 5 | 5 |
| 9 | 5 | 6 |
| 10 | 6 | 1 |
+----+------------+----------+
Select unique color values (if i put WHERE color_id = 1 the product colors are not longer of one color only) :
SELECT product_id
FROM pc_relation
// WHERE color_id = 1
GROUP BY product_id
HAVING MIN(color_id) = MAX(color_id)
pc_relation.id = 6,7,10
SELECT *
FROM color_table
INNER JOIN pc_relation ON pc_relation.color_id = color_table.color_id
INNER JOIN product_table ON pc_relation.product_id = product_table.product_id
WHERE colors.color_slug = 'white'
Values wanted (color_slug = white):
pc_relation.id = 7,10
product_table.product_name = jacket, glasses
*all the combinations are unique and indexed. For example I cannot have one product with the same color
twice.

You are on the right track with your first query. Move the color comparison to the HAVING clause:
SELECT product_id
FROM pc_relation
GROUP BY product_id
HAVING MIN(color_id) = MAX(color_id) AND
MIN(color_id) = 1;
You can also phrase this using NOT EXISTS:
select r.*
from pc_relation r
where r.color_id = 1 and
not exists (select 1
from pc_relation r2
where r2.product_id = r.product_id and r2.color_id <> r.color_id
);
However, the GROUP BY method is more general.

Related

MySQL - custom, additional columns based on foreign keys with other table

I have the following database schema:
Table: products
| id | name | content |
|----|--------|---------|
| 1 | Pen | ... |
| 2 | Pencil | ... |
| 3 | Rubber | ... |
| 4 | Ruler | ... |
Table: feature_types
| id | name |
|----|----------|
| 1 | Color |
| 2 | Material |
| 3 | ... |
| 4 | ... |
Table: features
| id | product_id | feature_type_id | value |
|----|------------|-----------------|-----------|
| 1 | 1 | 1 | Red |
| 2 | 1 | 2 | Aluminum |
| 3 | 2 | 1 | Green |
| 4 | 2 | 2 | Wood |
| 5 | 3 | 1 | White |
| 6 | 4 | 2 | Plastic |
My question is how can I do something like this:
SELECT *, ... FROM products ...
With result:
| id | name | content | feature_type_1 | feature_type_2 |
|----|--------|---------|----------------|----------------|
| 1 | Pen | ... | Red | Aluminum |
| 2 | Pencil | ... | Green | Wood |
| 3 | Rubber | ... | White | NULL |
| 4 | Ruler | ... | NULL | Plastic |
So as you see, in results we have all columns from products table and additional columns for specified feature_types. Column names correspond to their identifiers, according to the pattern: "feature_type_{ID}".
I know feature_types IDs so it is not necessary to add all possible columns feature_types. I need only 2 additional columns with ID 1 and 2.
If you are only interested in the features: Color and Material, join the tables and group by product:
select
p.id, p.name, p.content,
max(case when t.name = 'Color' then f.value end) Color,
max(case when t.name = 'Material' then f.value end) Material
from products p
left join features f on f.product_id = p.id
left join feature_types t
on t.id = f.feature_type_id and t.name in ('Color', 'Material')
group by p.id, p.name, p.content
I guess in your sample data you did a mistake by setting 1 instead of 2 as feature_type_id for Plastic in the table features.
See the demo.
Results:
| id | name | content | Color | Material |
| --- | ------ | ------- | ----- | --------- |
| 1 | Pen | ... | Red | Aluminium |
| 2 | Pencil | ... | Green | Wood |
| 3 | Rubber | ... | White | |
| 4 | Ruler | ... | | Plastic |
Here's one solution to the part of the problem with which you are struggling...
SELECT product_id
, MAX(CASE WHEN feature_type_id = 1 THEN value END) feature_type_1
, MAX(CASE WHEN feature_type_id = 2 THEN value END) feature_type_2
FROM features
GROUP
BY product_id;
+------------+----------------+----------------+
| product_id | feature_type_1 | feature_type_2 |
+------------+----------------+----------------+
| 1 | Red | Aluminium |
| 2 | Green | Wood |
| 3 | White | NULL |
| 4 | Plastic | NULL |
+------------+----------------+----------------+
4 rows in set (0.03 sec)
or...
SELECT f1.product_id
, f1.value feature_type_1
, f2.value feature_type_2
FROM features f1
LEFT
JOIN features f2
ON f2.product_id = f1.product_id
AND f2.feature_type_id = 2
WHERE f1.feature_type_id = 1;
a semplified way is based on building a string using group_concat
select p.id, p.name, p.content , group_concat( concat(t.name,':',f.value )) all_features
from products p
inner join features f on f.product_id = p.id
inner join feature_types t on t.id = f.feature_type_id
group by p.id

Select multiple rows containing values from one column

Actually my question is almost the same with MySQL: Select multiple rows containing values from one column, I want to find the car_id of the cars that have MAKE='FORD' AND COLOR='SILVER', so in this case here it will returns car_id 1 and 2.
PS: There could be multiple criteria at once, like I can search by MAKE + CARLINE + COLOR, MAKE + CARLINE, and etc.
table_cars
+----+--------+----------+-----------+
| id | car_id | name | value |
+----+--------+----------+-----------+
| 1 | 1 | MAKE | FORD |
| 2 | 1 | CARLINE | FIESTA |
| 3 | 1 | COLOR | SILVER |
| 4 | 1 | TOPSPEED | 210KM/H |
| 5 | 2 | MAKE | FORD |
| 6 | 2 | CARLINE | FOCUS |
| 7 | 2 | COLOR | SILVER |
| 8 | 2 | TOPSPEED | 200KM/H |
| 9 | 3 | MAKE | HOLDEN |
| 10 | 3 | CARLINE | ASTRA |
| 11 | 3 | COLOR | WHITE |
| 12 | 3 | TOPSPEED | 212KM/H |
+----+--------+----------+-----------+
Thank you!
select car_id
from your_table
group by car_id
having sum(name = 'MAKE' and value = 'FORD') > 0
and sum(name = 'COLOR' and value = 'SILVER') > 0
Try with self join as below:
SELECT distinct car_id
FROM mytable mt1 INNER JOIN mytable mt2
ON mt1.car_id = mt2.car_id
WHERE mt1.name = 'MAKE'
AND mt1.value = 'FORD'
AND mt2.name = 'COLOR'
AND mt2.value = 'SILVER'

Counting from another table [mysql]

I have one table like this:
+----+---------+-------------+
| id | site_id | search_term |
+----+---------+-------------+
| 1 | 2 | apple |
| 2 | 2 | banana |
| 3 | 3 | cheese |
| 4 | 1 | aubergine |
+----+---------+-------------+
And another like this:
+----+---------+-------------+
| id | site_id | search_term |
+----+---------+-------------+
| 1 | 2 | 1 |
| 2 | 2 | 2 |
| 3 | 2 | 1 |
| 4 | 2 | 1 |
| 5 | 3 | 3 |
| 6 | 1 | 4 |
+----+---------+-------------+
I want to find out how many times each search_term shows up in the 2nd table, and how many times.
In other words, from this data, if I was asking about site_id 2, I'd want this to be returned:
+-------------+-------+
| search_term | count |
+-------------+-------+
| apple | 3 |
| banana | 1 |
+-------------+-------+
I'm familiar with basic joins and such, as well as COUNT, but I'm not sure how to count things from another table.
Thanks!
You could join the tables together, and count the number of rows in the second table:
select t1.search_term
, count(t2.id)
from table1 t1
left join table2 t2
on t1.id = t2.search_term
group by t1.search_term
Give this a try:
select t1.search_term, count(*) from t1
join t2 on t1.id = t2.search_term and t1.site_id = t2.site_id
where t1.site_id = 2
group by t1.search_term

Querying across 6 tables, is there a better way of doing this?

What I did was, I wanted each user to have their own "unique" numbering system. Instead of auto incrementing the item number by 1, I did it so that Bob's first item would start at #1 and Alice's number would also start at #1. The same goes for rooms and categories. I achieved this by creating "mapping" tables for items, rooms and categories.
The query below works, but I know it can definitely be refactored. I have primary keys in each table (on the "ids").
SELECT unique_item_id as item_id, item_name, category_name, item_value, room_name
FROM
users_items, users_map_item, users_room, users_map_room, users_category, users_map_category
WHERE
users_items.id = users_map_item.map_item_id AND
item_location = users_map_room.unique_room_id AND
users_map_room.map_room_id = users_room.room_id AND
users_map_room.map_user_id = 1 AND
item_category = users_map_category.unique_category_id AND
users_map_category.map_category_id = users_category.category_id AND
users_category.user_id = users_map_category.map_user_id AND
users_map_category.map_user_id = 1
ORDER BY item_name
users_items
| id | item_name | item_location |item_category |
--------------------------------------------------------
| 1 | item_a | 1 | 1 |
| 2 | item_b | 2 | 1 |
| 3 | item_c | 1 | 1 |
users_map_item
| map_item_id | map_user_id | unique_item_id |
----------------------------------------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
users_rooms
| id | room_name |
----------------------
| 1 | basement |
| 2 | kitchen |
| 3 | attic |
users_map_room
| map_room_id | map_user_id | unique_room_id |
----------------------------------------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
users_category
| id | room_name |
----------------------
| 1 | antiques |
| 2 | appliance |
| 3 | sporting goods |
users_map_category
| map_room_id | map_user_id | unique_category_id |
----------------------------------------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
Rewriting your query with explicit JOIN conditions makes it more readable (while doing the same).
SELECT mi.unique_item_id AS item_id
, i.item_name
, c.category_name
, i.item_value
, r.room_name
FROM users_map_item mi
JOIN users_items i ON i.id = mi.map_item_id
JOIN users_map_room mr ON mr.unique_room_id = i.item_location
JOIN users_room r ON r.room_id = mr.map_room_id
JOIN users_map_category mc ON mc.unique_category_id = i.item_category
JOIN users_category c ON (c.user_id, c.category_id)
= (mc.map_user_id, mc.map_category_id)
WHERE mr.map_user_id = 1
AND mc.map_user_id = 1
ORDER BY i.item_name
The result is unchanged. Query plan should be the same. I see no way to improve the query further.
You should use LEFT [OUTER] JOIN instead of [INNER] JOIN if you want to keep rows in the result where no matching rows are found in the right hand table. You may want to move the additional WHERE clauses to the JOIN condition in this case, as it changes the outcome.

Include third table in LEFT JOIN query

I have five mysql tables. shops
+----+--------------+--------------+
| id | name | address |
+----+--------------+--------------+
| 1 | Shop1 | Street1 |
| 2 | Shop2 | Street2 |
| 3 | Shop3 | Street3 |
| 4 | Shop4 | Street4 |
+----+--------------+--------------+
fruits
+----+--------------+--------------+
| id | fruit | price |
+----+--------------+--------------+
| 1 | Bannana | 2.5 |
| 2 | Apple | 2.1 |
| 3 | Orange | 1.8 |
| 4 | Plum | 2.2 |
+----+--------------+--------------+
availability
+----+--------------+--------------+
| id | shop_id | fruit_id |
+----+--------------+--------------+
| 1 | 1 | 2 |
| 2 | 2 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
+----+--------------+--------------+
shop_activity
+----+--------------+--------------+--------------+
| id | shop_id | user_id | status |
+----+--------------+--------------+--------------+
| 1 | 2 | 1 | 1 |
| 2 | 3 | 2 | 1 |
| 3 | 1 | 2 | 2 |
| 4 | 2 | 2 | 1 |
+----+--------------+--------------+--------------+
users
+----+--------------+
| id | name |
+----+--------------+
| 1 | Peter |
| 2 | John |
+----+--------------+
I have query
SELECT
availability.shop_id,
shops.name
FROM availability
LEFT JOIN shops
ON availability.shop_id=shops.id
WHERE
fruit_id = 2
As a result I get name list of shops where fruit with id 2 (apple) is available.
What should I do so that I can include shop_activity table in query to get user's status if users.id = 1 beside proper shop. Something like this...
Shop1, NULL
Shop2, status: 1
You could try something like this:
SELECT
availability.shop_id,
shops.name,
shop_activity.status
FROM availability
LEFT JOIN shops
ON availability.shop_id=shops.id
LEFT JOIN shop_activity
ON shop_activity.shop_id = availability.shop_id
and shop_activity.user_id = 1
WHERE
fruit_id = 2
SELECT
availability.shop_id,
shops.name
FROM shops
LEFT JOIN availability ON availability.shop_id=shops.id
LEFT JOIN shop_activity ON shop_activity .shop_id=shops.id
WHERE
fruit_id = 2
and users.id=1
try making shops as the first table in left join
Try the following:
SELECT shops.name, shop_activity.status
FROM shops
INNER JOIN availability ON availability.shop_id = shops.id
AND availability.fruit_id = 2
LEFT JOIN shop_activity ON shops.shop_id = shop_activity.shop_id
AND shop_activity.user_id = 1
This should give you a row for every shop with apples, but the status will show as null for shops where the user has no activity, otherwise shows the status of that user.