简体   繁体   中英

MySQL ordering by enum on grouped data

I want to return the most recent action in the timetable that has taken place, grouped by channel- but ordered by enum. The timetable can have 'WEEKDAY' options, or specific days ('WED'). The specific days need to take precedence over the 'WEEKDAY' option.

CREATE TABLE `heatingtimetable` (
  `id` int(11) NOT NULL,
  `channel` enum('CENTRALHEATING','HOTWATER') NOT NULL,
   `command` enum('ON','OFF') NOT NULL DEFAULT 'OFF',
  `thetime` time NOT NULL,
  `day` enum('SUN','MON','TUE','WED','THU','FRI','SAT','WEEKDAY','WEEKEND','HOLIDAY') NOT NULL
);


INSERT INTO `heatingtimetable` (`id`, `channel`, `command`, `thetime`, `day`) VALUES
(1, 'CENTRALHEATING', 'ON', '08:00:00', 'WEEKDAY'),
(2, 'CENTRALHEATING', 'OFF', '10:00:00', 'WEEKDAY'),
(13, 'CENTRALHEATING', 'ON', '07:00:00', 'WED'),
(14, 'CENTRALHEATING', 'OFF', '9:00:00', 'WED');

https://www.db-fiddle.com/#&togetherjs=NEuAL2we4r

I can get back the most recent- but not a custom/enum order as the group by happens first- the most recent time always comes up. Assuming that is it 10.15am on a Wednesday- this brings back WEEKDAY, but needs to bring back 'WED 9am' row 14.

SELECT h.channel, command, h.thetime, day FROM
(SELECT channel, MAX(thetime) as thetime
FROM `heatingtimetable` 
where thetime < '10:15'
AND
(DAYOFWEEK(CURDATE()) >= 2 AND DAYOFWEEK(CURDATE()) <=6 AND day = 'WEEKDAY') || (DAYOFWEEK(CURDATE()) = day)
group by channel) as l
INNER JOIN heatingtimetable h on h.channel = l.channel and h.thetime = l.thetime```

Rethink structure if you can

I managed a solution here below, but wow it isn't pretty. I would really really recommend thinking about structuring this differently so that comparing current day/time to the entries is easier. Hopefully you have control of the table structure and such.

Think first about what query you would like to use. Then you can work from there to find the best table structure to suit such queries. A comparison of datetimes would be a lot friendlier since you can do math that includes both the date and the time. Also mixing different types like WED, FRI, WEEKDAY, WEEKEND and having them as enums with no natural way of ranking them is problematic. That's why this query below is gigantic. If you could get rid of the different types being mixed that would help. If you can't do that, you could perhaps add a new column that helps you rank WED higher than WEEKDAY.

We need a way to rank these

I understand about the WED vs WEEKDAY precedence now. In that case, we can do this by making our own ranking value . We want to rank a matching DAYOFWEEK as higher than a matching WEEKDAY/WEEKEND, etc. And then you also want to rank by time with lower priority than those. So we can order by these 3 different things in that order.

CASE WHEN/THEN/ELSE comes in handy for conditionals.

There are MANY situations to consider here

By the way, your query does not return the most recent like you said in some situations. If today is Saturday, then DAYOFWEEK(CURDATE()) will be 7 and you will have no rows returned. You probably want row 2 to be returned since it would be the most recent. Your query also didn't consider WEEKENDs in any way.

What if the current day matches the table entry's day, but the current time is after the table entry's time? We should not count that entry as matching the day. So we have to include a time check in these ranking values.

Adding on to that- What if today is Friday and that doesn't match the day of any entries... but you have one entry for WEEKDAY and one for THU? Which would be preferred as the most recent? I assume you would want THU to be preferred to WEEKDAY like if it is the same day. And if the choice was between WED and WEEKDAY, then WEEKDAY would be preferred because Thursday is a more recent weekday than WED.

It gets quite complicated.

CURTIME()

Also, I noticed you specified the current day in SQL using CURDATE(), but for the time you just entered a constant value '10:15'. I'm not sure where that comes from, but if you want to do that in SQL also, it would just be - CURTIME(). You can also get both the date and time together with NOW().

Set up the scoring

I created the following ranking values:

day_score1 - This will be 1 if the day of the week matches exactly (like WED/Wednesday) and the table entry time is before the current time, otherwise 0.

day_score2 - This will be 1 if the current day matches WEEKDAY/WEEKEND and the table entry time is before the current time, otherwise 0

day_score3 - This value will be used to go backwards in days and the more recent the day, the higher the score. We have to use subraction here, and we also have to wrap around. A big problem here is that there is no way to get a weekday index number from your day values such as WED. DAYOFWEEK() will take a date and return the weekday index number, but there is no reverse. So we'll have to do a check on each possible day of the week. We won't need to consider the time in this one since we know we are going backwards at least one day.

And if we still want to have WED have higher precedence than WEEKDAY in this situation where we have to look backwards, there have to be even more lines for scoring.

thetime - And lastly I have ordering also by thetime descending because that way if we have to fall back on a previous day and there is more than one entry with that day, it will put the latest one first.

I see you have HOLIDAY there also but I ignored that. This is plenty complex already and I will not bite on that.

Here is my DB Fiddle - https://www.db-fiddle.com/f/wgSzEiWrDgAJuc88de1Fwq/0

CREATE TABLE and INSERTions below-

CREATE TABLE `heatingtimetable` (
  `id` int(11) NOT NULL,
  `channel` enum('CENTRALHEATING','HOTWATER') NOT NULL,
   `command` enum('ON','OFF') NOT NULL DEFAULT 'OFF',
  `thetime` time NOT NULL,
  `day` enum('SUN','MON','TUE','WED','THU','FRI','SAT','WEEKDAY','WEEKEND','HOLIDAY') NOT NULL
);

INSERT INTO `heatingtimetable` (`id`, `channel`, `command`, `thetime`, `day`) VALUES
(1, 'CENTRALHEATING', 'ON', '08:00:00', 'WEEKDAY'),
(2, 'CENTRALHEATING', 'OFF', '10:00:00', 'WEEKDAY'),
(3, 'CENTRALHEATING', 'OFF', '22:00:00', 'WEEKDAY'),
(4, 'CENTRALHEATING', 'ON', '05:00:00', 'MON'),
(5, 'CENTRALHEATING', 'OFF', '16:00:00', 'TUE'),
(6, 'CENTRALHEATING', 'OFF', '23:00:00', 'THU'),
(7, 'CENTRALHEATING', 'OFF', '6:00:00', 'THU'),
(8, 'CENTRALHEATING', 'ON', '07:00:00', 'FRI'),
(9, 'CENTRALHEATING', 'OFF', '21:00:00', 'FRI'),
(13, 'CENTRALHEATING', 'ON', '07:00:00', 'WED'),
(14, 'CENTRALHEATING', 'OFF', '9:00:00', 'WED');

And this gigantic query-

SELECT 

channel, command, thetime, day, 

CASE WHEN (DAYOFWEEK(CURDATE()) = day AND thetime <= CURTIME()) THEN 1 ELSE 0 END AS day_score1, 
                    
CASE WHEN 
(
    (
    (DAYOFWEEK(CURDATE()) >= 2 AND DAYOFWEEK(CURDATE()) <= 6 AND day = 'WEEKDAY') 
    OR 
    ((DAYOFWEEK(CURDATE()) = 1 OR DAYOFWEEK(CURDATE()) = 7) AND day = 'WEEKEND')
    )
    AND thetime <= CURTIME()
) THEN 1 ELSE 0 END as day_score2, 

CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 6 AND day = 'SAT' THEN 12 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 5 AND day = 'SUN' THEN 12 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 4 AND day = 'MON' THEN 12 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 3 AND day = 'TUE'  THEN 12 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 2 AND day = 'WED' THEN 12 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 1 AND day = 'THU' THEN 12 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 0 AND day = 'FRI' THEN 12 ELSE 0 END 
+
CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 6 AND day = 'FRI' THEN 10 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 5 AND day = 'SAT' THEN 10 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 4 AND day = 'SUN' THEN 10 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 3 AND day = 'MON' THEN 10 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 2 AND day = 'TUE' THEN 10 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 1 AND day = 'WED' THEN 10 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 0 AND day = 'THU' THEN 10 ELSE 0 END  
+
CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 6 AND day = 'THU' THEN 8 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 5 AND day = 'FRI' THEN 8 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 4 AND day = 'SAT' THEN 8 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 3 AND day = 'SUN' THEN 8 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 2 AND day = 'MON' THEN 8 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 1 AND day = 'TUE' THEN 8 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 0 AND day = 'WED' THEN 8 ELSE 0 END  
+
CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 6 AND day = 'WED' THEN 6 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 5 AND day = 'THU' THEN 6 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 4 AND day = 'FRI' THEN 6 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 3 AND day = 'SAT' THEN 6 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 2 AND day = 'SUN' THEN 6 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 1 AND day = 'MON' THEN 6 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 0 AND day = 'TUE' THEN 6 ELSE 0 END  
+
CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 6 AND day = 'TUE' THEN 4 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 5 AND day = 'WED' THEN 4 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 4 AND day = 'THU' THEN 4 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 3 AND day = 'FRI' THEN 4 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 2 AND day = 'SAT' THEN 4 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 1 AND day = 'SUN' THEN 4 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 0 AND day = 'MON' THEN 4 ELSE 0 END  
+
CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 6 AND day = 'MON' THEN 2 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 5 AND day = 'TUE' THEN 2 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 4 AND day = 'WED' THEN 2 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 3 AND day = 'THU' THEN 2 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 2 AND day = 'FRI' THEN 2 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 1 AND day = 'SAT' THEN 2 ELSE 0 END 
+ CASE WHEN (7 - DAYOFWEEK(CURDATE())) = 0 AND day = 'SUN' THEN 2 ELSE 0 END  

+ 
CASE 
    WHEN (7 - DAYOFWEEK(CURDATE())) <= 4 AND day = 'WEEKDAY' THEN 11 
    WHEN (7 - DAYOFWEEK(CURDATE())) >= 5 AND day = 'WEEKEND' THEN 11 
    WHEN (7 - DAYOFWEEK(CURDATE()) >= 6 OR 7 - DAYOFWEEK(CURDATE()) <= 3) AND day = 'WEEKDAY' THEN 9 
    WHEN (7 - DAYOFWEEK(CURDATE()) = 5 OR 7 - DAYOFWEEK(CURDATE()) = 4) AND day = 'WEEKEND' THEN 9 
    WHEN (7 - DAYOFWEEK(CURDATE()) >= 5 OR 7 - DAYOFWEEK(CURDATE()) <= 2) AND day = 'WEEKDAY' THEN 7 
    WHEN (7 - DAYOFWEEK(CURDATE()) = 4 OR 7 - DAYOFWEEK(CURDATE()) = 3) AND day = 'WEEKEND' THEN 7 
    WHEN (7 - DAYOFWEEK(CURDATE()) >= 4 OR 7 - DAYOFWEEK(CURDATE()) <= 1) AND day = 'WEEKDAY' THEN 5 
    WHEN (7 - DAYOFWEEK(CURDATE()) = 3 OR 7 - DAYOFWEEK(CURDATE()) = 2) AND day = 'WEEKEND' THEN 5 
    WHEN (7 - DAYOFWEEK(CURDATE()) >= 3 OR 7 - DAYOFWEEK(CURDATE()) <= 0) AND day = 'WEEKDAY' THEN 3 
    WHEN (7 - DAYOFWEEK(CURDATE()) = 2 OR 7 - DAYOFWEEK(CURDATE()) = 1) AND day = 'WEEKEND' THEN 3 
    WHEN (7 - DAYOFWEEK(CURDATE()) >= 2) AND day = 'WEEKDAY' THEN 1 
    WHEN (7 - DAYOFWEEK(CURDATE()) <= 1) AND day = 'WEEKEND' THEN 1 
    ELSE 0 
END 

AS day_score3

FROM heatingtimetable 

ORDER BY day_score1 DESC, day_score2 DESC, day_score3 DESC, thetime DESC

My result right now is below. It's Friday and current time at DB-Fiddle is showing as 20:41:40.

channel command thetime day day_score1 day_score2 day_score3
CENTRALHEATING ON 07:00:00 FRI 1 0 0
CENTRALHEATING OFF 10:00:00 WEEKDAY 0 1 11
CENTRALHEATING ON 08:00:00 WEEKDAY 0 1 11
CENTRALHEATING OFF 23:00:00 THU 0 0 12
CENTRALHEATING OFF 06:00:00 THU 0 0 12
CENTRALHEATING OFF 22:00:00 WEEKDAY 0 0 11
CENTRALHEATING OFF 09:00:00 WED 0 0 10
CENTRALHEATING ON 07:00:00 WED 0 0 10
CENTRALHEATING OFF 16:00:00 TUE 0 0 8
CENTRALHEATING ON 05:00:00 MON 0 0 6
CENTRALHEATING OFF 21:00:00 FRI 0 0 0

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM