I have 3 tables:
1) exercise: exercise_id, name
2) equipment: equipment_id, name
3) exercise_equipment: exercise_id, equipment_id - this is the linking table between exercise and equipment. In this table, one exercise can require multiple pieces of equipment, and the same equipment can be used for many exercises.
CREATE TABLE exercise
(`ex_id` int, `name` varchar(30))
;
INSERT INTO exercise
(`ex_id`, `name`)
VALUES
(1, 'push ups'),
(2, 'sit ups'),
(3, 'squats'),
(4, 'push ups with a fitness ball')
;
CREATE TABLE equipment
(`eq_id` int, `name` varchar(30))
;
INSERT INTO equipment
(`eq_id`, `name`)
VALUES
(1, 'none'),
(2, 'mat'),
(3, 'ball')
;
CREATE TABLE exercise_equipment
(`ex_id` int, `eq_id` int)
;
INSERT INTO exercise_equipment
(`ex_id`, `eq_id`)
VALUES
(1, 2),
(2, 2),
(3, 1),
(4, 2),
(4, 3)
;
What I need to do is select all the exercises that the user can do with the equipment that he has (for example, 1 and 2 - no equipment or mat).
I have found some examples and tried the following queries with inner join and where in:
SELECT ex.ex_id, ex.name from exercise ex
LEFT JOIN exercise_equipment exeq ON ex.ex_id = exeq.ex_id
WHERE exeq.eq_id IN (1,2);
and
select ex_id, name from exercise
where ex_id in (select ex_id from exercise_equipment where eq_id in (1,2));
But they return all exercises, instead of just the first three. In this case, if the user wants to do exercises that require no equipment or a mat (1 and 2), I want push ups, sit ups, and squats to be returned, but not the mat exercise with a ball.
I hope my explanation is clear. I would really appreciate any help I can get!
You want exercises that use only 1 and 2. When you use a WHERE clause, you are filtering out all the other equipment, so that won't work.
Instead, you need to aggregate and check that no other equipment is being used for the entire exercise. This is a similar query, with a HAVING clause:
SELECT ex.ex_id, ex.name
FROM exercise ex LEFT JOIN
exercise_equipment exeq
ON ex.ex_id = exeq.ex_id
GROUP BY ex.ex_id, ex.name
HAVING SUM(exeq.eq_id NOT IN (1, 2)) = 0;
In the event that some exercises have no equipment at all, you might want:
HAVING COALESCE(SUM(exeq.eq_id NOT IN (1, 2)), 0) = 0;
Related
I have two tables
1)LEAD TABLE (which have 3 columns)
Lead_ID || Created_Date || Industry
2)ACCOUNTS TABLE (which have 4 columns)
Account_ID||Created_Date|| Revenue_Range|| Lead_ID
How would I get the average number of days between a lead created and an account created
Don't pay attention to mess in data, I just randomly populated it.
Query returns leadId and difference in days between lead.created_date and account.created_date.
Query:
create table Leads
(
leadId int not null,
created_date datetime,
industry varchar(10),
PRIMARY KEY (leadId)
);
create table Accounts
(
accountId int not null,
created_date datetime,
revenue_range varchar(10),
leadId int not null,
FOREIGN KEY (leadId) REFERENCES Leads(leadId)
);
insert into Leads
values
(1, '2020-01-01', 'a'),
(2, '2020-01-02', 'b'),
(3, '2020-01-03', 'c'),
(4, '2020-02-01', 'd'),
(5, '2020-03-01', 'e');
insert into Accounts
values
(1, '2020-01-03', '1k', 1),
(2, '2020-03-10', '2k', 5),
(3, '2020-02-03', '3k', 2);
select
-- l.leadId,
-- l.created_date as LeadCreatedDate,
-- a.created_date as AccountCreatedDate,
-- ABS is used because it returns with minus sign
AVG(ABS(DATEDIFF(l.created_date, a.created_date))) as AvgDifferenceInDaysBetweenCreation
from Leads as l
inner join Accounts as a
on l.leadId = a.leadId;
You can try it out at SQLize Online
Consider this schema:
create table Operation(id integer, name varchar(100));
create table Pipeline(operation_in integer, operation_out integer);
Pipeline has foreign keys to Operations, so pipelines are chained, in a way. operation_out is nullable. How do I get the names of operations in both the longest and the shortest pipeline chains using MySQL?
Operations look like this:
INSERT INTO Operation VALUES (1, 'operation one');
While the pipelines look something like this:
INSERT INTO Pipeline VALUES (1, 2);
INSERT INTO Pipeline VALUES (2, 4);
INSERT INTO Pipeline VALUES (4, 7);
INSERT INTO Pipeline VALUES (7, NULL);
I.e. the chain here would be operations with ID 1, 2, 4, 7 and expected result along the lines of:
"operation one", "operation two", "operation four"...
After a few hours of research, I am not sure quite what the solution I am looking for.
Any MySQL version is applicable.
In MySQL 8.x you can use a Recursive CTE to find the chains you need.
For example:
with recursive
a as (
select
p.operation_in,
p.operation_out as current_out,
o.name as op_names,
concat('', p.operation_in) as chain,
1 as size
from pipeline p
join operation o on o.id = p.operation_in
where not exists (
select 1 from pipeline p2 where p2.operation_out = p.operation_in
)
union all
select
a.operation_in,
p.operation_out,
concat(op_names, ', ', o.name),
concat(chain, ',', p.operation_in),
size + 1
from a
join pipeline p on p.operation_in = a.current_out
join operation o on o.id = p.operation_in
),
chains as (
select * from a where current_out is null
)
select op_names, chain, size
from chains
where size = (select max(size) from chains) -- finds the longest one
or size = (select min(size) from chains); -- finds the shortest one
Result:
op_names chain size
--------------------------------- ------- ----
op-nine, op-six 9,6 2
op-one, op-two, op-four, op-seven 1,2,4,7 4
The data script I used is:
create table operation (id integer, name varchar(100));
create table pipeline (operation_in integer, operation_out integer);
insert into operation values (1, 'op-one');
insert into operation values (2, 'op-two');
insert into operation values (4, 'op-four');
insert into operation values (6, 'op-six');
insert into operation values (7, 'op-seven');
insert into operation values (9, 'op-nine');
insert into pipeline values (1, 2);
insert into pipeline values (2, 4);
insert into pipeline values (4, 7);
insert into pipeline values (7, null);
insert into pipeline values (9, 6);
insert into pipeline values (6, null);
I have three tables. I want to write a query to calculate the total handling cost of each order using the tables in MySQL.
create table orders(id integer, packaging varchar(100), delivery
varchar(100));
create table packaging_cost_tbl(packaging_type varchar(100), packaging_cost
integer);
create table delivery_cos_tbl(delivery_type varchar(100), delivery_cost
integer);
insert into orders(id, packaging, delivery) values(1, "Large", "Fast"),(2,
"Small", "Fast"), (3, "Large", "Express"), (4, "Medium", "Standard"), (5,
"Fragile", "Express"), (6, "Medium", "Fast"), (7, "Medium", "Standard");
insert into packaging_cost_tbl(packaging_type, packaging_cost)
values("Small", 2), ("Medium", 5), ("Large", 8), ("Fragile", 10);
insert into delivery_cost_tbl(delivery_type, delivery_cost)
values("Standard", 3), ("Fast", 7), ("Express", 15);
enter image description here
I have mentioned above create table queries and data insert queries for each tables. Output should be,
Order ID
Total handling cost( handling cost=packaging cost + delivery cost)
I'd join the orders table on the two others and sum their costs:
SELECT id, packaging_cost + delivery_cost
FROM orders o
JOIN packaging_cost_tbl p ON o.pacakging = p.packaging_type
JOIN delivery_cost_tbl d ON o.delivery = d.delivery_type
I have a schema that requires joining to the same table multiple times to get more information on the data pointed to by the columns. Below is an example schema that shows this situation:
SQL Fiddle: http://sqlfiddle.com/#!9/7a4019/1
CREATE TABLE STATE
(
employee INT NOT NULL,
boss INT,
manager INT,
rep INT
);
CREATE TABLE EMPLOYEE
(
id INT NOT NULL,
name VARCHAR(255) NOT NULL
);
INSERT INTO EMPLOYEE (id, name) VALUES (1, "Joe");
INSERT INTO EMPLOYEE (id, name) VALUES (2, "John");
INSERT INTO EMPLOYEE (id, name) VALUES (3, "Jack");
INSERT INTO EMPLOYEE (id, name) VALUES (4, "Jeff");
INSERT INTO EMPLOYEE (id, name) VALUES (5, "Jason");
INSERT INTO STATE (employee, boss, manager, rep) VALUES (1, 2, 3, 4);
INSERT INTO STATE (employee, boss, manager, rep) VALUES (2, 3, 3, 4);
INSERT INTO STATE (employee, boss, manager, rep) VALUES (3, NULL, NULL, 4);
INSERT INTO STATE (employee, boss, manager, rep) VALUES (4, 3, 3, NULL);
INSERT INTO STATE (employee, boss, manager, rep) VALUES (5, 2, 3, 4);
Currently, the only way i know to get this information in single rows for each employee, is left joining multiple times like this:
SELECT employee, b.name AS boss, m.name AS manager, r.name AS rep
FROM STATE
LEFT JOIN EMPLOYEE b ON b.employee = STATE.boss
LEFT JOIN EMPLOYEE m ON m.employee = STATE.manager
LEFT JOIN EMPLOYEE r ON r.employee = STATE.rep
Is there a way to do it without joins and without subqueries?
You asked:
Is there a way to do it without joins and without subqueries?
Not really. You are using the JOIN operations precisely as they're intended to be used -- each JOIN reflects a specific relationship between rows of a table.
You can avoid doing multiple joins by using aggregate functions, which I recently found useful after hitting the limit (61) of the number of joins that can be done in a query in MySQL/MariaDB.
SELECT s.employee,
GROUP_CONCAT(if(e.id=s.boss, name, NULL)) as boss,
GROUP_CONCAT(if(e.id=s.manager, name, NULL)) as manager,
GROUP_CONCAT(if(e.id=s.rep, name, NULL)) as rep,
FROM STATE s, EMPLOYEE e
GROUP BY s.employee
The above example uses MySQL's GROUP_CONCAT function. It appears not to be an ANSI standard. Other relational databases may have similar functions. A cursory web search turned up a page that discussed aggregate functions for various relational databases: http://www.postgresonline.com/journal/archives/191-String-Aggregation-in-PostgreSQL,-SQL-Server,-and-MySQL.html
Please take a look at SQLFiddle
I have two tables to store users' likes/dislikes on products. I pull up the number of likes and dislikes for each toy on a page using RIGHT JOIN. I'd like to know if it's an appropriate approach to get this output:
TOY LIKES DISLIKES
ToyA 2 0
ToyB 0 0
ToyC 0 0
ToyD 1 2
ToyE 0 0
ToyF 0 0
ToyG 0 1
ToyH 1 0
ToyI 0 0
Another approach I've come up is to store the number of likes/dislikes in table toy so that it doesn't need a RIGHT JOIN, but it requires an additional insert query when someone votes. Which one would you prefer?
SELECT
b.toy,
IFNULL(SUM( liketype =1 ),0)AS likes,
IFNULL(SUM( liketype =0 ),0) AS dislikes
FROM `product_review` a
RIGHT JOIN toy b
ON b.ID = a.toyid
GROUP BY b.toy
TABLE
CREATE TABLE toy
(`ID` int, `toy` varchar(21), `like` int ,`dislike` int)
;
INSERT INTO toy
(`ID`,`toy`, `like`,`dislike`)
VALUES
(1, 'ToyA', 2, 0),
(2, 'ToyB',0 , 0),
(3, 'ToyC', 0, 0),
(4, 'ToyD', 1, 2),
(5, 'ToyE', 0, 0),
(6, 'ToyF', 0, 0),
(7, 'ToyG',0, 1),
(8, 'ToyH',0, 1),
(9, 'ToyI', 0, 0)
;
CREATE TABLE product_review
(`ID` int,`user` varchar(20), `toyid` varchar(21), `liketype` int)
;
INSERT INTO product_review
(`ID`, `user`,`toyid`, `liketype`)
VALUES
(1, 'Tom','1', 1),
(2, 'Ben', '4',0),
(3, 'Peter','1', 1),
(4, 'May','4', 0),
(5, 'May', '8',1),
(6, 'Tom','7',0),
(7, 'Paul','4', 1)
;
If the number of user votes is too much & you need to calculate & show result of voting many times, then this joining may be an overhead. At the other side, storing vote informations in a separate table would be more flexible. So I personally prefer to mix two solutions:
Keep product_review table & add the number of like/dislikes in the toy table, then add a trigger on product_review table to update like/dislike field for corresponding toy in toy table after every vote record insertion. This acts like a cache for you and helps you to benefit two solutions in the same time.