Get the longest and shortest "chains" in MySQL? - mysql

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

Related

Query that join two tables, shows all rows from first table and from second only the row that has the date bigger than today and closer to today

I have two tables, partners and tasks. I would like to write a query that shows all the partners and if there are tasks show only one >= than today (date). It is important that the task is the immediately next to today as date, not the first >= than today that is in the db.
Please find the tables and code below. My problem is that the query does not return the immediately next value to the inserted date, but returns the >= that find in the database.
-- create a table
CREATE TABLE partners
(
id INTEGER PRIMARY KEY,
code INTEGER,
name TEXT
);
CREATE TABLE tasks
(
id INTEGER PRIMARY KEY,
code INTEGER,
comment TEXT,
data INTEGER
);
-- insert some values
INSERT INTO partners VALUES (1, 222, 'M');
INSERT INTO partners VALUES (2, 333, 'F');
INSERT INTO partners VALUES (3, 555, 'F-m');
INSERT INTO tasks VALUES (1, 333, 'f', 2020 );
INSERT INTO tasks VALUES (2, 333, 'f', 2022 );
INSERT INTO tasks VALUES (3, 333, 'f', 2021 );
INSERT INTO tasks VALUES (4, 333, 'f-i', 2023 );
INSERT INTO tasks VALUES (5, 222, 'm', 2021 );
INSERT INTO tasks VALUES (6, 444, 'F', 2025 );
-- fetch some values
SELECT *
FROM partners
LEFT JOIN tasks ON partners.code = tasks.code
WHERE tasks.data >= '2021'
GROUP BY tasks.code
ORDER BY tasks.data ASC;
Desired Result / Query Output:
1 , 222, M, m, 2021
2 , 333, F, f, 2021
3 , 555, F-m, No Task

MySQL query parameters in a table field

Good afternoon.
I have two tables:
1 - table1 - (id, address, who_lives) who_lives field - text, comma-separated list of identifiers
2 - table2 - (id, name_of_resident)
composition of table 1
12| US Oregon | 12,13
14| US Washington| 9,11
composition of table 2
9 |Petrov
11|Sidorov
12|Ivanov
13|Popov
How to make MySQL request, preferably via join, that the result was
US Oregon |Ivanov,Popov
US Washington|Petrov,Sidorov
If possible, replace the comma with the br tag: Ivanov,Popov -> Ivanov >br< Popov
SELECT table1.address, GROUP_CONCAT(table2.name_of_resident SEPARATOR '<br>')
FROM table1
JOIN table2 ON FIND_IN_SET(table2.id, table1.who_lives)
GROUP BY table1.address
DEMO
That is not how you design tables. Don't do comma separated listes. Do either a (foreign) key from table 2 to table 1, or a table with table1.id and table2.id for each relation.
Then writing the query will be easier.
create table state (
state_id int primary key,
address varchar(255)
);
create table resident (
resident_id int primary key,
name varchar(255)
);
create table who_lives (
state_id int,
resident_id int,
primary key (state_id, resident_id)
);
insert into state values (12, 'US Oregon');
insert into state values (14, 'US Washington');
insert into resident values (9, 'Petrov');
insert into resident values (11, 'Sidorov');
insert into resident values (12, 'Ivanov');
insert into resident values (13, 'Popov');
insert into who_lives values (12, 12);
insert into who_lives values (12, 13);
insert into who_lives values (14, 9);
insert into who_lives values (14, 11);
select address, group_concat(name)
from state
join who_lives using (state_id)
join resident using (resident_id)
group by state_id;
http://sqlfiddle.com/#!9/e7041f/2

MYSQL: select from many-to-many linking table

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;

mysql running total as view

I am still an sql greenhorn and try to convert this script, building a running total as view in mysql:
DROP TABLE IF EXISTS `table_account`;
CREATE TABLE `table_account`
(
id int(11),
account int(11),
bdate DATE,
amount DECIMAL(10,2)
);
ALTER TABLE `table_account` ADD PRIMARY KEY(id);
INSERT INTO `table_account` VALUES (1, 1, '2014-01-01', 1.0);
INSERT INTO `table_account` VALUES (2, 1, '2014-01-02', 2.1);
INSERT INTO `table_account` VALUES (4, 1, '2014-01-02', 2.2);
INSERT INTO `table_account` VALUES (5, 1, '2014-01-02', 2.3);
INSERT INTO `table_account` VALUES (3, 1, '2014-01-03', 3.0);
INSERT INTO `table_account` VALUES (7, 1, '2014-01-04', 4.0);
INSERT INTO `table_account` VALUES (6, 1, '2014-01-06', 5.0);
INSERT INTO `table_account` VALUES (8, 1, '2014-01-07', 6.0);
SET #iruntot:=0.00;
SELECT
q1.account,
q1.bdate,
q1.amount,
(#iruntot := #iruntot + q1.amount) AS runningtotal
FROM
(SELECT
account AS account,
bdate AS bdate,
amount AS amount
FROM `table_account`
ORDER BY account ASC, bdate ASC) AS q1
This is much more faster than building a sum over the whole history on each line.
The problems I cannot solve are:
Set in view
Subquery in view
I think it might be posssible to use some kind of JOIN instead of "SET #iruntot:=0.00;"
and use two views to prevent the need of a subquery.
But I do know how.
Will be happy for any hints to try.
Regards,
Abraxas
MySQL doesn't allow subqueries in the from clause for a view. Nor does it allow variables. You can do this with a correlated subquery, though:
SELECT q.account, q.b_date, q.amount,
(SELECT SUM(q2.amount)
FROM myview1 q2
WHERE q2.account < q.account OR
q2.account = q.account and q2.date <= q.date
) as running total
FROM myview1 q;
Note that this assumes that the account/date column is unique -- no repeated dates for an account. Otherwise, the results will not be exactly the same.
Also, it seems a little strange that you are doing a running total across all accounts and dates. I might expect a running total within accounts, but this is how you formulated the query in the question.

Select rows where field in joining table not same value in every row

Is it possible in MySQL to see if a field in a join table does not have the same value in every row.
I tried to put it in an easy example in this sqlfiddle
-- Create tables
create table tbl_cake (
id INT
);
create table tbl_cake_piece (
id INT,
cake_id INT,
share INT
);
-- This cake is divided in 2 pieces with size 1/2
insert into tbl_cake values (1);
insert into tbl_cake_piece values (1, 1, 2);
insert into tbl_cake_piece values (2, 1, 2);
-- This cake is divided in 1 piece with size 1/2 and 2 pieces with size 1/4
insert into tbl_cake values (2);
insert into tbl_cake_piece values (3, 2, 2);
insert into tbl_cake_piece values (4, 2, 4);
insert into tbl_cake_piece values (5, 2, 4);
-- I want to select cakes that are not divided in equals pieces
-- So this query should return cake with id '2'
select * from tbl_cake c
join tbl_cake_piece p on p.cake_id = c.id
Cakes that aren't cut in X equal pieces
select cake_id
from tbl_cake_piece
group by cake_id
having count(distinct share) > 1