How to get rows ordered by desc from specific ids - mysql

My goal : Getting Purchase requests ordered by most confirmed shipment locations.
Purchase requests are linked to a shipment location (warehouse).
I have a table :
CREATE TABLE IF NOT EXISTS `shipment_locations` (
`id` int(6) primary key,
`name` varchar(200) NOT NULL
);
INSERT INTO `shipment_locations` (`id`, `name`) VALUES
('1', 'france'),
('2', 'usa'),
('3', 'spain'),
('4', 'germany');
CREATE TABLE IF NOT EXISTS `purchase_requests` (
`id` int(6) primary key,
`name` varchar(200) NOT NULL,
`total_cost_confirmed` int(6) NULL,
`shipment_location_id` int(6) NULL,
FOREIGN KEY (`shipment_location_id`) REFERENCES `shipment_locations` (`id`)
);
INSERT INTO `purchase_requests` (`id`, `name`, `total_cost_confirmed`, `shipment_location_id`) VALUES
('1', 'pr1', '109', 1),
('2', 'pr2', '1500', 3),
('3', 'pr3', '3000', 2),
('4', 'pr4', '10000', 2),
('5', 'pr5', '5', 3),
('6', 'pr6', '3000', 2),
('7', 'pr7', '3000', 2),
('8', 'pr8', '1', 3),
('9', 'pr9', '10000000', 3);
For ordering by shipment location that have the most confirmed cost, it's pretty simple :
SELECT shipment_location_id, SUM(total_cost_confirmed) totalConfirmed
FROM purchase_requests
GROUP BY shipment_location_id
ORDER BY totalConfirmed DESC
It works perfectly here : http://sqlfiddle.com/#!9/732f32/2/0
But, then I tried to filter by purchase request id (adding GROUP BY id and WHERE id IN(...)) it gives me the wrong order (because it's taking ids present in the result).
=> (sqlfiddle)
How I can keep the correct order from the first query while filtering by Purchase request id ?
Adding sqlfiddle : Sqlfiddle
Thanks by advance for your help :)

First aggregate to get the sum of totalConfirmed and then join to the table:
SELECT p.id, p.name, p.shipment_location_id, t.totalConfirmed
FROM purchase_requests p
INNER JOIN (
SELECT shipment_location_id, SUM(total_cost_confirmed) totalConfirmed
FROM purchase_requests
GROUP BY shipment_location_id
) t ON t.shipment_location_id = p.shipment_location_id
WHERE p.id IN ('1', '3', '4', '8')
ORDER BY t.totalConfirmed DESC
See the demo.
Results:
> id | name | shipment_location_id | totalConfirmed
> -: | :--- | -------------------: | -------------:
> 4 | pr4 | 2 | 19000
> 3 | pr3 | 2 | 19000
> 8 | pr8 | 1 | 110
> 1 | pr1 | 1 | 110

Related

Find DISTINCT LAST record with SQL LEFT JOIN

I'm running MySQL 5.6.
I have two related tables:
CREATE TABLE Cars (
id INT NOT NULL AUTO_INCREMENT,
plate VARCHAR(16) NOT NULL,
flag TINYINT,
PRIMARY KEY(id)
)
and:
CREATE TABLE Rents (
id INT NOT NULL AUTO_INCREMENT,
out_date DATE NOT NULL,
in_date DATE,
car_id INT,
FOREIGN KEY (car_id) REFERENCES Cars(id),
PRIMARY KEY(id)
)
I can have multiple rents for each car (0 to many).
I need to select all vehicles in table Cars (with flag = 1) along with their status i.e. I need to know if each car is currently unavailable (only out_date is filled) or availabe (out_date and in_date filled) of course also vehicles without any rents are to be considered available.
The result set need to include out_date and in_date values [Update 17/07/2022].
I tought to use something like:
SELECT
*,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rent.Car_id WHERE Cars.Flag = 1
but this of course will just return all the rows with positive flag match and a status evaluation (0 unavailable, 1 available):
id | plate | flag | id | out_date | in_date | car_id | status
---------------------------------------------------------------------
'1', 'FA787MX', '1', '1', '2022-07-14', '2022-07-15', '1', '1'
'1', 'FA787MX', '1', '2', '2022-07-16', NULL, '1', '0'
'3', 'AB124DF', '1', '4', '2022-07-13', '2022-07-14', '3', '1'
'4', 'CC666VC', '1', NULL, NULL, NULL, NULL, '1'
'5', 'GG435ED', '1', '5', '2022-07-16', NULL, '5', '0'
While I need to have this (edited 17/07/2022):
'1', 'FA787MX', '1', '2', '2022-07-16', NULL, '1', '0'
'3', 'AB124DF', '1', '4', '2022-07-13', '2022-07-14', '3', '1'
'4', 'CC666VC', '1', NULL, NULL, NULL, NULL, '1'
'5', 'GG435ED', '1', '5', '2022-07-16', NULL, '5', '0'
i.e. only the second row of FA787MX car should be mantained since it's the most recent out_date value (no matter if it's id is higher or lower).
For the sake of completeness: There is no guarantee that rental ids will be kept consistent with their rental history. In other words you cannot be sure that for a given car the rental where in_date = NULL is the correct one but you should compare them by out_date value.
Data sample:
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (1, 'FA787MX', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (2, 'EX431YY', 0);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (3, 'AB124DF', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (4, 'CC666VC', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (5, 'GG435ED', 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (1, '2022-07-14', '2022-07-15', 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (2, '2022-07-16', NULL, 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (3, '2022-07-16', NULL, 2);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (4, '2022-07-13', '2022-07-14', 3);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (5, '2022-07-16', NULL, 5);
One option is to join to find only those rentals that are still outstanding (in_date IS NULL). That will drop the old rentals having in_date not null.
Based on the updated requirements, there are a few ways to do it. One is a simple outer join to find the most recent rental per car to obtain the corresponding in_date as well...
MySQL 5.6 fiddle
SELECT Cars.*
, Rents.out_date
, Rents.in_date
, Rents.id IS NULL OR Rents.in_date IS NOT NULL AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
LEFT JOIN Rents AS r2
ON Rents.out_date < r2.out_date
AND Rents.Car_id = r2.Car_id
WHERE Cars.Flag = 1
AND r2.Car_id IS NULL
ORDER BY Cars.id
;
The result:
id
plate
flag
out_date
in_date
status_final
1
FA787MX
1
2022-07-16
0
3
AB124DF
1
2022-07-13
2022-07-14
1
4
CC666VC
1
1
5
GG435ED
1
2022-07-16
0
Based on the original requirements: Try this (fiddle):
SELECT Cars.*
, Rents.in_date
, CASE WHEN in_date IS NOT NULL OR Rents.id IS NULL THEN 1 ELSE 0 END AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
AND in_date IS NULL
WHERE Cars.Flag = 1
;
and if the results contain only those with in_date IS NULL, this reduces to:
SELECT Cars.*
, out_date
, Rents.in_date
, Rents.id IS NULL AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
AND in_date IS NULL
WHERE Cars.Flag = 1
;
Result:
id
plate
flag
out_date
in_date
status_final
1
FA787MX
1
2022-07-16
0
3
AB124DF
1
1
4
CC666VC
1
1
5
GG435ED
1
2022-07-16
0
If your version of MySql is 8.0+ use ROW_NUMBER() window function to pick the latest row for each car in Rents:
SELECT c.*, r.*,
r.out_date IS NULL OR r.in_date IS NOT NULL status
FROM Cars c
LEFT JOIN (
SELECT *, ROW_NUMBER() OVER (PARTITION BY car_id ORDER BY out_date DESC) rn
FROM Rents
) r ON r.car_id = c.id AND r.rn = 1
WHERE c.flag = 1;
For previous versions use NOT EXISTS:
SELECT c.*, r.*,
r.out_date IS NULL OR r.in_date IS NOT NULL status
FROM Cars c
LEFT JOIN (
SELECT r1.*
FROM Rents r1
WHERE NOT EXISTS (
SELECT *
FROM Rents r2
WHERE r2.car_id = r1.car_id AND r2.out_date > r1.out_date
)
) r ON r.car_id = c.id
WHERE c.flag = 1;
See the demo.
If you imagine the result of your query as a table, you can easily write a query that would give you what you need (the subquery is just yours with the select spelled out to give a unique column name to the second id column, as it seemed useful - the only way to uniquely identify a row):
SELECT MAX(rent_id) FROM (
SELECT
Cars.id as id,
plate,
flag,
Rents.id as rent_id,
out_date,
in_date,
car_id,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rents.car_id WHERE Cars.Flag = 1
) as rental_status
WHERE status = 0
GROUP BY car_id;
Which tells you which rows are interesting:
+--------------+
| MAX(rent_id) |
+--------------+
| 2 |
| 5 |
+--------------+
Now you can use a join to return the results of your initial query only for the interesting rows. To avoid having to spell out that query all over again, MySQL 8 has a way to stash the results of your core query and use it like a table:
WITH
status_data AS (
SELECT
Cars.id as id,
plate,
flag,
Rents.id as rent_id,
out_date,
in_date,
car_id,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rents.car_id WHERE Cars.Flag = 1
)
SELECT * from status_data
JOIN (
SELECT MAX(rent_id) as rent_id FROM status_data
WHERE status = 0
GROUP BY car_id
) as ids using(rent_id);
Giving the result:
+---------+----+---------+------+------------+---------+--------+--------+
| rent_id | id | plate | flag | out_date | in_date | car_id | status |
+---------+----+---------+------+------------+---------+--------+--------+
| 2 | 1 | FA787MX | 1 | 2022-07-16 | NULL | 1 | 0 |
| 5 | 5 | GG435ED | 1 | 2022-07-16 | NULL | 5 | 0 |
+---------+----+---------+------+------------+---------+--------+--------+

Query for any person has any account of type x

Imagine I have two tables, Person and Account, a person can have accounts (type 1 and/or 2).
I'd like to get a list of people who have at least one type 1 account, and also get a list of people who don't have a type 1 account. I'm using Query #1 and #2 for this respectively but I think I'm doing something is wrong because the results do not match.
Schema (MySQL v5.7)
CREATE TABLE Person (
`PersonId` INTEGER,
`Name` VARCHAR(5)
);
INSERT INTO Person
(`PersonId`, `Name`)
VALUES
('1', 'Leo'),
('2', 'Natan'),
('3', 'Vera'),
('4', 'Julio'),
('5', 'Mary');
CREATE TABLE Accounts (
`AccountId` INTEGER,
`PersonId` INTEGER,
`Type` INTEGER
);
INSERT INTO Accounts
(`AccountId`, `PersonId`, `Type`)
VALUES
('1', '1', '0'),
('2', '1', '1'),
('3', '2', '0'),
('4', '2', '0'),
('5', '3', '1'),
('6', '4', '0'),
('7', '1', '0'),
('8', '2', '0');
Query #1
SELECT * FROM Person AS PD
LEFT JOIN Accounts AS AC ON AC.PersonId = PD.PersonId
WHERE AC.Type = 1;
PersonId
Name
AccountId
PersonId
Type
1
Leo
2
1
1
3
Vera
5
3
1
Query #2
SELECT * FROM Person AS PD
LEFT JOIN Accounts AS AC ON AC.PersonId = PD.PersonId
WHERE AC.Type = 0;
PersonId
Name
AccountId
PersonId
Type
1
Leo
1
1
0
1
Leo
7
1
0
2
Natan
3
2
0
2
Natan
4
2
0
2
Natan
8
2
0
4
Julio
6
4
0
View on DB Fiddle
EXISTS and NOT EXISTS are the more suitable solutions for this requirement:
-- Account type = 1
SELECT p.* FROM Person AS p
WHERE EXISTS (
SELECT *
FROM Accounts AS a
WHERE a.PersonId = p.PersonId AND a.Type = 1
);
-- No type 1 account
SELECT p.* FROM Person AS p
WHERE NOT EXISTS (
SELECT *
FROM Accounts AS a
WHERE a.PersonId = p.PersonId AND a.Type = 1
);
See the demo.

inner join with subquery results differs for these data sets

CREATE TABLE IF NOT EXISTS `wcd` (
`id` int(6) unsigned NOT NULL,
`wid` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `wcd` (`id`, `wid`) VALUES
('168', '5685'),
('167', '5685'),
('166', '5685'),
('165', '5685'),
('164', '5685'),
('163', '5685'),
('162', '5684'),
('161', '5684');
CREATE TABLE IF NOT EXISTS `cases` (
`id` int(6) unsigned NOT NULL,
`wcd_id` int(11) unsigned NOT NULL,
`reason_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `cases` (`id`, `wcd_id`, `reason_id`) VALUES
('20', '168', '4'),
('19', '168', '1'),
('18', '167', '6'),
('17', '167', '5'),
('16', '166', '4'),
('15', '166', '1'),
('14', '165', '4'),
('13', '165', '1'),
('12', '164', '1'),
('11', '163', '4'),
('10', '162', '1'),
('9', '162', '4'),
('8', '162', '5'),
('7', '161', '5'),
('6', '161', '6');
the above two table has foreignkey relation with wcd.id = cases.wcd_id,
Lets consider the records related to wcd.wid 5865. The result should be grouped by reason_id with the condition max(cases.id)
I used the query below to achieve this and got the result as expected.
SELECT d.id, d.wid, c.* FROM wcd d
LEFT JOIN cases c ON c.wcd_id = d.id
inner JOIN (SELECT MAX(id) AS max_id FROM cases GROUP BY reason_id) c2
ON c2.max_id = c.id
WHERE d.wid = 5685;
Result:
id wid id wcd_id reason_id
168 5685 19 168 1
168 5685 20 168 4
167 5685 17 167 5
167 5685 18 167 6
with the same query for 5684, the query returns 0 rows though there is data available for it. but I'm expecting the rows below.
id wid id wcd_id reason_id
162 5684 10 162 1
162 5684 9 162 4
162 5684 8 162 5
161 5684 6 161 6
What the issue with the query and what needs to be changed to get the result above for 5684.?
here is the sqlfiddle link
You need to look back at the wcd table to propery correlate, since you need the id of the row that has the "latest" reason per wid - and that column is not available in cases.
In MySQL 8.0, we would just use row_number()... but you tagged your question MySQL 5.6. I find that the simplest way to express this is with a correlated subquery:
SELECT d.id, d.wid, c.*
FROM wcd d
INNER JOIN cases c ON c.wcd_id = d.id
WHERE c.id = (
SELECT max(c2.id)
FROM wcd d2
INNER JOIN cases c2 ON c2.wcd_id = d2.id
WHERE d2.wid = d.wid AND c2.reason_id = c.reason_id
)
AND d.wid = 5685;
Then you must use MIN and get rid of the Where Clause.because
('162', '5684')
('161', '5684')
because
SELECT
d.id
, d.wid
,
c.*
FROM
wcd d
LEFT JOIN
cases c
ON c.wcd_id = d.id
inner JOIN (SELECT MIN(id) AS min_id FROM cases GROUP BY reason_id) c2
ON c2.min_id = c.id
see http://sqlfiddle.com/#!9/fb4569/26

MySQL pivoting not working as expected

Schema:
DROP TABLE IF EXISTS `questions_tags`;
CREATE TABLE `questions_tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag_id` int(11) NOT NULL,
`question_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `questions_tags` VALUES ('1', '1', '1');
INSERT INTO `questions_tags` VALUES ('2', '2', '1');
INSERT INTO `questions_tags` VALUES ('3', '3', '1');
INSERT INTO `questions_tags` VALUES ('4', '4', '1');
INSERT INTO `questions_tags` VALUES ('5', '5', '1');
INSERT INTO `questions_tags` VALUES ('6', '2', '2');
Data:
id tag_id question_id
1 1 1
2 2 1
3 3 1
4 4 1
5 5 1
6 2 2
What I've tried:
SELECT
question_id,
CASE WHEN tag_id = 1 THEN 'TAG1' END AS FirstTag,
CASE WHEN tag_id = 2 THEN 'TAG2' END AS SecondTag,
CASE WHEN tag_id = 3 THEN 'TAG3' END AS ThirdTag,
CASE WHEN tag_id = 4 THEN 'TAG4' END AS FourthTag,
CASE WHEN tag_id = 5 THEN 'TAG5' END AS FifthTag
FROM questions_tags
GROUP BY question_id;
Current Output:
Expected Output:
Is there something that I misjudged about Pivoting? Any help is appreciated.
Use aggregation on the case expressions.
SELECT
question_id,
max(CASE WHEN tag_id = 1 THEN 'TAG1' END) AS FirstTag,
max(CASE WHEN tag_id = 2 THEN 'TAG2' END) AS SecondTag,
max(CASE WHEN tag_id = 3 THEN 'TAG3' END) AS ThirdTag,
max(CASE WHEN tag_id = 4 THEN 'TAG4' END) AS FourthTag,
max(CASE WHEN tag_id = 5 THEN 'TAG5' END) AS FifthTag
FROM questions_tags
GROUP BY question_id;

mysql query hierarchic

I need help to find a sql query
CREATE TABLE album (
id_album INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
nom VARCHAR(255) NOT NULL
);
CREATE TABLE sous_album (
id_sous_album INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
nom VARCHAR(255) NOT NULL,
id_album INTEGER REFERENCES album(id_album)
);
CREATE TABLE photo (
id_photo INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
nom VARCHAR(255) NOT NULL,
id_sous_album INTEGER REFERENCES sous_album(id_sous_album)
);
INSERT INTO album(id_album, nom) VALUES
('1', 'album1'),
('2', 'album2'),
('3', 'album3')
;
INSERT INTO sous_album(id_sous_album, nom, id_album) VALUES
('1', 'sous album 1', '1'),
('2', 'sous album 2', '1'),
('3', 'sous album 3', '1'),
('4', 'sous album 4', '2'),
('5', 'sous album 5', '2'),
('6', 'sous album 6', '3')
;
INSERT INTO photo(id_photo, nom, id_sous_album) VALUES
('1', 'photo1', '1'),
('2', 'photo2', '1'),
('3', 'photo3', '1'),
('4', 'photo4', '1'),
('5', 'photo5', '2'),
('6', 'photo6', '2'),
('7', 'photo7', '3'),
('8', 'photo8', '3'),
('9', 'photo9', '3'),
('10', 'photo10', '4'),
('11', 'photo11', '5'),
('12', 'photo12', '6'),
('13', 'photo13', '6'),
('14', 'photo14', '6'),
('15', 'photo15', '6')
;
SELECT distinct album.nom, count(photo.id_photo)
from album, photo, sous_album
where album.id_album=sous_album.id_album
AND sous_album.id_sous_album=photo.id_sous_album
group by album.nom
order by album.nom`
this query give me the name of the album and the number of photos but i want also the sous album and their numbers of photo... can u help me ?
the result :
nom | count(photo.id_photo)
album1 | 9
album2 | 2
album3 | 4
i need this result :
nom | count(photo.id_photo)
album1 | 9
sous album 1 | 4
sous album 2 | 2
sous album 3 | 3
album2 | 2
sous album 4 | 1
sous album 5 | 1
album3 | 4
sous album 6 | 4
SELECT x.*
, y.*
FROM
( SELECT a.nom
, COUNT(*) total
FROM album a
JOIN sous_album ap
ON ap.id_album = a.id_album
JOIN photo p
ON p.id_sous_album = ap.id_sous_album
GROUP
BY a.nom
) x
JOIN
( SELECT a.nom nom_album
, ap.nom nom_sous_ablum
, COUNT(*) total
FROM album a
JOIN sous_album ap
ON ap.id_album = a.id_album
JOIN photo p
ON p.id_sous_album = ap.id_sous_album
GROUP
BY a.nom
, ap.nom
) y
ON y.nom_album = x.nom;
Note that this particular query will only return an album if there is at least one sous_album (and photo) associated with it.
thanks for your solution... i find a other one...
SELECT a.nom, sa.nom, COUNT( * ) AS NbPhoto FROM album a INNER JOIN sous_album sa ON sa.id_album = a.id_album INNER JOIN photo p ON p.id_sous_album = sa.id_sous_album GROUP BY a.nom, sa.nom WITH ROLLUP