Search code examples
sqldatetimeweekday

How can I know the number of remaining mondays, tuesdays, wednesdays, ..., and sundays in a month using SQL?


I want to update the values of a row that has a date column and I want to generate the column values of RemainingMondays, RemainingTuesdays, etc. in that month. For example if I have 2019-03-20 (March 20th, 2019) I need to update that row with the next column values. RemainingMondays -> 2, RemainingTuesdays-> 1, RemainingWednesdays -> 1, RemainingThursdays -> 2, RemainingFridays -> 2, RemainingSaturdays -> 2, RemainingSundays -> 2, (as you can see) I don't want to count the weekday in that day (It means that in the last day of the month all the column values will have a value of 0). How to do this in SQL?


Solution

  • I tested this solution with MySQL, but it should works in any SQL engine.

    Test data:

    CREATE TABLE test_date (
      date date NOT NULL
    );
    
    INSERT INTO test_date VALUES
    ('2019-03-01'),
    ('2019-03-20'),
    ('2019-03-21'),
    ('2019-03-22'),
    ('2019-03-23'),
    ('2019-03-24'),
    ('2019-03-25'),
    ('2019-03-26');
    

    The query to get your data:

    SELECT 
        td2.date,
        /* 
        To calculate remaining of a weekday, you can then use this formula:
    
        if desired_week_day_number > week_day_number
           and desired_week_day_number <= (week_day_number + remaining_days_after_weeks):
            remaining_weeks + 1
        elseif desired_week_day_number <= (week_day_number + remaining_days_after_weeks - 7):
            remaining_weeks + 1
        else remaining_weeks
        */
        CASE WHEN (1 > td2.week_day_number AND 1 <= (td2.week_day_number + td2.remaining_days_after_weeks))
                  OR 1 <= (td2.week_day_number + td2.remaining_days_after_weeks - 7)
             THEN td2.remaining_weeks + 1
             ELSE td2.remaining_weeks
        END remaining_sundays,
        CASE WHEN (2 > td2.week_day_number AND 2 <= (td2.week_day_number + td2.remaining_days_after_weeks))
                  OR 2 <= (td2.week_day_number + td2.remaining_days_after_weeks - 7)
             THEN td2.remaining_weeks + 1
             ELSE td2.remaining_weeks
        END remaining_mondays,
        CASE WHEN (3 > td2.week_day_number AND 3 <= (td2.week_day_number + td2.remaining_days_after_weeks))
                  OR 3 <= (td2.week_day_number + td2.remaining_days_after_weeks - 7)
             THEN td2.remaining_weeks + 1
             ELSE td2.remaining_weeks
        END remaining_tuesdays,
        CASE WHEN (4 > td2.week_day_number AND 4 <= (td2.week_day_number + td2.remaining_days_after_weeks))
                  OR 4 <= (td2.week_day_number + td2.remaining_days_after_weeks - 7)
             THEN td2.remaining_weeks + 1
             ELSE td2.remaining_weeks
        END remaining_wednesdays,
        CASE WHEN (5 > td2.week_day_number AND 5 <= (td2.week_day_number + td2.remaining_days_after_weeks))
                  OR 5 <= (td2.week_day_number + td2.remaining_days_after_weeks - 7)
             THEN td2.remaining_weeks + 1
             ELSE td2.remaining_weeks
        END remaining_thursdays, 
        CASE WHEN (6 > td2.week_day_number AND 6 <= (td2.week_day_number + td2.remaining_days_after_weeks))
                  OR 6 <= (td2.week_day_number + td2.remaining_days_after_weeks - 7)
             THEN td2.remaining_weeks + 1
             ELSE td2.remaining_weeks
        END remaining_fridays,
        CASE WHEN (7 > td2.week_day_number AND 7 <= (td2.week_day_number + td2.remaining_days_after_weeks))
                  OR 7 <= (td2.week_day_number + td2.remaining_days_after_weeks - 7)
             THEN td2.remaining_weeks + 1
             ELSE td2.remaining_weeks
        END remaining_saturdays,
        td2.*
    FROM (
        SELECT
            td.date,
            day(td.date) day_number,
            dayofweek(td.date) week_day_number,
            dayname(td.date) week_day_name,
            day(last_day(td.date)) nb_month_days,
            day(last_day(td.date)) - day(td.date) remaining_days,
            floor((day(last_day(td.date)) - day(td.date)) / 7) remaining_weeks,
            day(last_day(td.date)) - day(td.date) -
                floor((day(last_day(td.date)) - day(td.date)) / 7) * 7 remaining_days_after_weeks
        FROM test_date td
    ) AS td2
    ORDER BY td2.date;
    

    You can plug the exact same thing in an update statement accordingly.

    It can of course be made way shorter/simpler if you put the weekdays in a sub-query / sub-table, avoiding to repeat the formula for each day of the week, but I let this exercice to you for now. ;-) The hard of the work was mainly to figure out how to calculate the remaining of a given weekday.