MySQL - How to use subquery into IN statement by value - sql

The question is to get table column data and use it as a value list for IN function;
For this example I created 2 tables: movies and genres
Table "movies" contains 3 columns: id, name and genre.
Table "genres" contains 2 columns: id and name.
+- movies-+
| |- movie_id - int(11) - AUTO_INCREMENT - PRIMARY
| |- movie_name - varchar(255)
| |- movie_genres - varchar(255)
|
|
+- genres-+
|- genre_id - int(11) - AUTO_INCREMENT - PRIMARY
|- genre_name - varchar(255)
Both tables contain some dummy data:
+----------+------------+--------------+
| movie_id | movie_name | movie_genres |
+----------+------------+--------------+
| 1 | MOVIE 1 | 2,3,1 |
| 2 | MOVIE 2 | 2,4 |
| 3 | MOVIE 3 | 1,3 |
| 4 | MOVIE 4 | 3,4 |
+----------+------------+--------------+
+----------+------------+
| genre_id | genre_name |
+----------+------------+
| 1 | Comedy |
| 2 | Fantasy |
| 3 | Action |
| 4 | Mystery |
+----------+------------+
My goal is to get result like this:
+----------+------------+--------------+-----------------------+
| movie_id | movie_name | movie_genres | movie_genre_names |
+----------+------------+--------------+-----------------------+
| 1 | MOVIE 1 | 2,3,1 | Fantasy,Action,Comedy |
| 2 | MOVIE 2 | 2,4 | Fantasy,Mystery |
| 3 | MOVIE 3 | 1,3 | Comedy,Action |
| 4 | MOVIE 4 | 3,4 | Action,Mystery |
+----------+------------+--------------+-----------------------+
I'm using this query and it's partly working only problem is that it uses the first value of the movie_genres field in the IN value list.
SELECT `m` . * , GROUP_CONCAT( `g`.`genre_name` ) AS `movie_genre_names`
FROM `genres` AS `g`
LEFT JOIN `movies` AS `m` ON ( `g`.`genre_id`
IN (
`m`.`movie_genres`
) )
WHERE `g`.`genre_id`
IN (
(
SELECT `movie_genres`
FROM `movies`
WHERE `movie_id` =1
)
)
GROUP BY 1 =1
The results greatly differ from the one I want:
+----------+------------+--------------+-------------------+
| movie_id | movie_name | movie_genres | movie_genre_names |
+----------+------------+--------------+-------------------+
| 1 | MOVIE 1 | 2,3,1 | Fantasy,Fantasy |
+----------+------------+--------------+-------------------+
Sorry if I missed some data I'm new to mysql.
What query should I use to get the wanted results?

This is a bad design. You should create a many-to-many link table (movie_id, genre_id)
If you cannot change this design, however, use this query:
SELECT movie.*
(
SELECT GROUP_CONCAT(genre_name)
FROM genres
WHERE find_in_set(genre_id, movie_genres)
) as movie_genre_names
FROM movies

Related

how to perform sql actions/query for duplicate rows

I have 2 tables:
1-brokers(this is a company that could have multiple broker individuals)
and
2-brokerIndividuals (A person/individuals table that has a foreign key of broker company it belongs to and the individuals details)
I'm trying to create a unique index column for brokers table where the fields companyName are unique and isDeleted is NULL. Currently, the table is already populated so I want to write an SQL QUERY to find duplicate rows and whenever there are rows with the same companyName and isDeleted=NULL, I would like to perform 2 actions/queries:
1-keep the first row as it is and changes other duplicates(rows following the first duplicate) rows' isDeleted columns value to true.
2- associate or change the foreign key in brokerIndividuals for the duplicate rows for the first row.
The verbal description of what I am trying to do is: soft delete the duplicate rows and associate their corresponding brokerIndividuals to the first occurrence of duplicates. Table needs to have 1 occurrence of companyName where isDeleted is NULL.
I am using knex.js ORM so if that help's you can also suggest a solution using knex functions but knex doesn't support partial index yet( Knex.js - How to create unique index with 'where' clause? ) so I have to use the raw SQL method. Plus the DB I'm using is mssql(version: 6.0.1).
Here's a full test case (commented), with link to the fiddle:
Working test case, tested with MySQL 5.5, 5.6, 5.7, 8.0 and MariaDB up to 10.6
Create the tables and insert initial data with duplicate company_name entries:
CREATE TABLE brokers (
id int primary key auto_increment
, company_name VARCHAR(30)
, isDeleted boolean default null
);
CREATE TABLE brokerIndividuals (
id int primary key auto_increment
, broker_id int references brokers (id)
);
INSERT INTO brokers (company_name) VALUES
('name1')
, ('name1')
, ('name1')
, ('name1')
, ('name123')
, ('name123')
, ('name123')
, ('name123')
;
INSERT INTO brokerIndividuals (broker_id) VALUES
(2)
, (7)
;
SELECT * FROM brokers;
+----+--------------+-----------+
| id | company_name | isDeleted |
+----+--------------+-----------+
| 1 | name1 | null |
| 2 | name1 | null |
| 3 | name1 | null |
| 4 | name1 | null |
| 5 | name123 | null |
| 6 | name123 | null |
| 7 | name123 | null |
| 8 | name123 | null |
+----+--------------+-----------+
SELECT * FROM brokerIndividuals;
+----+-----------+
| id | broker_id |
+----+-----------+
| 1 | 2 |
| 2 | 7 |
+----+-----------+
Adjust brokers to determine isDeleted based on the MIN(id) per company_name:
UPDATE brokers
JOIN (
SELECT company_name, MIN(id) AS id
FROM brokers
GROUP BY company_name
) AS x
ON x.company_name = brokers.company_name
AND isDeleted IS NULL
SET isDeleted = CASE WHEN (x.id <> brokers.id) THEN 1 END
;
The updated brokers contents:
SELECT * FROM brokers;
+----+--------------+-----------+
| id | company_name | isDeleted |
+----+--------------+-----------+
| 1 | name1 | null |
| 2 | name1 | 1 |
| 3 | name1 | 1 |
| 4 | name1 | 1 |
| 5 | name123 | null |
| 6 | name123 | 1 |
| 7 | name123 | 1 |
| 8 | name123 | 1 |
+----+--------------+-----------+
For brokerIndividuals, find / set the correct broker_id:
UPDATE brokerIndividuals
JOIN brokers AS b1
ON b1.id = brokerIndividuals.broker_id
JOIN brokers AS b2
ON b1.company_name = b2.company_name
AND b2.isDeleted IS NULL
SET brokerIndividuals.broker_id = b2.id
;
New contents:
SELECT * FROM brokerIndividuals;
+----+-----------+
| id | broker_id |
+----+-----------+
| 1 | 1 |
| 2 | 5 |
+----+-----------+

Unique column by foreign key but seems to duplicate purpose of another table. Is this the idomatic way of doing it?

Given the following model where we have a menu that contains multiple categories
Menu *-- Categories
Menu A
- Category 1
- Category 2
- Category 3
Menu B
- Category blah
- Category 1
- Category 2
As such in the database
CREATE TABLE menus (
id UUID NOT NULL DEFAULT uuid_generate_v4() UNIQUE
);
CREATE TABLE categories (
id BIGSERIAL NOT NULL PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE menu_category_members (
menu_id UUID NOT NULL REFERENCES menus(id),
category_id BIGINT NOT NULL REFERENCES categories(id),
UNIQUE(menu_id, category_id)
);
// Menu
| id | name |
| 1 | Menu A |
| 2 | Menu B |
// Category
| id | name |
| 1 | Category 1 |
| 2 | Category 2 |
| 3 | Category 3 |
| 4 | Category blah |
| 5 | Category 1 |
| 6 | Category 2 |
// Category members
| menu_id | category_id |
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 4 |
| 2 | 5 |
| 2 | 6 |
I want to ensure that categories.name is unique per menu i.e. no duplicate categories in a menu
Is it as simple as including menu_id as FK in categories and adding unique constraint like below?
Isn't this duplicating the purpose of menu_category_members table making the table redundant?
CREATE TABLE categories (
id BIGSERIAL NOT NULL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
menu_id UUID NOT NULL REFERENCES menus(id),
UNIQUE(name, menu_id)
);
In this case, you should simplify the structure which is best practice in dynamic Menu implementation.
Pls see below structure
// Menu
| id | name |
| 1 | Menu A |
| 2 | Menu B |
// Category
| id | name |parent_menu_id
| 1 | Category 1 |1
| 2 | Category 2 |1
| 3 | Category 3 |1
| 4 | Category blah |2
| 5 | Category 1 |2
| 6 | Category 2 |2
Then you can implement Unique key constraint on parent_menu_id and name as below
CREATE TABLE categories (
id BIGSERIAL NOT NULL PRIMARY KEY,
name VARCHAR(100) NOT NULL
parent_menu_id BIGSERIAL NOT NULL,
CONSTRAINT uc_category UNIQUE (parent_menu_id,name)
);

How to join a polymorphic table with its child tables?

I am sorry about not being able to articulate the title of the question or the description of this question better. However, I will give the schema, sample data and expected result. Please help me write a query for such a use case.
Schema of restaurants
id
name
item_type
item_id
Schema of foods
id
name
Schema of food_items
id
name
food_id
Sample data in restaurants
|---------------------|------------------|---------------------|------------------|
| id | name | item_type | item_id |
|---------------------|------------------|---------------------|------------------|
| 1 | Apple Crushers | food_items | 1 |
|---------------------|------------------|---------------------|------------------|
| 2 | Retro Cafe | foods | 2 |
|---------------------|------------------|---------------------|------------------|
| 3 | Fruit Mania | foods | 1 |
|---------------------|------------------|---------------------|------------------|
| 4 | Meat and Eat | NULL | NULL |
|---------------------|------------------|---------------------|------------------|
Sample data in foods:
|---------------------|------------------|
| id | Name |
|---------------------|------------------|
| 1 | Fruits |
|---------------------|------------------|
| 2 | Chocolates |
|---------------------|------------------|
Sample data in food_items
|---------------------|------------------|---------------------|
| id | Name | food_id |
|---------------------|------------------|---------------------|
| 1 | Apple | 1 |
|---------------------|------------------|---------------------|
| 2 | Mango | 1 |
|---------------------|------------------|---------------------|
I need to write a query such that I get this as my result.
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
| r_id | r_name | food_id | food_name | food_item_id | food_item_name |
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
| 1 | Apple Crushers | 1 | Fruit | 1 | Apple |
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
| 2 | Retro Cafe | 2 | Chocolates | NULL | NULL |
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
| 3 | Fruit Mania | 1 | Fruit | NULL | NULL |
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
| 4 | Meat and Eat | NULL | NULL | NULL | NULL |
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
p.s: It will also be very helpful if someone could come up with an appropriate title and description for this problem. I am lost for words to describe this.
You must join the food table twice and use COALESCE:
select
r.id,
r.name,
coalesce(f.id, fif.id) as food_id,
coalesce(f.name, fif.name) as food_name,
fi.id as food_item_id,
fi.name as food_item_name
from restaurants r
left join foods f on f.id = r.item_id and r.item_type = 'foods'
left join food_items fi on fi.id = r.item_id and r.item_type = 'food_items'
left join foods fif on fif.id = fi.food_id
order by r.id;
select r.id, r.name, foods.id, foods.name, food_items.id, food_items.name from restaurents as r
left join food_items on r.item_type = 'food_items' and r.item_id = food_items.id
left join foods on (r.item_type = 'foods' and r.item_id = foods.id) or (r.item_type = 'foods' and r.item_id = food_items.food_id)
This might be having some syntax issues related to table names, but it should work.
For your database, I would suggest another data model based on compound keys. This would guarantee data consistency and makes queries a tad simpler:
food_group
(
food_group_no integer not null,
name varchar(100) not null,
primary key (food_group_no)
);
food
(
food_group_no integer not null,
food_no integer not null,
name varchar(100) not null,
primary key (food_group_no, food_no)
);
restaurant
(
restaurant_no integer not null,
name varchar(100) not null,
food_group_no integer not null,
food_no integer null,
primary key (restaurant_id),
foreign key (food_group_no) references food_group(food_group_no),
foreign key (food_group_no, food_no) references food(food_group_no, food_no)
);
The query:
select
r.restaurant_no,
r.name as restaurant_name,
fg.food_group_no,
fg.name as food_group_name,
f.food_id,
f.name as food_name
from restaurants r
join food_group fg on fg.id = r.food_group_no
left join food f on f.food_group_no = r.food_group_no and f.food_no = r.food_no
order by r.id;

SQL query by compound index

Let's say I have a table items with columns id type number room, id is primary key, (type, number) is a unique compound key; And a table inventory with columns id, item_type, item_number, owner, id is primary key, (type, number) is a unique compound key.
Example:
items
| id | type | number | room |
+----+---------+--------+------+
| 1 | laptop | 1 | 12 |
| 2 | laptop | 2 | 13 |
| 3 | desktop | 1 | 13 |
inventory
| id | item_type | item_number | owner |
+----+-----------+-------------+-------+
| 1 | laptop | 1 | Joe |
| 2 | laptop | 2 | Joe |
| 3 | desktop | 1 | Susan |
How do I query all items owned by Joe? If I do
SELECT *
FROM items
WHERE (type, number) IN (
SELECT item_type, item_number FROM inventory WHERE owner = 'Joe'
)
I only get one row in the result, though subquery returns multiple rows. I can't seem to do join on multiple columns either, like
SELECT *
FROM items
JOIN inventory ON inventory.item_type = items.type,
inventory.item_number = items.number`
WHERE inventory.owner = 'Joe'
You ought to combine the join conditions with AND, not with a comma.

SQL Performance: Using Union and Subqueries

Hi stackoverflow(My first question!),
We're doing something like an SNS, and got a question about optimizing queries.
Using mysql 5.1, the current table was created with:
CREATE TABLE friends(
user_id BIGINT NOT NULL,
friend_id BIGINT NOT NULL,
PRIMARY KEY (user_id, friend_id)
) ENGINE INNODB;
Sample data is populated like:
INSERT INTO friends VALUES
(1,2),
(1,3),
(1,4),
(1,5),
(2,1),
(2,3),
(2,4),
(3,1),
(3,2),
(4,1),
(4,2),
(5,1),
(5,6),
(6,5),
(7,8),
(8,7);
The business logic: we need to figure out which users are friends or friends of friends for a given user.
The current query for this for a user with user_id=1 is:
SELECT friend_id FROM friends WHERE user_id = 1
UNION
SELECT DISTINCT friend_id FROM friends WHERE user_id IN (
SELECT friend_id FROM friends WHERE user_id = 1
);
The expected result is(order doesn't matter):
2
3
4
5
1
6
As you can see, the above query performs the subquery "SELECT friend_id FROM friends WHERE user_id = 1" twice.
So, here is the question. If performance is your primary concern, how would you change the above query or schema?
Thanks in advance.
In this particular case, you can use a JOIN:
SELECT DISTINCT f2.friend_id
FROM friends AS f1
JOIN friends AS f2 ON f1.friend_id=f2.user_id OR f2.user_id=1
WHERE f1.user_id=1;
Examining each query suggests the JOIN will about as performant as the UNION in a big-O sense, though perhaps faster by a constant factor. Jasie's query looks like it might be big-O faster.
EXPLAIN SELECT friend_id FROM friends WHERE user_id = 1
UNION
SELECT DISTINCT friend_id FROM friends WHERE user_id IN (
SELECT friend_id FROM friends WHERE user_id = 1
);
+----+--------------------+------------+--------+---------------+---------+---------+------------+------+-------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+------------+--------+---------------+---------+---------+------------+------+-------------------------------------------+
| 1 | PRIMARY | friends | ref | PRIMARY | PRIMARY | 8 | const | 4 | Using index |
| 2 | UNION | friends | index | NULL | PRIMARY | 16 | NULL | 16 | Using where; Using index; Using temporary |
| 3 | DEPENDENT SUBQUERY | friends | eq_ref | PRIMARY | PRIMARY | 16 | const,func | 1 | Using index |
| NULL | UNION RESULT | <union1,2> | ALL | NULL | NULL | NULL | NULL | NULL | |
+----+--------------------+------------+--------+---------------+---------+---------+------------+------+-------------------------------------------+
EXPLAIN SELECT DISTINCT f2.friend_id
FROM friends AS f1
JOIN friends AS f2
ON f1.friend_id=f2.user_id OR f2.user_id=1
WHERE f1.user_id=1;
+----+-------------+-------+-------+---------------+---------+---------+-------+------+---------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+---------------------------------------------+
| 1 | SIMPLE | f1 | ref | PRIMARY | PRIMARY | 8 | const | 4 | Using index; Using temporary |
| 1 | SIMPLE | f2 | index | PRIMARY | PRIMARY | 16 | NULL | 16 | Using where; Using index; Using join buffer |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+---------------------------------------------+
EXPLAIN SELECT DISTINCT friend_id FROM friends WHERE user_id IN (
SELECT friend_id FROM friends WHERE user_id = 1
) OR user_id = 1;
+----+--------------------+---------+--------+---------------+---------+---------+------------+------+-------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+---------+--------+---------------+---------+---------+------------+------+-------------------------------------------+
| 1 | PRIMARY | friends | index | PRIMARY | PRIMARY | 16 | NULL | 16 | Using where; Using index; Using temporary |
| 2 | DEPENDENT SUBQUERY | friends | eq_ref | PRIMARY | PRIMARY | 16 | const,func | 1 | Using index |
+----+--------------------+---------+--------+---------------+---------+---------+------------+------+-------------------------------------------+
No need for the UNION. Just include an OR with the user_id of the beginning user:
SELECT DISTINCT friend_id FROM friends WHERE user_id IN (
SELECT friend_id FROM friends WHERE user_id = 1
) OR user_id = 1;
+-----------+
| friend_id |
+-----------+
| 2 |
| 3 |
| 4 |
| 5 |
| 1 |
| 6 |
+-----------+