可以使用 sql 在月历中显示事件吗?

Posted

技术标签:

【中文标题】可以使用 sql 在月历中显示事件吗?【英文标题】:Can you display events in a monthly calendar using sql? 【发布时间】:2020-09-01 14:29:08 【问题描述】:

我有一张包含事件(日期和描述)的表格。 我想以当月的日历格式显示即将发生的事件。 使用 SQL 以日历格式显示当前事件的最佳方式是什么? 我的目标是 Oracle,但也许有比其他解决方案更通用的解决方案。

输出应该类似于这个:

     WEEK | MON               | TUE       ! WED       | THU             | FRI       | SAT       | SUN      
--------- ------------------- ----------- ----------- ----------------- ----------- ----------- -----------
       36 |                   | 01        | 02        | 03 stay at home | 04        | 05        | 06       
       37 | 07                | 08        | 09        | 10              | 11        | 12        | 13       
       38 | 14 work from home | 15        | 16        | 17              | 18        | 19        | 20       
       39 | 21                | 22        | 23        | 24              | 25        | 26        | 27       
       40 | 28                | 29        | 30        |                 |           |           |        

【问题讨论】:

您的源数据是否每天都有一个条目,或者您是否需要报告来填补空白?每天可以举办多个活动吗? 好问题@WilliamRobertson,报告​​将填补空白,但如果每天能有多个事件就好了,所以我猜想列出一个列表。 【参考方案1】:

样本数据(包括有两个事件的一天):

create table calendar
( cal_date   date
, cal_event  varchar2(50) );

insert all
    into calendar values (date '2020-09-03', 'stay at home')
    into calendar values (date '2020-09-03', 'play Fallout')
    into calendar values (date '2020-09-14', 'work from home')
select * from dual;

报告:

select cal_week as week
     , mon_events as mon
     , tue_events as tue
     , wed_events as wed
     , thu_events as thu
     , fri_events as fri
from   ( select to_char(d.dt,'Dy') as cal_day
               , to_number(to_char(d.dt,'iw')) as cal_week
               , to_char(d.dt,'DD')||' '||listagg(cal_event,', ') within group (order by c.cal_date) as cal_event
         from    ( select date '2020-09-01' -1 + rownum as dt
                   from   xmltable('1 to 30' ) ) d
                 left join calendar c on c.cal_date = d.dt
         group by d.dt
       )
pivot  (max(cal_event) as events
        for (cal_day) in ('Mon' as mon, 'Tue' as tue, 'Wed' as wed, 'Thu' as thu, 'Fri' as fri))
order by cal_week;

结果:

+------+-------------------+-----+-----+-------------------------------+-----+
| WEEK | MON               | TUE | WED | THU                           | FRI |
+------+-------------------+-----+-----+-------------------------------+-----+
|   36 |                   | 01  | 02  | 03 play Fallout, stay at home | 04  |
|   37 | 07                | 08  | 09  | 10                            | 11  |
|   38 | 14 work from home | 15  | 16  | 17                            | 18  |
|   39 | 21                | 22  | 23  | 24                            | 25  |
|   40 | 28                | 29  | 30  |                               |     |
+------+-------------------+-----+-----+-------------------------------+-----+

我必须在pivot 之前将日期编号与事件列表连接起来,这意味着聚合必须在子查询中,而max(cal_event) 只是为了满足需要聚合函数的pivot 语法。

【讨论】:

哇,@William,这看起来很棒!我想我更喜欢更通用的方式来生成当月的所有日期,但是 xmltable 函数和 pivot 是不错的技巧!而且我不知道 insert all ... select * from dual!非常感谢您的帮助! 这项技术(xmltable('1 to 30' ))真的很棒,罗伯逊先生!【参考方案2】:

您可以将条件聚合与GROUPing BY 周数一起使用:

WITH events AS
(
  SELECT date'2020-09-03' AS dy, 'stay at home' AS event
    FROM dual
  UNION ALL
  SELECT date'2020-09-14' AS dy
  ,      'work FROM home' AS event
    FROM dual
    UNION ALL
  SELECT date'2020-10-14' AS dy
  ,      'work FROM home' AS event
    FROM dual
) ,  month(dy) AS (
SELECT trunc(sysdate, 'month') + level - 1 AS dy 
  FROM dual
CONNECT BY level <=  last_day(sysdate)-trunc(sysdate, 'month') + 1
)
SELECT TO_CHAR( m.dy, 'iw' ) AS week, 
       MAX(CASE WHEN TO_CHAR( m.dy, 'DY' ) = 'MON' THEN TO_CHAR( m.dy, 'DD ' )||e.event END) AS "MON",
       MAX(CASE WHEN TO_CHAR( m.dy, 'DY' ) = 'TUE' THEN TO_CHAR( m.dy, 'DD ' )||e.event END) AS "TUE",
       MAX(CASE WHEN TO_CHAR( m.dy, 'DY' ) = 'WED' THEN TO_CHAR( m.dy, 'DD ' )||e.event END) AS "WED",
       MAX(CASE WHEN TO_CHAR( m.dy, 'DY' ) = 'THU' THEN TO_CHAR( m.dy, 'DD ' )||e.event END) AS "THU",
       MAX(CASE WHEN TO_CHAR( m.dy, 'DY' ) = 'FRI' THEN TO_CHAR( m.dy, 'DD ' )||e.event END) AS "FRI",
       MAX(CASE WHEN TO_CHAR( m.dy, 'DY' ) = 'SAT' THEN TO_CHAR( m.dy, 'DD ' )||e.event END) AS "SAT",
       MAX(CASE WHEN TO_CHAR( m.dy, 'DY' ) = 'SUN' THEN TO_CHAR( m.dy, 'DD ' )||e.event END) AS "SUN"
  FROM month m 
  LEFT JOIN events e
    ON e.dy = m.dy
 GROUP BY TO_CHAR( m.dy, 'iw' ) 

Demo

【讨论】:

谢谢@Rabaros,这是降低复杂性的好方法! 顺便说一下,@Barbaros,我想我更喜欢 with-clause 方式来生成天数,因为按级别连接只能在 Oracle 中使用。我不知道其中一个是否有更好的性能。 不客气@Marko。是的,应该测试性能。顺便说一句,也可以使用 Pivot 子句。【参考方案3】:

我想出了这个解决方案。也许有人会得到帮助,但我也对其他解决方案感到好奇。

经过编辑以包括同一天的多个事件。

with events as (
  select date'2020-09-03' as dy
  ,      'stay at home' as event
  from dual
  union all
  select date'2020-09-03' as dy
  ,      'stay at home 2' as event
  from dual
  union all
  select date'2020-09-14' as dy
  ,      'work from home' as event
  from dual
    union all
  select date'2020-10-14' as dy
  ,      'work from home' as event
  from dual
) , 
day_events as (
  select dy, listagg(event,', ') within group  (order by event) as event
  from events
  group by dy
)
, month(dy) as (
select trunc(sysdate, 'month') as dy from dual
union all
select dy + 1 from month
where dy < last_day(sysdate)-1
)
, days as (
  select to_char(dy, 'iw') as week,
  case to_char(dy, 'dy') when 'mon' then dy end as mon,
  case to_char(dy, 'dy') when 'tue' then dy end as tue,
  case to_char(dy, 'dy') when 'wed' then dy end as wed,
  case to_char(dy, 'dy') when 'thu' then dy end as thu,
  case to_char(dy, 'dy') when 'fri' then dy end as fri,
  case to_char(dy, 'dy') when 'sat' then dy end as sat,
  case to_char(dy, 'dy') when 'sun' then dy end as sun
  from month
), calendar as (
select week
,      max(mon) as mon
,      max(tue) as tue
,      max(wed) as wed
,      max(thu) as thu
,      max(fri) as fri
,      max(sat) as sat
,      max(sun) as sun
from days
group by week
order by week)
select week
, '| '||decode(mon, e.dy, to_char(mon,'dd')||' '||e.event, to_char(mon, 'dd')) as "| MON"
, '| '||decode(tue, e.dy, to_char(tue,'dd')||' '||e.event, to_char(tue, 'dd')) as "| TUE"
, '| '||decode(wed, e.dy, to_char(wed,'dd')||' '||e.event, to_char(wed, 'dd')) as "! WED"
, '| '||decode(thu, e.dy, to_char(thu,'dd')||' '||e.event, to_char(thu, 'dd')) as "| THU"
, '| '||decode(fri, e.dy, to_char(fri,'dd')||' '||e.event, to_char(fri, 'dd')) as "| FRI"
, '| '||decode(sat, e.dy, to_char(sat,'dd')||' '||e.event, to_char(sat, 'dd')) as "| SAT"
, '| '||decode(sun, e.dy, to_char(sun,'dd')||' '||e.event, to_char(sun, 'dd')) as "| SUN"
from calendar c left join day_events e
on c.week = to_char(e.dy, 'iw')
order by c.week;

【讨论】:

以上是关于可以使用 sql 在月历中显示事件吗?的主要内容,如果未能解决你的问题,请参考以下文章

我如何在 vb 的月历上将两个日期的范围加粗或突出显示?

如何突出显示月历控件中的日期?

5.2月历

VB.NET 在月历中禁用过去的日期

周一至周日的月历显示日期[关闭]

当维度大于 1,1 时,指定月历中的第一个月