How to select differences using three tables? - mysql

I need to run a script in order to fix some rows from my table company_menu.
However, I can't build this query to get these registers.
I build the schema in this link: http://sqlfiddle.com/#!9/5ab86b
Below I show the expected result.
companies
id
name
1
company 1
2
company 2
3
company 3
menu_items
id
name
1
home
2
charts
3
users
4
projects
company_menu
id
company_id
menu_item_id
1
1
1
2
1
2
3
1
3
4
1
4
5
2
1
6
2
3
This is a result that I expected:
id
company_id
menu_item_id
1
2
2
2
2
4
3
3
1
4
3
2
5
3
3
6
3
4
CREATE TABLE companies(
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
CREATE TABLE menu_items(
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
CREATE TABLE company_menu(
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
company_id INT,
menu_item_id INT,
FOREIGN KEY(company_id) REFERENCES companies(id),
FOREIGN KEY(menu_item_id) REFERENCES menu_items(id)
);
INSERT INTO companies (name) VALUES ("Company 1"),("Company 2"),("Company 3");
INSERT INTO menu_items (name) VALUES ("home"),("charts"),("users"),("projects");
INSERT INTO company_menu (company_id, menu_item_id) VALUES (1, 1),(1, 2),(1,3),(1,4);
INSERT INTO company_menu (company_id, menu_item_id) VALUES (2, 1),(2,3);

Two ways I can think of. Don't know which is more efficient. Both start with a full compannies-menu_items join to get all possible combinations, and then cut out the existing:
WHERE NOT EXISTS
select c.id company_id, m.id menu_item_id
from companies c
join menu_items m
where not exists (
select * from company_menu where company_id = c.id and menu_item_id = m.id
);
LEFT JOIN + IS NULL:
select c.id company_id, m.id menu_item_id
from companies c
join menu_items m
left join company_menu cm on cm.company_id = c.id and cm.menu_item_id = m.id
where cm.id is null;
Both are sortable on any company or menu_item column.
http://sqlfiddle.com/#!9/5ab86b/11

Related

How can I make a LEFT JOIN, but with rows separed from its relations in MySQL?

I need to get all rows that are in the table A, but joining with the table B (basically a LEFT JOIN), but also, I need to get the A table row itself, for example, with these tables:
Table A:
id
name
1
Random name
2
Random name #2
Table B:
id
parent_id
location
1
2
Location #1
2
2
Location #2
With this query:
SELECT * FROM A
LEFT JOIN B
ON A.id = B.parent_id;
I get something like this:
id
name
id
parent_id
location
1
Random name
NULL
NULL
NULL
2
Random name #2
1
2
Location #1
2
Random name #2
2
2
Location #2
But I want to get something like this:
id
name
id
parent_id
location
1
Random name
NULL
NULL
NULL
2
Random name #2
NULL
NULL
NULL
2
Random name #2
1
2
Location #1
2
Random name #2
2
2
Location #2
As you can see, there is a row by itself of "Random name #2" separated from its joins, how can I do that?
The main idea is that there are an ads table (the table A), but also, there are a subads table (the table B) with little variations of the ads table, and I need to show all ads and subads in a unique query.
Tanks a lot!
Two suggestions:
SELECT * FROM A
INNER JOIN B
ON A.id = B.parent_id
UNION ALL
SELECT *, NULL, NULL, NULL FROM A
or
SELECT A.*,B.*
FROM (SELECT 1 A_ONLY UNION ALL SELECT 0) A_ONLY
CROSS JOIN A
LEFT JOIN B
ON A.id = B.parent_id AND NOT A_ONLY
WHERE A_ONLY OR B.parent_id
The latter is an approach you can use to emulate WITH ROLLUP when that isn't allowed or when you want something slightly different than that produces (here, avoiding a grand total record and avoiding a double record when there are no B rows).
Probably not the best implementation, but until someone comes up with a proper solution...
SELECT A.id, name, B.id, parent_id, location FROM A
LEFT JOIN B
ON A.id = B.parent_id;
UNION ALL
SELECT A.id, name, NULL as id, NULL as parent_id, NULL as location FROM A
WHERE A.id IN (SELECT parent_id FROM B)
Simply UNION ALL with another query taking the values from A that had matches on B, hence no NULL values from the first query.
you need only the NULL added rows from A and the rest of the inner JOIN
CREATE TABLE A
(`id` int, `name` varchar(14))
;
INSERT INTO A
(`id`, `name`)
VALUES
(1, 'Random name'),
(2, 'Random name #2')
;
CREATE TABLE B
(`id` int, `parent_id` int, `location` varchar(11))
;
INSERT INTO B
(`id`, `parent_id`, `location`)
VALUES
(1, 2, 'Location #1'),
(2, 2, 'Location #2')
;
(SELECT A.id as a_id,A.name,B.* FROM A
INNER JOIN B
ON A.id = B.parent_id)
UNION
(SELECT A.*,NULL,NULL,NULL FROM A)
ORDER by a_id,id;
a_id | name | id | parent_id | location
---: | :------------- | ---: | --------: | :----------
1 | Random name | null | null | null
2 | Random name #2 | null | null | null
2 | Random name #2 | 1 | 2 | Location #1
2 | Random name #2 | 2 | 2 | Location #2
db<>fiddle here
You can make INNER JOIN instead of LEFT JOIN and UNION ALL with table A content:
Both queries must return the same number of columns.
SELECT *, NULL, NULL, NULL
FROM A
UNION ALL
SELECT *
FROM A
INNER JOIN B ON A.id = B.parent_id;

A better solution than using heavily the conditional JOIN

I'm trying to create a "complex" view in MySql. I need good performance because I have to query it 2 times per second and each result count about 1200 rows.
I report a schema example with data:
CREATE TABLE objects (
object_id INT AUTO_INCREMENT,
model_id INT,
mode TINYINT,
recipe_id INT,
CONSTRAINT pk_objects PRIMARY KEY (object_id));
INSERT INTO objects (model_id, mode, recipe_id) VALUES (1, 0, 1), (1, 1, 1), (2, 1, 1);
CREATE TABLE models (
model_id INT AUTO_INCREMENT,
family_id INT,
CONSTRAINT pk_models PRIMARY KEY (model_id));
INSERT INTO models (family_id) VALUES (0), (1);
CREATE TABLE models_recipes (
model_id INT,
recipe_id INT,
distinction_id INT,
CONSTRAINT pk_models_recipes PRIMARY KEY (model_id, recipe_id, distinction_id));
INSERT INTO models_recipes (model_id, recipe_id, distinction_id) VALUES (1, 2, 1), (1, 3, 2);
CREATE TABLE families (
family_id INT AUTO_INCREMENT,
name VARCHAR(45),
CONSTRAINT pk_families PRIMARY KEY (family_id));
INSERT INTO families (name) VALUES ("Family_1");
CREATE TABLE families_recipes (
family_id INT,
recipe_id INT,
distinction_id INT,
CONSTRAINT pk_families_recipes PRIMARY KEY (family_id, recipe_id, distinction_id));
INSERT INTO families_recipes (family_id, recipe_id, distinction_id) VALUES (1, 3, 1), (1, 2, 2);
CREATE TABLE recipes (
recipe_id INT AUTO_INCREMENT,
name VARCHAR(45),
CONSTRAINT pk_recipes PRIMARY KEY (recipe_id));
INSERT INTO recipes (name) VALUES ("recipe1"), ("recipe2"), ("recipe3");
My view needs to report the recipe name in these different conditions:
IF 'objects.mode' is 0 -> the name of 'object.recipe_id'
IF 'objects.mode' is 1
IF 'models.family_id > 0' -> the name of 'families_recipes.recipe_id' WHERE distinction_id = foo
ELSE -> the name of 'models_recipes.recipe_id' WHERE distinction_id = foo
I have written this query:
SELECT o.object_id, o.mode, o.model_id,
CASE
WHEN o.mode = 1 THEN
CASE
WHEN m.family_id > 0 THEN rf.name
ELSE rm.name
END
WHEN o.mode = 0 THEN ro.name
END AS 'recipe_name'
FROM objects AS o
LEFT JOIN models AS m
ON o.model_id = m.model_id
LEFT JOIN (SELECT * FROM models_recipes WHERE distinction_id = 1) AS mr
ON m.model_id = mr.model_id
LEFT JOIN recipes AS rm
ON mr.recipe_id = rm.recipe_id
LEFT JOIN (SELECT * FROM families_recipes WHERE distinction_id = 1) AS fr
ON m.family_id = fr.family_id
LEFT JOIN recipes AS rf
ON fr.recipe_id = rf.recipe_id
LEFT JOIN recipes AS ro
ON o.recipe_id = ro.recipe_id;
and the result is right
object_id | mode | model_id | recipe_name
-----------------------------------------
1 | 0 | 1 | recipe1
2 | 1 | 1 | recipe2
3 | 1 | 2 | recipe3
But I'm looking for a better solution, avoiding to JOIN the wanted data (recipes) a number of times equal to the number conditions.
Thanks
You can join recipes only once if you use conditional aggregation:
select o.object_id, o.mode, o.model_id,
case o.mode
when 0 then max(case when r.recipe_id = o.recipe_id then r.name end)
when 1 then case
when m.family_id > 0 then max(case when r.recipe_id = fr.recipe_id then r.name end)
else max(case when r.recipe_id = mr.recipe_id then r.name end)
end
end recipe_name
from objects o
left join models m on m.model_id = o.model_id
left join families f on f.family_id = m.family_id
left join families_recipes fr on fr.family_id = f.family_id and fr.distinction_id = 1
left join models_recipes mr on mr.model_id = m.model_id and mr.distinction_id = 1
left join recipes r on r.recipe_id in (o.recipe_id, fr.recipe_id, mr.recipe_id)
group by o.object_id, o.mode, o.model_id
See the demo.
Results:
object_id | mode | model_id | recipe_name
--------: | ---: | -------: | :----------
1 | 0 | 1 | recipe1
2 | 1 | 1 | recipe2
3 | 1 | 2 | recipe3

How to get one extra record for LEFT JOIN to represent a record not include on the left joined table

I have a database with two tables one table (shops) has an admin user column and the other a user with less privileges. I plan to LEFT JOIN the table of the user with less privileges. When I retrieve the data, the records for the admin user must be on a separate row and must have NULL values for the left joined table followed by records of users with less privileges (records of the left joined table) if any. I am using MySQL.
I have looked into the UNION commands but I don't think it can help. Please see the results bellow of what I need.
Thank you.
SELECT *
FROM shops LEFT JOIN users USING(shop_id)
WHERE shop_id = 1 AND (admin_id = 1 OR user_id = 1);
+---------+----------+---------+
| shop_id | admin_id | user_id |
+---------+----------+---------+
| 1 | 1 | NULL | <-- Need this one extra record
| 1 | 1 | 1 |
| 1 | 1 | 2 |
| 1 | 1 | 3 |
+---------+----------+---------+
Here is an example structure of the databases and some sample data:
CREATE SCHEMA test DEFAULT CHARACTER SET utf8 ;
USE test;
CREATE TABLE admin(
admin_id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY(admin_id)
);
CREATE TABLE shops(
shop_id INT NOT NULL AUTO_INCREMENT,
admin_id INT NOT NULL,
PRIMARY KEY(shop_id),
CONSTRAINT fk_shop_admin FOREIGN KEY(admin_id) REFERENCES admin (admin_id)
);
CREATE TABLE users(
user_id INT NOT NULL AUTO_INCREMENT,
shop_id INT NOT NULL,
CONSTRAINT fk_user_shop FOREIGN KEY(shop_id) REFERENCES admin (shop_id)
);
-- Sample data
INSERT INTO admin() VALUES ();
INSERT INTO shops(admin_id) VALUES (1);
INSERT INTO users(shop_id) VALUES (1),(1),(1);
I think you need union all:
select s.shop_id, s.admin_id, null as user_id
from shops s
where s.shop_id = 1
union all
select s.shop_id, s.admin_id, u.user_id
from shops s join
users u
on s.shop_id = u.shop_id
where shop_id = 1;
Put your where condition in On clause
SELECT *
FROM shops LEFT JOIN users on shops.shop_id=users.shop_id and (admin_id = 1 OR user_id = 1)
WHERE shops.shop_id = 1

Additional rows needed in SQL view

I have the following SQL tables, and require a solution compatible with both MySQL and Postgresql
create table price_level (
id serial primary key,
name varchar(200)
);
create table product (
id serial primary key,
name varchar(200),
base numeric not null,
vat int not null
);
create table product_price (
id serial primary key,
base numeric,
vat numeric,
product_id int not null references product(id) on update cascade on delete cascade,
price_level_id int not null references price_level(id) on update cascade on delete cascade,
unique(product_id,price_level_id)
);
For the SQL structure above I've created a view:
create view view_product as
select
p.id as product_id,
coalesce(pp.base, p.base) as base,
coalesce(pp.vat, p.vat) as vat,
pp.price_level_id
from
product as p
left join
product_price as pp on pp.product_id=p.id
;
These are sample data:
Table price_level
id name
1 A
2 B
3 C
4 D
5 E
Table product
id name base vat
1 Test 100 20
Table product_price
id base vat product_id price_level_id
1 NULL NULL 1 1
2 200 NULL 1 2
3 NULL 10 1 3
Output of the view view_product is:
product_id base vat price_level_id
1 100 20 1
1 200 20 2
1 100 10 3
... and the question is: How do I get output like this?:
product_id base vat price_level_id
1 100 20 1
1 200 20 2
1 100 10 3
1 100 20 4
1 100 20 5
As you see in the example above I need to get D and E price_level as additional rows. How do I create such view/join? It should have good performance also because tables can get big with additional price levels.
Thanks for help.
I would use union to add those records from price_level table that do not have corresponding record in product_price table for a certain product:
select
p.id as product_id,
coalesce(pp.base, p.base) as base,
coalesce(pp.vat, p.vat) as vat,
pp.price_level_id
from
product as p
left join
product_price as pp on pp.product_id=p.id
union distinct
select
p.id as product_id,
p.base,
p.vat,
pl.price_level_id
from
price_level pl
join
product as p
where (p.id, pl.id) not in (select product_id, price_level_id from product_price)
I would use following approach, cross join tables price_level and product. Then just lookup if override exists in product_price table.
SELECT
product.id as product_id,
IFNULL(product_price.base, product.base) as `base`,
IFNULL(product_price.vat, product.vat) as `vat`,
price_level.id as price_level_id
FROM price_level
CROSS JOIN product
LEFT JOIN product_price ON
product_price.price_level_id = price_level.id AND
product_price.product_id = product.id
WHERE product.id = 1
ORDER BY product.id, price_level.id
just remember to use product.id and not product_id in WHERE conditions
Try with:
create view view_product as
select
p.id as product_id,
coalesce(pp.base, p.base) as base,
coalesce(pp.vat, p.vat) as vat,
coalesce(pp.price_level_id,pl.id) --modified row
from
product as p
left join
product_price as pp on pp.product_id=p.id
LEFT JOIN price_level pl on pp.price_level_id=pl.id -- modified row
;
(not tested, but for sure you have to catch the price levels from the properly table)

SQL RIGHT JOIN misunderstanding

I'm working on ASP.NET application whose SQL backend (MySQL 5.6) has 4 tables:
The first table is defined in this way:
CREATE TABLE `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`descr` varchar(45) NOT NULL,
`modus` varchar(8) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
);
These are the items managed in the application.
the second table:
CREATE TABLE `files` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`file_path` varchar(255) NOT NULL,
`id_item` int(11) NOT NULL,
`id_type` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
);
these are files that are required for items management. Each 'item' can have 0 or multiple files ('id_item' field is filled with a valid 'id' of 'items' table).
the third table:
CREATE TABLE `file_types` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`file_type` varchar(32) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
);
this table describe the type of the file.
the fourth table:
CREATE TABLE `checklist` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_type` int(11) NOT NULL,
`modus` varchar(8) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
);
this table, as suggested by its name, is a checklist. It describe what types of files needs to be collected for a particular 'modus', 'modus' field holds the same values as for 'modus' in 'items' table, 'id_type' holds valid 'id' values from 'file_types' table.
Let's suppose that the first table holds those items:
id descr modus
--------------------
1 First M
2 Second P
3 Third M
4 Fourth M
--------------------
The second:
id file_path id_item id_type
--------------------------------------
1 file1.jpg 1 1
2 file2.jpg 1 2
3 file3.jpg 2 1
4 file4.jpg 1 4
5 file5.jpg 1 1
--------------------------------------
The third:
id file_type
--------------
1 red
2 blue
3 green
4 default
--------------
The fourth table:
id id_type modus
--------------------
1 1 M
2 2 M
3 3 M
4 4 M
5 1 P
6 4 P
--------------------
What I need to obtain is a table with such items (referred to id_item = 1):
id_item file_path id_type file_type
--------------------------------------------
1 file1.jpg 1 red
1 file5.jpg 1 red
1 file2.jpg 2 blue
1 file4.jpg 4 default
<null> <null> 3 green
--------------------------------------------
While the result table for id_item = 2 should be the following:
id_item file_path id_type file_type
--------------------------------------------
2 file3.jpg 1 red
<null> <null> 4 default
--------------------------------------------
where of course 'id_item' is the 'id' of 'items' table, 'id_type' is the 'id' of the 'types' table etc.
In short I need to have a table that depicts the checklist status for a particularm 'item' id i.e. which files have been collected but also which of them are missing.
I tried to use RIGHT JOIN clause without success:
SELECT
items.id AS id_item,
files.file_path AS file_path,
file_types.id AS id_type,
file_types.file_type AS file_type
FROM
files
RIGHT JOIN
checklist ON (files.id_type = checklist.id_type )
INNER JOIN
items ON (files.id_item = items.id)
AND (items.modus = checklist.modus)
INNER JOIN
file_types ON (checklist.id_type = file_types.id)
WHERE (items.id = 1);
the result of this query is:
id_item file_path id_type file_type
------------------------------------------
1 file1.jpg 1 red
1 file5.jpg 1 red
1 file2.jpg 2 blue
1 file4.jpg 4 default
it lacks of the last row (the missing file from the checklist).
Following query gives you status of each item as following (kind of checklist). I had to change some of the column names which were reserved words in my test environment.
select item_id,
fp filepath,
m_type,
item_desc,
modee,
(select t.type from typess t where t.id = m_type)
from (select null item_id,
i.descr item_desc,
c.modee modee,
c.id_type m_type,
null fp
from items i, checklist c
where c.modee = i.modee
and i.id = 0
and c.id_type not in
(select f.id_type from files f where f.id_item = i.id)
union all
select i.id item_id,
i.descr item_desc,
c.modee modee,
c.id_type m_type,
f.file_path fp
from items i, checklist c, files f
where c.modee = i.modee
and i.id = 0
and f.id_item = i.id
and f.id_type = c.id_type)
order by item_id asc, m_type asc
Try this:
SELECT
files.file_path,
types.type
FROM files
LEFT JOIN checklist ON (files.id_type = checklist.id_type )
LEFT JOIN items ON (files.id_item = items.id)
AND (items.mode = checklist.mode)
LEFT JOIN types ON (checklist.id_type = types .id)
WHERE (items.id = 0);
I have created and populated your tables, but I a discrepancy between what you request (for each item) and your example output (for each item type). However, I have created a query based on the output:
;with cte as (
SELECT i.id, f.file_path, f.id_type
from checklist ck
JOIN files f on f.id_type = ck.id_type
JOIN items i on i.id = f.id_item AND i.mode = ck.mode AND i.id = 0
)
SELECT cte.id, cte.file_path, T.id, T.[type]
FROM types T
LEFT JOIN cte on cte.id_type = T.id
[edit]
My result is the following (SQL):
id file_path id type
---------------------------------
0 file1.jpg 0 red
0 file5.jpg 0 red
0 file2.jpg 1 blue
NULL NULL 2 green
0 file4.jpg 3 default
No CTE version:
SELECT cte.id, cte.file_path, T.id, T.[type]
FROM types T
LEFT JOIN (
SELECT i.id, f.file_path, f.id_type
from checklist ck
JOIN files f on f.id_type = ck.id_type
JOIN items i on i.id = f.id_item AND i.mode = ck.mode AND i.id = 0
) cte on cte.id_type = T.id