Order rows by column whilst keeping rows with a common column together - php

I'm running MariaDB 5.5 which is equivalent to MySQL 5.5.
I have the following test data.
CREATE TABLE `dev_test` (
`id` INT NOT NULL AUTO_INCREMENT ,
`date` DATE NOT NULL ,
`venue` INT NOT NULL ,
PRIMARY KEY (`id`)
) ENGINE = InnoDB;
INSERT INTO `dev_test` (`id`, `date`, `venue`) VALUES (NULL, '2019-08-01', '2'),
(NULL, '2019-09-01', '1'), (NULL, '2019-10-01', '2');
INSERT INTO `dev_test` (`id`, `date`, `venue`) VALUES (NULL, '2019-11-01', '3');
I wish to order the venues and the events so that the venue of the next event is first, then all other events at that venue. Then the next event that's not all ready listed.
So with this data I want:
Event ID 1 - 2019-08-01 Venue 2
Event ID 3 - 2019-10-01 Venue 2
Event ID 2 - 2019-09-01 Venue 1
Event ID 4 - 2019-11-01 Venue 3
I could just grab all the events in any order then order them using PHP.
Or I could select with
SELECT venue FROM `dev_test` GROUP BY venue ORDER BY date;
Then using PHP get the venues one at a time ordered by date
SELECT * FROM `dev_test` WHERE venue = 2 ORDER BY date;
SELECT * FROM `dev_test` WHERE venue = 1 ORDER BY date;
But is there a nice way in pure MySQL (MariaDB) to do this?
Maybe some way of giving all venues a temp date column that is the same as the earliest date at that venue?
Or should I just do it in PHP?
Sorry for the title gore. I tried to make "Order events by date whilst keeping events at the same location together" more generic.

Try this query, I think this helps you
SELECT t2.* FROM
(SELECT venue FROM dev_test GROUP BY venue ORDER BY date) AS t1
LEFT JOIN (SELECT * FROM dev_test ORDER BY date) AS t2 ON t1.venue=t2.venue
Here result

You can use window functions with CTEs to achieve this (note: Requires MySQL 8+:
WITH added_initial_date
AS (SELECT id,
date,
venue,
FIRST_VALUE(date)
OVER(
PARTITION BY venue
ORDER BY date) AS 'initial_date'
FROM dev_test),
ranked
AS (SELECT id,
date,
venue,
initial_date,
RANK()
OVER(
ORDER BY initial_date ASC) AS position
FROM added_initial_date)
SELECT id,
date,
venue
FROM ranked
ORDER BY position ASC, date ASC;
Maybe it is not most effective, but it works.
UPDATE: added ordering by date to get closest record first. Here is db-fiddle: https://www.db-fiddle.com/f/puYqhDF53ocRiRys61XH53/0

You can try below - using order by venue desc, date asc
SELECT * FROM `dev_test`
order by venue desc, date asc

E.g. :
SELECT a.*
FROM dev_test a
LEFT
JOIN (SELECT * FROM dev_test ORDER BY date DESC LIMIT 1) b
ON b.venue = a.venue
ORDER
BY b.id IS NULL, date DESC;
This assumes dates are UNIQUE, as per the sample data

Related

MySQL: Use my defined aggregated field in sub select

I have a query that want to compute each user_id's rank in comparison to all all other user_id real time. Real-time means when query is executing:
My query is as below, but unfortunately computed rank field is not as desired:
SELECT user_id, SUM(COALESCE(`duration`, 0)) AS duration_total,
(SELECT COUNT(*)
FROM `forms`
GROUP BY `user_id`
HAVING SUM(`duration`) > duration_total
) AS rank
FROM `forms`
GROUP BY `user_id`
The problem is on Having condition in the internal select. Where I want to count user_id that have more duration than current user_id.

Simplify my mysql query

I currently have the following tables with:
TABLE klusbonnen_deelnemers:
bonnummer (varchar) - order number
adres (varchar) - order adres
deelnemer (varchar) - user
binnen (date) - date order received
klaar (date) - original order milestone
datum_gereed (date) - date order completed
gereed (varchar) - YES or NO (YES= completed NO= Not yet completed)
datum_factuur (date) - date when user marked order completed (button clicked)
factuur (varchar) - weeknumber order completed
One order(bonnummer) can have multiple users (deelnemer) who all have to mark the order "completed" (datum_gereed). Only when ALL users (deelnemer) have marked an order (bonnummer) "completed" (datum_gereed) the order IS "completed".
I am trying to write a query that gives me:
All completed orders (bonnummer) in a given timespan (last month).
However...
The completion date (datum_gereed) should hold the LAST date (as that is the actual total completion date).
The list should have the Order (bonnummer) with the latest "marked completed" date (datum_factuur) on top (sort DESC) (of course only when all users (deelnemer) have completed the order (all users(deelnemers) having gereed="YES")
So far i have this:
SELECT DISTINCT tbl1.bonnummer AS 'KLUSBONNUMMER', tbl1.adres AS 'ADRES',
tbl1.binnen AS 'BINNENGEKOMEN OP', tbl1.klaar AS 'ORIGINELE STREEFDATUM',
tbl1.datum_gereed AS 'GEREEDGEKOMEN OP', tbl1.factuur AS 'WEEKNUMMER'
FROM klusbonnen_deelnemers AS tbl1
INNER JOIN
( SELECT tbl2.bonnummer
FROM klusbonnen_deelnemers AS tbl2
WHERE tbl2.bonnummer NOT IN (
SELECT tbl3.bonnummer
FROM klusbonnen_deelnemers AS tbl3
WHERE tbl3.gereed = 'NEE')
) AS tbl4 ON tbl1.bonnummer = tbl4.bonnummer
INNER JOIN
( SELECT bonnummer, MAX(datum_gereed) AS 'MAXDATUM'
FROM klusbonnen_deelnemers
GROUP BY bonnummer
) MAXFILTER ON tbl1.bonnummer = MAXFILTER.bonnummer
AND tbl1.datum_gereed = MAXFILTER.MAXDATUM
WHERE tbl1.datum_factuur BETWEEN NOW() - INTERVAL 2 MONTH AND NOW()
ORDER BY tbl1.bonnummer DESC
This query DOES work, however i think this can be done in a much simpler way.
On top of that the query only works in my navicat editor. Calling this query on my "live" website gives an error (subquery in WHERE clause...) (i do have all login correct as other queries DO work).
Anyone out there who can help (simplify) this query? Thx...
this part:
INNER JOIN (SELECT tbl2.bonnummer
FROM klusbonnen_deelnemers AS tbl2
WHERE tbl2.bonnummer NOT IN
(SELECT tbl3.bonnummer
FROM klusbonnen_deelnemers AS tbl3
WHERE tbl3.gereed = 'NEE')) AS tbl4
ON tbl1.bonnummer = tbl4.bonnummer
seems like useless. try to use gereed <> 'NEE' in the "very-bottom"-WHERE
SELECT DISTINCT
kd.bonnummer AS 'KLUSBONNUMMER',
kd.adres AS 'ADRES',
kd.binnen AS 'BINNENGEKOMEN OP',
kd.klaar AS 'ORIGINELE STREEFDATUM',
kd.datum_gereed AS 'GEREEDGEKOMEN OP',
kd.factuur AS 'WEEKNUMMER'
FROM klusbonnen_deelnemers AS kd
INNER JOIN (
SELECT bonnummer, MAX(datum_gereed) AS 'MAXDATUM'
FROM klusbonnen_deelnemers
GROUP BY bonnummer
) AS MAXFILTER
ON (kd.bonnummer = MAXFILTER.bonnummer AND kd.datum_gereed = MAXFILTER.MAXDATUM)
WHERE
kd.gereed <> 'NEE'
kd.datum_factuur BETWEEN NOW() - INTERVAL 2 MONTH AND NOW()
ORDER BY
kd.bonnummer DESC

How to get unique records based on one column in mysql

I have a table in which subscription_id is used multiple times, every month a customer charged an order to be generated with same data & unique order id so there is a column 'updated_at' which also updates so I want to get only one (1) latest record from table based on subscription_id, I am using following query, but it's not working and getting old record.
SELECT *
FROM members_subscriptions
WHERE status = 'active'
GROUP BY subscription_id
ORDER BY updated_at DESC
Can any one tell me what's wrong with this query ....
You need get the MAX() of updated_at , order by runs after grouping so you need to compare with the newest date
SELECT DISTINCT *
FROM members_subscriptions
WHERE status = 'active'
AND updated_at IN(
SELECT MAX(updated_at)
FROM members_subscriptions
GROUP BY subscription_id
)
ORDER BY updated_at DESC
or
SELECT DISTINCT m.*
FROM members_subscriptions m
INNER JOIN (
SELECT subscription_id, MAX(updated_at) AS updated_at
FROM members_subscriptions GROUP BY subscription_id
) AS maxdatte USING (subscription_id, updated_at);
Assumming that each subsciption can have only unique dates in updated_at, then this query should give desired result:
SELECT *
FROM members_subscriptions
WHERE status = 'active'
AND (subscription_id, updated_at) IN(
SELECT subscription_id, MAX(updated_at)
FROM members_subscriptions
GROUP BY subscription_id
)
ORDER BY updated_at DESC
But if one subscription may have more that one same dates, then the query must use unique id to get only one record from two or more with the same date:
SELECT *
FROM members_subscriptions m
WHERE status = 'active'
AND id = (
SELECT id FROM members_subscriptions m1
WHERE m.subscription_id = m1.subscription_id
AND status = 'active'
ORDER BY updated_at DESC
LIMIT 1
)
SELECT distinct (name)
FROM members_subscriptions
WHERE status = 'active'
GROUP BY subscription_id
ORDER BY updated_at DESC
use distinct with field name you want to find
The problem with your query is that result is first being grouped and only then ordered. If you need latest result, you need to do something like this:
SELECT *
FROM members_subscriptions as main
WHERE status = `active` AND `updated_at` = (
SELECT MAX(`updated_at`) FROM members_subscriptions as sub
WHERE status='active' and main.`subscription_id` = sub.`subscription_id`
)

How to order this specific Inner Joins?

Right now I'm creating an online game where I list the last transfers of players.
The table that handles the history of players, has the columns history_join_date and history_end_date.
When history_end_date is filled, it means that player left a club, and when it is like the default (0000-00-00 00:00:00) and history_join_date has some date it means player joined the club (in that date).
Right now, I've the following query:
SELECT
player_id,
player_nickname,
team_id,
team_name,
history_join_date,
history_end_date
FROM
players
INNER JOIN history
ON history.history_user_id = players.player_id
INNER JOIN teams
ON history.history_team_id = teams.team_id
ORDER BY
history_end_date DESC,
history_join_date DESC
LIMIT 7
However, this query returns something like (filtered with PHP above):
(22-Aug-2012 23:05): Folha has left Portuguese Haxball Team.
(22-Aug-2012 00:25): mancini has left United.
(21-Aug-2012 01:29): PatoDaOldSchool has left Reign In Power.
(22-Aug-2012 23:37): Master has joined Born To Win.
(22-Aug-2012 23:28): AceR has joined Born To Win.
(22-Aug-2012 23:08): Nasri has joined Porto Club of Haxball.
(22-Aug-2012 18:53): Lloyd Banks has joined ARRIBA.
PHP Filter:
foreach ($transfers as $transfer) {
//has joined
if($transfer['history_end_date']<$transfer['history_join_date']) {
$type = ' has joined ';
$date = date("d-M-Y H:i", strtotime($transfer['history_join_date']));
} else {
$type = ' has left ';
$date = date("d-M-Y H:i", strtotime($transfer['history_end_date']));
}
As you can see, in the transfers order, the date is not being followed strictly (22-Aug => 21-Aug => 22-Aug).
What am I missing in the SQL?
Regards!
The issue is you are ordering based upon two different values. So your results are ordered first by history_end_date, and when the end dates are equal (i.e. when it is the default value), they are then ordered by history_join_date
(Note that your first results are all ends, and then your subsequent results are all joins, and each subset is properly ordered).
How much control do you have over this data structure? You might be able to restructure the history table such that there is only a single date, and a history type of JOINED or END... You might be able to make a view of joined_date and end_date and sort across that...
From what you have in the question I made up the following DDL & Data:
create table players (
player_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
player_nickname VARCHAR(255) NOT NULL UNIQUE
);
create table teams (
team_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
team_name VARCHAR(255) NOT NULL UNIQUE
);
create table history (
history_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
history_user_id INT NOT NULL, history_team_id INT NOT NULL,
history_join_date DATETIME NOT NULL,
history_end_date DATETIME NOT NULL DEFAULT "0000-00-00 00:00:00"
);
insert into players VALUES
(1,'Folha'),
(2,'mancini'),
(3,'PatoDaOldSchool'),
(4,'Master'),
(5,'AceR'),
(6,'Nasri'),
(7,'Lloyd Banks');
insert into teams VALUES
(1,'Portuguese Haxball Team'),
(2,'United'),
(3,'Reign In Power'),
(4,'Born To Win'),
(5,'Porto Club of Haxball'),
(6,'ARRIBA');
insert into history VALUES
(DEFAULT,1,1,'2012-08-01 00:04','2012-08-22 23:05'),
(DEFAULT,2,2,'2012-08-21 19:04','2012-08-22 00:25'),
(DEFAULT,3,3,'2012-08-19 01:29','2012-08-21 01:29'),
(DEFAULT,4,4,'2012-08-22 23:37',DEFAULT),
(DEFAULT,5,4,'2012-08-22 23:28',DEFAULT),
(DEFAULT,6,5,'2012-08-22 23:08',DEFAULT),
(DEFAULT,7,6,'2012-08-22 18:53',DEFAULT);
SOLUTION ONE - History Event View
This is obviously not the only solution (and you'd have to evaluate options as they suit your needs, but you could create a view in MySQL for your history events and join to it and use it for ordering similar to the following:
create view historyevent (
event_user_id,
event_team_id,
event_date,
event_type
) AS
SELECT
history_user_id,
history_team_id,
history_join_date,
'JOIN'
FROM history
UNION
SELECT
history_user_id,
history_team_id,
history_end_date,
'END'
FROM history
WHERE history_end_date <> "0000-00-00 00:00:00";
Your select then becomes:
SELECT
player_id,
player_nickname,
team_id,
team_name,
event_date,
event_type
FROM players
INNER JOIN historyevent
ON historyevent.event_user_id = players.player_id
INNER JOIN teams
ON historyevent.event_team_id = teams.team_id
ORDER BY
event_date DESC;
Benefit here is you can get both joins and leaves for the same player.
SOLUTION TWO - Pseudo column. use the IF construction to pick one or the other column.
SELECT
player_id,
player_nickname,
team_id,
team_name,
history_join_date,
history_end_date,
IF(history_end_date>history_join_date,history_end_date,history_join_date) as order_date
FROM
players
INNER JOIN history
ON history.history_user_id = players.player_id
INNER JOIN teams
ON history.history_team_id = teams.team_id
ORDER BY
order_date DESC;
Building from #Barmar's answer, you can also use GREATEST() to pick the greatest of the arguments. (MAX() is a grouping function... not actually what you're looking for)
I think what you want is:
ORDER BY MAX(history_join_date, history_end_date)

MySQL query for selecting a maximum element

I have a table with 4 columns: place_id, username, counter, last_checkin
I'm writing a check-in based system and I'm trying to get a query that will give me the "mayor" of each place. The mayor is the one with most check-ins, and if there is more than 1 than the minimum last_checkin wins.
For example, if I have:
place_id, username, counter, last_checkin
123, tom, 3 , 13/4/10
123, jill, 3, 14/4/10
365, bob, 2, 15/4/10
365, alice, 1, 13/4/10
I want the result to be:
123, tom
365, bob
I'm using it in PHP code
Here is the test data:
CREATE TABLE `my_table` ( `place_id` int(11), `username` varchar(50), `counter` int(11), `last_checkin` date);
INSERT INTO `my_table` VALUES (123,'tom',3,'2010-04-13'),(123,'jill',3,'2010-04-14'),(365,'bob',2,'2010-04-15'),(365,'alice',1,'2010-04-13');
How about..
SELECT
place_id,
(SELECT username
FROM my_table MT2
WHERE MT2.place_id = MT1.place_id
ORDER BY counter DESC, last_checkin ASC
LIMIT 1) AS mayor
FROM my_table MT1
GROUP BY place_id;
Edited as Unreason suggests to have ascending order for last_checkin.
Brian's correlated query is something I would write. However I found this different take and it might perform differently depending on the data
SELECT
mt1.place_id,
mt1.username
FROM
my_table mt1 LEFT JOIN my_table mt2
ON mt1.place_id = mt2.place_id AND
(mt1.counter < mt2.counter OR
(mt1.counter = mt2.counter AND mt1.last_checkin > mt2.last_checkin)
)
WHERE
mt2.place_id IS NULL
Which uses left join to get to the top records according to certain conditions.
$data = query("SELECT max(counter) counter,username FROM table GROUP By place_id ORDER By last_checkin DESC");

Categories