Recipe9.7.Creating a Calendar


Recipe 9.7. Creating a Calendar

Problem

You want to create a calendar for the current month. The calendar should be formatted like a calendar you might have on your desk seven columns across, (usually) five rows down.

Solution

Each solution will look a bit different, but they all solve the problem the same way: return each day for the current month, and then pivot on the day of the week for each week in the month to create a calendar.

There are different formats available for calendars. For example, the Unix cal command formats the days from Sunday to Saturday. The examples in this recipe are based on ISO weeks, so the Monday through Friday format is the most convenient to generate. Once you become comfortable with the solutions, you'll see that reformatting however you like is simply a matter of modifying the values assigned by the ISO week before pivoting.

As you begin to use different types of formatting with SQL to create readable output, you will notice your queries becoming longer. Don't let those long queries intimidate you; the queries presented for this recipe are extremely simple once broken down and run piece by piece.


DB2

Use the recursive WITH clause to return every day in the current month. Then pivot on the day of the week using CASE and MAX:

  1   with x(dy,dm,mth,dw,wk)  2   as (  3 select (current_date -day(current_date) day +1 day) dy,  4         day((current_date -day(current_date) day +1 day)) dm,  5         month(current_date) mth,  6         dayofweek(current_date -day(current_date) day +1 day) dw,  7         week_iso(current_date -day(current_date) day +1 day) wk  8   from t1  9  union all 10 select dy+1 day, day(dy+1 day), mth, 11         dayofweek(dy+1 day), week_iso(dy+1 day) 12   from x 13  where month(dy+1 day) = mth 14  ) 15  select max(case dw when 2 then dm end) as Mo, 16         max(case dw when 3 then dm end) as Tu, 17         max(case dw when 4 then dm end) as We, 18         max(case dw when 5 then dm end) as Th, 19         max(case dw when 6 then dm end) as Fr, 20         max(case dw when 7 then dm end) as Sa, 21         max(case dw when 1 then dm end) as Su 22   from x 23  group by wk 24  order by wk 

Oracle

Use the recursive CONNECT BY clause to return each day in the current month. Then pivot on the day of the week using CASE and MAX:

  1  with x  2    as (  3 select *  4   from (  5 select to_char(trunc(sysdate,'mm')+level-1,'iw') wk,  6        to_char(trunc(sysdate,'mm')+level-1,'dd') dm,  7        to_number(to_char(trunc(sysdate,'mm')+level-1,'d')) dw,  8        to_char(trunc(sysdate,'mm')+level-1,'mm') curr_mth,  9        to_char(sysdate,'mm') mth 10   from dual 11  connect by level <= 31 12        ) 13  where curr_mth = mth 14 ) 15 select max(case dw when 2 then dm end) Mo, 16        max(case dw when 3 then dm end) Tu, 17        max(case dw when 4 then dm end) We, 18        max(case dw when 5 then dm end) Th, 19        max(case dw when 6 then dm end) Fr, 20        max(case dw when 7 then dm end) Sa, 21        max(case dw when 1 then dm end) Su 22   from x 23  group by wk 24  order by wk 

PostgreSQL

Use the function GENERATE_SERIES to return every day in the current month. Then pivot on the day of the week using MAX and CASE:

  1 select max(case dw when 2 then dm end) as Mo,  2        max(case dw when 3 then dm end) as Tu,  3        max(case dw when 4 then dm end) as We,  4        max(case dw when 5 then dm end) as Th,  5        max(case dw when 6 then dm end) as Fr,  6        max(case dw when 7 then dm end) as Sa,  7        max(case dw when 1 then dm end) as Su  8   from (  9 select * 10   from ( 11 select cast(date_trunc('month',current_date) as date)+x.id, 12        to_char( 13           cast( 14     date_trunc('month',current_date) 15                as date)+x.id,'iw') as wk, 16        to_char( 17           cast( 18     date_trunc('month',current_date) 19                as date)+x.id,'dd') as dm, 20        cast( 21     to_char( 22        cast( 23   date_trunc('month',current_date) 24                 as date)+x.id,'d') as integer) as dw, 25         to_char( 26            cast( 27     date_trunc('month',current_date) 28                 as date)+x.id,'mm') as curr_mth, 29         to_char(current_date,'mm') as mth 30   from generate_series (0,31) x(id) 31        ) x 32  where mth = curr_mth 33        ) y 34  group by wk 35  order by wk 

Mysol

Use table T500 to return each day in the current month. Then pivot on the day of the week using MAX and CASE:

  1 select max(case dw when 2 then dm end) as Mo,  2        max(case dw when 3 then dm end) as Tu,  3        max(case dw when 4 then dm end) as We,  4        max(case dw when 5 then dm end) as Th,  5        max(case dw when 6 then dm end) as Fr,  6        max(case dw when 7 then dm end) as Sa,  7        max(case dw when 1 then dm end) as Su  8   from (  9 select date_format(dy,'%u') wk, 10        date_format(dy,'%d') dm, 11        date_format(dy,'%w')+1 dw 12   from ( 13 select adddate(x.dy,t500.id-1) dy, 14        x.mth 15   from ( 16 select adddate(current_date,-dayofmonth(current_date)+1) dy, 17        date_format( 18            adddate(current_date, 19                    -dayofmonth(current_date)+1), 20                    '%m') mth 21    from t1 22         ) x, 23           t500 24  where t500.id <= 31 25    and date_format(adddate(x.dy,t500.id-1),'%m') = x.mth 26        ) y 27        ) z 28  group by wk 29  order by wk 

SQL Server

Use the recursive WITH clause to return every day in the current month. Then pivot on the day of the week using CASE and MAX:

  1   with x(dy,dm,mth,dw,wk)  2     as (  3 select dy,  4        day(dy) dm,  5        datepart(m,dy) mth,  6        datepart(dw,dy) dw,  7        case when datepart(dw,dy) = 1  8             then datepart(ww,dy)-1  9             else datepart(ww,dy) 10        end wk 11   from ( 12 select dateadd(day,-day(getdate())+1,getdate( )) dy 13   from t1 14        ) x 15  union all 16  select dateadd(d,1,dy), day(dateadd(d,1,dy)), mth, 17         datepart(dw,dateadd(d,1,dy)), 18         case when datepart(dw,dateadd(d,1,dy)) = 1 19              then datepart(wk,dateadd(d,1,dy))-1 20              else datepart(wk,dateadd(d,1,dy)) 21         end 22    from x 23   where datepart(m,dateadd(d,1,dy)) = mth 24 ) 25 select max(case dw when 2 then dm end) as Mo, 26        max(case dw when 3 then dm end) as Tu, 27        max(case dw when 4 then dm end) as We, 28        max(case dw when 5 then dm end) as Th, 29        max(case dw when 6 then dm end) as Fr, 30        max(case dw when 7 then dm end) as Sa, 31        max(case dw when 1 then dm end) as Su 32   from x 33  group by wk 34  order by wk 

Discussion

DB2

The first step is to return each day in the month for which you want to create a calendar. Do that using the recursive WITH clause (if you don't have WITH available, you can use a pivot table, such as T500, as in the MySQL solution). Along with each day of the month (alias DM) you will need to return different parts of each date: the day of the week (alias DW), the current month you are working with (alias MTH), and the ISO week for each day of the month (alias WK). The results of the recursive view X prior to recursion taking place (the upper portion of the UNION ALL) are shown below:

  select (current_date -day(current_date) day +1 day) dy,        day((current_date -day(current_date) day +1 day)) dm,        month(current_date) mth,        dayofweek(current_date -day(current_date) day +1 day) dw,        week_iso(current_date -day(current_date) day +1 day) wk   from t1 DY          DM MTH         DW WK ----------- -- --- ---------- -- 01-JUN-2005 01  06          4 22 

The next step is to repeatedly increase the value for DM (move through the days of the month) until you are no longer in the current month. As you move through each day in the month, you will also return the day of the week that each day is, and which ISO week the current day of the month falls into. Partial results are shown below:

  with x(dy,dm,mth,dw,wk)   as ( select (current_date -day(current_date) day +1 day) dy,        day((current_date -day(current_date) day +1 day)) dm,        month(current_date) mth,        dayofweek(current_date -day(current_date) day +1 day) dw,        week_iso(current_date -day(current_date) day +1 day) wk   from t1  union all  select dy+1 day, day(dy+1 day), mth,         dayofweek(dy+1 day), week_iso(dy+1 day)    from x   where month(dy+1 day) = mth ) select *   from x DY          DM MTH         DW WK ----------- -- --- ---------- -- 01-JUN-2005 01 06           4 22 02-JUN-2005 02 06           5 22 … 21-JUN-2005 21 06           3 25 22-JUN-2005 22 06           4 25 … 30-JUN-2005 30 06           5 26 

What you are returning at this point are: each day for the current month, the two-digit numeric day of the month, the two-digit numeric month, the one-digit day of the week (17 for SunSat), and the two-digit ISO week each day falls into. With all this information available, you can use a CASE expression to determine which day of the week each value of DM (each day of the month) falls into. A portion of the results is shown below:

  with x(dy,dm,mth,dw,wk)   as ( select (current_date -day(current_date) day +1 day) dy,        day((current_date -day(current_date) day +1 day)) dm,        month(current_date) mth,        dayofweek(current_date -day(current_date) day +1 day) dw,        week_iso(current_date -day(current_date) day +1 day) wk   from t1  union all  select dy+1 day, day(dy+1 day), mth,         dayofweek(dy+1 day), week_iso(dy+1 day)    from x   where month(dy+1 day) = mth  )  select wk,         case dw when 2 then dm end as Mo,         case dw when 3 then dm end as Tu,         case dw when 4 then dm end as We,         case dw when 5 then dm end as Th,         case dw when 6 then dm end as Fr,         case dw when 7 then dm end as Sa,         case dw when 1 then dm end as Su    from x WK MO TU WE TH FR SA SU -- -- -- -- -- -- -- -- 22       01 22          02 22             03 22                04 22                   05 23 06 23    07 23       08 23          09 23             10 23                11 23                   12 

As you can see from the partial output, every day in each week is returned as a row. What you want to do now is to group the days by week, and then collapse all the days for each week into a single row. Use the aggregate function MAX, and group by WK (the ISO week) to return all the days for a week as one row. To properly format the calendar and ensure that the days are in the right order, order the results by WK. The final output is shown below:

  with x(dy,dm,mth,dw,wk)   as ( select (current_date -day(current_date) day +1 day) dy,        day((current_date -day(current_date) day +1 day)) dm,        month(current_date) mth,        dayofweek(current_date -day(current_date) day +1 day) dw,        week_iso(current_date -day(current_date) day +1 day) wk   from t1  union all  select dy+1 day, day(dy+1 day), mth,         dayofweek(dy+1 day), week_iso(dy+1 day)    from x   where month(dy+1 day) = mth ) select max(case dw when 2 then dm end) as Mo,        max(case dw when 3 then dm end) as Tu,        max(case dw when 4 then dm end) as We,        max(case dw when 5 then dm end) as Th,        max(case dw when 6 then dm end) as Fr,        max(case dw when 7 then dm end) as Sa,        max(case dw when 1 then dm end) as Su   from x  group by wk  order by wk MO TU WE TH FR SA SU -- -- -- -- -- -- --       01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 

Oracle

Begin by using the recursive CONNECT BY clause to generate a row for each day in the month for which you wish to generate a calendar. If you aren't running at least Oracle9i Database, you can't use CONNECT BY this way. Instead, you can use a pivot table, such as T500 in the MySQL solution.

Along with each day of the month, you will need to return different bits of information for each day: the day of the month (alias DM), the day of the week (alias DW), the current month you are working with (alias MTH), and the ISO week for each day of the month (alias WK). The results of the WITH view X for the first day of the current month are shown below:

  select trunc(sysdate,'mm') dy,        to_char(trunc(sysdate,'mm'),'dd') dm,        to_char(sysdate,'mm') mth,        to_number(to_char(trunc(sysdate,'mm'),'d')) dw,        to_char(trunc(sysdate,'mm'),'iw') wk   from dual DY          DM MT         DW WK ----------- -- -- ---------- -- 01-JUN-2005 01 06          4 22 

The next step is to repeatedly increase the value for DM (move through the days of the month) until you are no longer in the current month. As you move through each day in the month, you will also return the day of the week for each day and the ISO week into which the current day falls. Partial results are shown below (the full date for each day is added below for readability):

  with x   as ( select *   from ( select trunc(sysdate,'mm')+level-1 dy,        to_char(trunc(sysdate,'mm')+level-1,'iw') wk,        to_char(trunc(sysdate,'mm')+level-1,'dd') dm,        to_number(to_char(trunc(sysdate,'mm')+level-1,'d')) dw,        to_char(trunc(sysdate,'mm')+level-1,'mm') curr_mth,        to_char(sysdate,'mm') mth   from dual  connect by level <= 31        )  where curr_mth = mth ) select *   from x DY          WK DM         DW CU MT ----------- -- -- ---------- -- -- 01-JUN-2005 22 01          4 06 06 02-JUN-2005 22 02          5 06 06 … 21-JUN-2005 25 21          3 06 06 22-JUN-2005 25 22          4 06 06 … 30-JUN-2005 26 30          5 06 06 

What you are returning at this point is one row for each day of the current month. In that row you have: the two-digit numeric day of the month, the two-digit numeric month, the one-digit day of the week (17 for SunSat), and the two-digit ISO week number. With all this information available, you can use a CASE expression to determine which day of the week each value of DM (each day of the month) falls into. A portion of the results is shown below:

  with x   as ( select *   from ( select trunc(sysdate,'mm')+level-1 dy,        to_char(trunc(sysdate,'mm')+level-1,'iw') wk,        to_char(trunc(sysdate,'mm')+level-1,'dd') dm,        to_number(to_char(trunc(sysdate,'mm')+level-1,'d')) dw,        to_char(trunc(sysdate,'mm')+level-1,'mm') curr_mth,        to_char(sysdate,'mm') mth   from dual  connect by level <= 31        )  where curr_mth = mth ) select wk,        case dw when 2 then dm end as Mo,        case dw when 3 then dm end as Tu,        case dw when 4 then dm end as We,        case dw when 5 then dm end as Th,        case dw when 6 then dm end as Fr,        case dw when 7 then dm end as Sa,        case dw when 1 then dm end as Su   from x WK MO TU WE TH FR SA SU -- -- -- -- -- -- -- -- 22       01 22          02 22             03 22                04 22                  05 23 06 23    07 23       08 23          09 23             10 23               11 23                 12 

As you can see from the partial output, every day in each week is returned as a row, but the day number is in one of seven columns corresponding to the day of the week. Your task now is to consolidate the days into one row for each week. Use the aggregate function MAX and group by WK (the ISO week) to return all the days for a week as one row. To ensure the days are in the right order, order the results by WK. The final output is shown below:

  with x   as ( select *   from ( select to_char(trunc(sysdate,'mm')+level-1,'iw') wk,        to_char(trunc(sysdate,'mm')+level-1,'dd') dm,        to_number(to_char(trunc(sysdate,'mm')+level-1,'d')) dw,        to_char(trunc(sysdate,'mm')+level-1,'mm') curr_mth,        to_char(sysdate,'mm') mth   from dual  connect by level <= 31        )  where curr_mth = mth ) select max(case dw when 2 then dm end) Mo,        max(case dw when 3 then dm end) Tu,        max(case dw when 4 then dm end) We,        max(case dw when 5 then dm end) Th,        max(case dw when 6 then dm end) Fr,        max(case dw when 7 then dm end) Sa,        max(case dw when 1 then dm end) Su   from x  group by wk  order by wk MO TU WE TH FR SA SU -- -- -- -- -- -- --    01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 

PostgreSQL

Use the GENERATE_SERIES function to return one row for each day in the month. If your version of PostgreSQL doesn't support GENERATE_SERIES, then query a pivot table as shown in the MySQL solution.

For each day of the month, return the following information: the day of the month (alias DM), the day of the week (alias DW), the current month you are working with (alias MTH), and the ISO week for each day of the month (alias WK). The formatting and explicit casting makes this solution tough on the eyes, but it's really quite simple. Partial results from inline view X are shown below:

  select cast(date_trunc('month',current_date) as date)+x.id as dy,        to_char(           cast(     date_trunc('month',current_date)                as date)+x.id,'iw') as wk,        to_char(           cast(     date_trunc('month',current_date)                as date)+x.id,'dd') as dm,        cast(     to_char(        cast(  date_trunc('month',current_date)                as date)+x.id,'d') as integer) as dw,        to_char(           cast(     date_trunc('month',current_date)                as date)+x.id,'mm') as curr_mth,        to_char(current_date,'mm') as mth   from generate_series (0,31) x(id) DY          WK DM         DW CU MT ----------- -- -- ---------- -- -- 01-JUN-2005 22 01          4 06 06 02-JUN-2005 22 02          5 06 06 … 21-JUN-2005 25 21          3 06 06 22-JUN-2005 25 22          4 06 06 … 30-JUN-2005 26 30          5 06 06 

Notice that as you move through each day in the month, you will also return the day of the week and the ISO week number. To ensure you return days only for the month you are interested in, return only rows where CURR_MTH = MTH (the month each day belongs to should be the month the current date belongs to). What you are returning at this point is, for each day for the current month: the two-digit numeric day of the month, the two-digit numeric month, the one-digit day of the week (17 for Sun Sat), and the two-digit ISO week. Your next step is to use a CASE expression to determine which day of the week each value of DM (each day of the month) falls into. A portion of the results is shown below:

  select case dw when 2 then dm end as Mo,        case dw when 3 then dm end as Tu,        case dw when 4 then dm end as We,        case dw when 5 then dm end as Th,        case dw when 6 then dm end as Fr,        case dw when 7 then dm end as Sa,        case dw when 1 then dm end as Su   from ( select *   from ( select cast(date_trunc('month',current_date) as date)+x.id,        to_char(           cast(     date_trunc('month',current_date)                as date)+x.id,'iw') as wk,        to_char(           cast(     date_trunc('month',current_date)                 as date)+x.id,'dd') as dm,        cast(     to_char(        cast(  date_trunc('month',current_date)                as date)+x.id,'d') as integer) as dw,        to_char(           cast(     date_trunc('month',current_date)                as date)+x.id,'mm') as curr_mth,        to_char(current_date,'mm') as mth   from generate_series (0,31) x(id)        ) x  where mth = curr_mth        ) y WK MO TU WE TH FR SA SU -- -- -- -- -- -- -- -- 22       01 22          02 22             03 22                04 22                   05 23 06 23    07 23       08 23          09 23             10 23                11 23                   12 

As you can see from the partial output, every day in each week is returned as a row, and each day number falls into the column corresponding to its day of the week. Your job now is to collapse the days into one row for each week. To that end, use the aggregate function MAX and group the rows by WK (the ISO week). The result will be all the days for each week returned as one row as you would see on a calendar. To ensure the days are in the right order, order the results by WK. The final output is shown below:

  select max(case dw when 2 then dm end) as Mo,        max(case dw when 3 then dm end) as Tu,        max(case dw when 4 then dm end) as We,        max(case dw when 5 then dm end) as Th,        max(case dw when 6 then dm end) as Fr,        max(case dw when 7 then dm end) as Sa,        max(case dw when 1 then dm end) as Su   from ( select *   from ( select cast(date_trunc('month',current_date) as date)+x.id,        to_char(           cast(     date_trunc('month',current_date)                as date)+x.id,'iw') as wk,        to_char(           cast(     date_trunc('month',current_date)                as date)+x.id,'dd') as dm,        cast(     to_char(        cast(  date_trunc('month',current_date)                as date)+x.id,'d') as integer) as dw,        to_char(           cast(     date_trunc('month',current_date)                as date)+x.id,'mm') as curr_mth,        to_char(current_date,'mm') as mth   from generate_series (0,31) x(id)        ) x  where mth = curr_mth        ) y  group by wk  order by wk MO TU WE TH FR SA SU -- -- -- -- -- -- --       01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 

MySQL

The first step is to return a row for each day in the month for which you want to create a calendar. To that end, query against table T500. By adding each value returned by T500 to the first day of the month, you can return each day in the month.

For each date, you will need to return the following bits of information: the day of the month (alias DM), the day of the week (alias DW), the current month you are working with (alias MTH), and the ISO week for each day of the month (alias WK). Inline view X returns the first day of the current month along with the two-digit numeric value for the current month. Results are shown below:

  select adddate(current_date,-dayofmonth(current_date)+1) dy,        date_format(        adddate(current_date,                -dayofmonth(current_date)+1),                '%m') mth   from t1 DY          MT ----------- -- 01-JUN-2005 06 

The next step is to move through the month, starting from the first day and returning each day in the month. Notice that as you move through each day in the month, you will also return the corresponding day of the week and ISO week number. To ensure you return days only for the month you are interested in, return only rows where the month of the day returned is equal to the current month (the month each day belongs to should be the month the current date belongs to). A portion of the rows from inline view Y is shown below:

  select date_format(dy,'%u') wk,        date_format(dy,'%d') dm,        date_format(dy,'%w')+1 dw   from ( select adddate(x.dy,t500.id-1) dy,        x.mth   from ( select adddate(current_date,-dayofmonth(current_date)+1) dy,        date_format(            adddate(current_date,            -dayofmonth(current_date)+1),            '%m') mth   from t1        ) x,          t500  where t500.id <= 31    and date_format(adddate(x.dy,t500.id-1),'%m') = x.mth        ) y WK DM         DW -- -- ---------- 22 01          4 22 02          5 … 25 21          3 25 22          4 … 26 30          5 

For each day for the current month you now have: the two-digit numeric day of the month (DM), the one-digit day of the week (DW), and the two-digit ISO week number (WK). Using this information, you can write a CASE expression to determine which day of the week each value of DM (each day of the month) falls into. A portion of the results is shown below:

  select case dw when 2 then dm end as Mo,        case dw when 3 then dm end as Tu,        case dw when 4 then dm end as We,        case dw when 5 then dm end as Th,        case dw when 6 then dm end as Fr,        case dw when 7 then dm end as Sa,        case dw when 1 then dm end as Su   from ( select date_format(dy,'%u') wk,        date_format(dy,'%d') dm,        date_format(dy,'%w')+1 dw   from ( select adddate(x.dy,t500.id-1) dy,        x.mth   from ( select adddate(current_date,-dayofmonth(current_date)+1) dy,        date_format(            adddate(current_date,                    -dayofmonth(current_date)+1),                    '%m') mth    from t1         ) x,           t500   where t500.id <= 31     and date_format(adddate(x.dy,t500.id-1),'%m') = x.mth         ) y         ) z WK MO TU WE TH FR SA SU -- -- -- -- -- -- -- -- 22       01 22          02 22             03 22                04 22                   05 23 06 23    07 23       08 23          09 23             10 23                11 23                   12 

As you can see from the partial output, every day in each week is returned as a row. Within each row, the day number falls into the column corresponding to the appropriate weekday. Now you need to consolidate the days into one row for each week. To do that, use the aggregate function MAX, and group the rows by WK (the ISO week). To ensure the days are in the right order, order the results by WK. The final output is shown below:

  select max(case dw when 2 then dm end) as Mo,        max(case dw when 3 then dm end) as Tu,        max(case dw when 4 then dm end) as We,        max(case dw when 5 then dm end) as Th,        max(case dw when 6 then dm end) as Fr,        max(case dw when 7 then dm end) as Sa,        max(case dw when 1 then dm end) as Su   from ( select date_format(dy,'%u') wk,        date_format(dy,'%d') dm,        date_format(dy,'%w')+1 dw   from ( select adddate(x.dy,t500.id-1) dy,        x.mth   from ( select adddate(current_date,-dayofmonth(current_date)+1) dy,        date_format(            adddate(current_date,                    -dayofmonth(current_date)+1),                    '%m') mth   from t1       ) x,          t500  where t500.id <= 31   and date_format(adddate(x.dy,t500.id-1),'%m') = x.mth       ) y       ) z  group by wk  order by wk MO TU WE TH FR SA SU -- -- -- -- -- -- --       01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 

SQL Server

Begin by returning one row for each day of the month. You can do that using the recursive WITH clause. Or, if your version of SQL Server doesn't support recursive WITH, you can use a pivot table in the same manner as the MySQL solution. For each row that you return, you will need the following items: the day of the month (alias DM), the day of the week (alias DW), the current month you are working with (alias MTH), and the ISO week for each day of the month (alias WK). The results of the recursive view X prior to recursion taking place (the upper portion of the UNION ALL) are shown below:

  select dy,        day(dy) dm,        datepart(m,dy) mth,        datepart(dw,dy) dw,        case when datepart(dw,dy) = 1             then datepart(ww,dy)-1             else datepart(ww,dy)        end wk   from ( select dateadd(day,-day(getdate())+1,getdate()) dy   from t1        ) x DY          DM MTH         DW WK ----------- -- --- ---------- -- 01-JUN-2005  1   6          4 23 

Your next step is to repeatedly increase the value for DM (move through the days of the month) until you are no longer in the current month. As you move through each day in the month, you will also return the day of the week and the ISO week number. Partial results are shown below:

      with x(dy,dm,mth,dw,wk)     as ( select dy,        day(dy) dm,        datepart(m,dy) mth,        datepart(dw,dy) dw,        case when datepart(dw,dy) = 1             then datepart(ww,dy)-1             else datepart(ww,dy)        end wk   from ( select dateadd(day,-day(getdate( ))+1,getdate( )) dy   from t1        ) x  union all  select dateadd(d,1,dy), day(dateadd(d,1,dy)), mth,         datepart(dw,dateadd(d,1,dy)),         case when datepart(dw,dateadd(d,1,dy)) = 1              then datepart(wk,dateadd(d,1,dy))-1              else datepart(wk,dateadd(d,1,dy))         end   from x  where datepart(m,dateadd(d,1,dy)) = mth ) select *   from x DY          DM MTH         DW WK ----------- -- --- ---------- -- 01-JUN-2005 01 06           4 23 02-JUN-2005 02 06           5 23 … 21-JUN-2005 21 06           3 26 22-JUN-2005 22 06           4 26 … 30-JUN-2005 30 06           5 27 

You now have, for each day in the current month: the two-digit numeric day of the month, the two-digit numeric month, the one-digit day of the week (17 for Sun Sat), and the two-digit ISO week number.

Now, use a CASE expression to determine which day of the week each value of DM (each day of the month) falls into. A portion of the results is shown below:

    with x(dy,dm,mth,dw,wk)     as ( select dy,        day(dy) dm,        datepart(m,dy) mth,        datepart(dw,dy) dw,        case when datepart(dw,dy) = 1             then datepart(ww,dy)-1             else datepart(ww,dy)        end wk   from ( select dateadd(day,-day(getdate( ))+1,getdate( )) dy   from t1        ) x  union all  select dateadd(d,1,dy), day(dateadd(d,1,dy)), mth,         datepart(dw,dateadd(d,1,dy)),         case when datepart(dw,dateadd(d,1,dy)) = 1              then datepart(wk,dateadd(d,1,dy))-1              else datepart(wk,dateadd(d,1,dy))         end    from x   where datepart(m,dateadd(d,1,dy)) = mth ) select case dw when 2 then dm end as Mo,        case dw when 3 then dm end as Tu,        case dw when 4 then dm end as We,        case dw when 5 then dm end as Th,        case dw when 6 then dm end as Fr,        case dw when 7 then dm end as Sa,        case dw when 1 then dm end as Su   from x WK MO TU WE TH FR SA SU -- -- -- -- -- -- -- -- 22       01 22          02 22             03 22                04 22                   05 23 06 23    07 23       08 23          09 23             10 23                11 23                   12 

Every day in each week is returned as a separate row. In each row, the column containing the day number corresponds to the day of the week. You now need to consolidate the days for each week into one row. Do that by grouping the rows by WK (the ISO week) and applying the MAX function to the different columns. The results will be in calendar format as shown below:

  with x(dy,dm,mth,dw,wk)     as ( select dy,        day(dy) dm,        datepart(m,dy) mth,        datepart(dw,dy) dw,        case when datepart(dw,dy) = 1             then datepart(ww,dy)-1             else datepart(ww,dy)        end wk   from ( select dateadd(day,-day(getdate( ))+1,getdate( )) dy   from t1        ) x  union all  select dateadd(d,1,dy), day(dateadd(d,1,dy)), mth,         datepart(dw,dateadd(d,1,dy)),         case when datepart(dw,dateadd(d,1,dy)) = 1              then datepart(wk,dateadd(d,1,dy))-1              else datepart(wk,dateadd(d,1,dy))         end    from x   where datepart(m,dateadd(d,1,dy)) = mth ) select max(case dw when 2 then dm end) as Mo,        max(case dw when 3 then dm end) as Tu,        max(case dw when 4 then dm end) as We,        max(case dw when 5 then dm end) as Th,        max(case dw when 6 then dm end) as Fr,        max(case dw when 7 then dm end) as Sa,        max(case dw when 1 then dm end) as Su   from x  group by wk  order by wk MO TU WE TH FR SA SU -- -- -- -- -- -- --       01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 




SQL Cookbook
SQL Cookbook (Cookbooks (OReilly))
ISBN: 0596009763
EAN: 2147483647
Year: 2005
Pages: 235

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net