views:

207

answers:

2

i have a table that lists the opening hours of restaurants. the columns are id, eateries_id, day_of_week, start_time, and end_time. each eatery is represented in the table multiple times because there is a separate entry for each day. see this previous question for more details: http://stackoverflow.com/questions/3070295/determine-if-a-restaurant-is-open-now-like-yelp-does-using-database-php-js

i'm wondering now how to take the data from this table and print it out in a human readable format. for example, instead of saying "M 1-3, T 1-3, W 1-3, Th 1-3, F 1-8" i would like to say "M-Th 1-3, F 1-8". similarly, i want "M 1-3, 5-8" instead of "M 1-3, M 5-8". how might i do this without a brute force method of numerous if statements?

thanks.

+2  A: 

Thought I would have a bash at this.

Test Table

CREATE TABLE `opening_hours` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `eateries_id` int(11) DEFAULT NULL,
  `day_of_week` int(11) DEFAULT NULL,
  `start_time` time DEFAULT NULL,
  `end_time` time DEFAULT NULL,
  PRIMARY KEY (`id`)
) 

Test Data

INSERT INTO `test`.`opening_hours`
(
`eateries_id`,
`day_of_week`,
`start_time`,
`end_time`)
SELECT 2 AS eateries_id, 1 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 2 AS eateries_id, 1 AS day_of_week, '17:00' AS start_time, '20:00' as end_time union all
SELECT 2 AS eateries_id, 2 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 2 AS eateries_id, 2 AS day_of_week, '17:00' AS start_time, '20:00' as end_time union all
SELECT 2 AS eateries_id, 3 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 2 AS eateries_id, 4 AS day_of_week, '13:00' AS start_time, '20:00' as end_time union all
SELECT 2 AS eateries_id, 5 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 2 AS eateries_id, 6 AS day_of_week, '13:00' AS start_time, '20:00' as end_time union all
SELECT 2 AS eateries_id, 7 AS day_of_week, '13:00' AS start_time, '21:00' as end_time
                                                                       union all
SELECT 3 AS eateries_id, 1 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 3 AS eateries_id, 2 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 3 AS eateries_id, 3 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 3 AS eateries_id, 4 AS day_of_week, '13:00' AS start_time, '20:00' as end_time union all
SELECT 3 AS eateries_id, 5 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 3 AS eateries_id, 6 AS day_of_week, '13:00' AS start_time, '20:00' as end_time union all
SELECT 3 AS eateries_id, 7 AS day_of_week, '13:00' AS start_time, '21:00'  as end_time

View definition to consolidate opening hours by day

CREATE VIEW `test`.`groupedhours` 
AS 
  select `test`.`opening_hours`.`eateries_id` AS `eateries_id`,
         `test`.`opening_hours`.`day_of_week` AS `day_of_week`,
         group_concat(concat(date_format(`test`.`opening_hours`.`start_time`,'%l'),' - ',date_format(`test`.`opening_hours`.`end_time`,'%l %p')) order by `test`.`opening_hours`.`start_time` ASC separator ', ') AS `OpeningHours` 
         from `test`.`opening_hours` 
         group by `test`.`opening_hours`.`eateries_id`,`test`.`opening_hours`.`day_of_week`

Query to find the 'islands' of contiguous days with the same opening hours (based on one by Itzik Ben Gan)

SET @rownum = NULL;
SET @rownum2 = NULL;



SELECT S.eateries_id, 
concat(CASE WHEN 
S.day_of_week <> E.day_of_week 
    THEN 
    CONCAT(CASE S.day_of_week 
             WHEN 1 THEN 'Su'
             WHEN 2 THEN 'Mo'     
             WHEN 3 THEN 'Tu'     
             WHEN 4 THEN 'We'
             WHEN 5 THEN 'Th'    
             WHEN 6 THEN 'Fr'    
             WHEN 7 THEN 'Sa'  
            End, ' - ')
    ELSE ''        
END,
CASE E.day_of_week 
     WHEN 1 THEN 'Su'
     WHEN 2 THEN 'Mo'     
     WHEN 3 THEN 'Tu'     
     WHEN 4 THEN 'We'
     WHEN 5 THEN 'Th'    
     WHEN 6 THEN 'Fr'    
     WHEN 7 THEN 'Sa'  
End, ' ', S.OpeningHours) AS `Range`
FROM (

SELECT 
    A.day_of_week,
    @rownum := IFNULL(@rownum, 0) + 1  AS rownum,
    A.eateries_id,
    A.OpeningHours
FROM `test`.`groupedhours` as A
WHERE NOT EXISTS(SELECT * FROM `test`.`groupedhours` B
                 WHERE A.eateries_id = B.eateries_id
                  AND A.OpeningHours = B.OpeningHours
                  AND B.day_of_week = A.day_of_week -1) 
ORDER BY eateries_id,day_of_week) AS S

JOIN (
SELECT 
    A.day_of_week,
    @rownum2 := IFNULL(@rownum2, 0) + 1  AS rownum,
    A.eateries_id,
    A.OpeningHours
FROM `test`.`groupedhours` as A 
WHERE NOT EXISTS(SELECT * FROM `test`.`groupedhours` B
                 WHERE A.eateries_id = B.eateries_id
                  AND A.OpeningHours = B.OpeningHours
                  AND B.day_of_week = A.day_of_week + 1)
ORDER BY eateries_id,day_of_week) AS E

ON  S.eateries_id = E.eateries_id AND
    S.OpeningHours = S.OpeningHours AND 
    S.rownum = E.rownum

Results

eateries_id             Range
2                Su - Mo 1 - 3 PM, 5 - 8 PM
2                Tu 1 - 3 PM
2                We 1 - 8 PM
2                Th 1 - 3 PM
2                Fr 1 - 8 PM
2                Sa 1 - 9 PM
3                Su - Tu 1 - 3 PM
3                We 1 - 8 PM
3                Th 1 - 3 PM
3                Fr 1 - 8 PM
3                Sa 1 - 9 PM
Martin Smith
This is interesting. My opinion is that SQL should get you the data fast because that is what it is best at. The massaging of it is best done (I think) using a higher-level language. Your SQL is impressive though.
Hamish Grubijan
+2  A: 
Hamish Grubijan
Here I was assuming that Mon = 1, Tue = 2, ... Sun = 7 because this is a convention in some countries - the week starts on Monday and ends on Sunday. http://www.cjvlang.com/Dow/SunMon.html
Hamish Grubijan
i think i understand what you're saying.. though wouldn't there be over a hundred combinations of days that i'd have to hard code?
vee
Not quite, 2^5 = 32; 2^7 = 128 :) These can be generated, however.
Hamish Grubijan