5

我正在寻找一些 SQL 来确定当前周是否是包含该月第 N 天的周。

例子。我想知道我是否在包含本月第三个星期五的那一周。或者每月的哪一周包含第三个星期五。周应定义为从星期日开始到星期六结束。因此,从星期六开始的月份直到第 4 周才会看到星期五,而从星期日到星期五开始的月份,会在该月的第三周看到它的第三个星期五。

不确定是否需要 PL/SQL。

4

3 回答 3

4

所有现代 SQL 平台都有丰富的日期/时间函数来进行日期运算。但是,当我查看基于它们构建的 SQL 时,尤其是对于像您这样的需求,我的眼睛呆滞了。

我改用精心制作的日历表。我非常喜欢使用它的两件事。

  • 相对不熟练的实习生可以很快学会正确查询。
  • 通常可以看到查询显然是正确的。

如果您试图回答“本周是否包含当月的第三个星期五?”这个问题,那么对我的日历表进行查询时的样子如下所示。(我的日历表使用 ISO 周数,而不是严格的日历周。以下两种解决方法。)

select cal_date
from calendar
where year_of_date = 2012 
  and iso_week = (select iso_week
                  from calendar
                  where cal_date = current_date)
  and day_of_week = 'Fri'
  and day_of_week_ordinal = 3;

它不返回任何行;本周(2012-08-19 到 2012-08-25)不包含 2012 年 8 月的第三个星期五。(它包含第四个星期五。)

完全不改变我的表,我可以用这个查询回答你的第一个问题。它将您对一周的定义包装在一个公用表表达式中。如果我必须在生产中使用这样的东西,我可能会创建一个视图而不是 CTE。

with current_week as (
  select *
  from calendar
  where cal_date between (select max(cal_date) 
                          from calendar 
                          where day_of_week = 'Sun' 
                            and cal_date <= current_date) 
                     and (select min(cal_date) 
                          from calendar 
                          where day_of_week = 'Sat' 
                            and cal_date >= current_date)
)
select cal_date
from current_week
where day_of_week = 'Fri'
  and day_of_week_ordinal = 3;

同样,它不返回任何行;同样的原因。

第二种选择是定义您自己的 week_number 列来替换我的 iso_week 列。然后你可以像上面的第一个一样表达你的查询。


PostgreSQL 中日历表的 DDL。DDL 是标准 SQL;不过,我不确定 iso_year 和 iso_week 上的 CHECK 约束是否是标准 SQL。添加适合您环境的索引。

包括填充表格的功能。应该很容易移植到 Oracle。

create table calendar (
  cal_date date primary key,
  year_of_date integer not null 
    check (year_of_date = extract(year from cal_date)),
  month_of_year integer not null 
    check (month_of_year = extract(month from cal_date)),
  day_of_month integer not null 
    check (day_of_month = extract(day from cal_date)),
  day_of_week char(3) not null 
    check (day_of_week = 
    case when extract(dow from cal_date) = 0 then 'Sun'
         when extract(dow from cal_date) = 1 then 'Mon'
         when extract(dow from cal_date) = 2 then 'Tue'
         when extract(dow from cal_date) = 3 then 'Wed'
         when extract(dow from cal_date) = 4 then 'Thu'
         when extract(dow from cal_date) = 5 then 'Fri'
         when extract(dow from cal_date) = 6 then 'Sat'
    end),
  day_of_week_ordinal integer not null
    check (day_of_week_ordinal = 
      case
        when day_of_month >= 1 and day_of_month <= 7 then 1
        when day_of_month >= 8 and day_of_month <= 14 then 2
        when day_of_month >= 15 and day_of_month <= 21 then 3
        when day_of_month >= 22 and day_of_month <= 28 then 4
        else 5
      end),
  iso_year integer not null 
    check (iso_year = extract(isoyear from cal_date)),
  iso_week integer not null 
    check (iso_week = extract(week from cal_date))

);


CREATE OR REPLACE FUNCTION insert_range_into_calendar(from_date date, to_date date)
  RETURNS void AS
$BODY$

DECLARE
    this_date date := from_date;
BEGIN

    while (this_date <= to_date) LOOP
        INSERT INTO calendar (cal_date, year_of_date, month_of_year, day_of_month, day_of_week, day_of_week_ordinal, iso_year, iso_week)
        VALUES (this_date, extract(year from this_date), extract(month from this_date), extract(day from this_date),
        case when extract(dow from this_date) = 0 then 'Sun'
             when extract(dow from this_date) = 1 then 'Mon'
             when extract(dow from this_date) = 2 then 'Tue'
             when extract(dow from this_date) = 3 then 'Wed'
             when extract(dow from this_date) = 4 then 'Thu'
             when extract(dow from this_date) = 5 then 'Fri'
             when extract(dow from this_date) = 6 then 'Sat'
        end,
        case when extract(day from this_date) between 1 and 7 then 1
             when extract(day from this_date) between 8 and 14 then 2
             when extract(day from this_date) between 15 and 21 then 3
             when extract(day from this_date) between 22 and 28 then 4
             when extract(day from this_date) > 28 then 5
        end,
        cast(extract(isoyear from this_date) as integer),
        cast(extract(week from this_date) as integer));
        this_date = this_date + interval '1 day';
    end loop;       

END;
$BODY$
  LANGUAGE plpgsql 
于 2012-08-21T23:02:32.193 回答
3

您可以简单地生成包含每个月第 N 天的星期的日期范围,例如每个月的第三个星期五,使用如下查询:

select d - day_of_week AS sunday
      ,d + (7 - day_of_week) AS saturday
from   (select trunc(sysdate,'YY')+rownum-1 AS d
              ,to_number(to_char(trunc(sysdate,'YY')+rownum-1,'D'))
               AS day_of_week
        from dual connect by level <= 366)
where  to_char(d,'W') = 3
and    to_char(d,'DY') = 'FRI';

SUNDAY      SATURDAY
==========  ==========
14/01/2012  21/01/2012
11/02/2012  18/02/2012
10/03/2012  17/03/2012
14/04/2012  21/04/2012
12/05/2012  19/05/2012
09/06/2012  16/06/2012
14/07/2012  21/07/2012
11/08/2012  18/08/2012
15/09/2012  22/09/2012
13/10/2012  20/10/2012
10/11/2012  17/11/2012
15/12/2012  22/12/2012

编辑:您可以创建一个简单的函数来检查单个日期,例如:

CREATE FUNCTION date_in_week_of_nth_day
   (in_date IN DATE
   ,in_week IN NUMBER
   ,in_day  IN VARCHAR2
   ) RETURN CHAR IS
  ret CHAR(1);
BEGIN
  select 'Y' into ret
  from   (select trunc(in_date,'MM')+rownum-1 AS d
                ,to_number(to_char(trunc(in_date,'MM')+rownum-1,'D'))
                 AS day_of_week
          from dual connect by level <= 31)
  where  to_char(d,'W') = in_week
  and    to_char(d,'DY') = in_day
  and    :in_date between (d - day_of_week) and (d + (7 - day_of_week));
  RETURN ret;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    RETURN 'N';
END;
于 2012-08-22T05:45:15.477 回答
0

可能有更好的方法可以做到这一点,并且考虑到您没有任何类型的calendar表格,如果第四个星期五在当前周,则下面的查询将返回“Y”-

查询周开始Sunday和结束Saturday

SELECT 'Y' FROM
  (
   SELECT max(to_date(month||' '||fri,'MON YYYY DD')) DT FROM (
        SELECT LPAD( MONTH, 20-(20-LENGTH(MONTH))/2 ) MONTH,Sun, Mon, Tue,
               Wed, Thu, Fri, Sat
        FROM (SELECT TO_CHAR(dt,'fmMonthfm YYYY') MONTH,TO_CHAR(dt+1,'iw') week,
                    MAX(DECODE(TO_CHAR(dt,'d'),'1',LPAD(TO_CHAR(dt,'fmdd'),2))) Sun,
                    MAX(DECODE(TO_CHAR(dt,'d'),'2',LPAD(TO_CHAR(dt,'fmdd'),2))) Mon,
                    MAX(DECODE(TO_CHAR(dt,'d'),'3',LPAD(TO_CHAR(dt,'fmdd'),2))) Tue,
                    MAX(DECODE(TO_CHAR(dt,'d'),'4',LPAD(TO_CHAR(dt,'fmdd'),2))) Wed,
                    MAX(DECODE(TO_CHAR(dt,'d'),'5',LPAD(TO_CHAR(dt,'fmdd'),2))) Thu,
                    MAX(DECODE(TO_CHAR(dt,'d'),'6',LPAD(TO_CHAR(dt,'fmdd'),2))) Fri,
                    MAX(DECODE(TO_CHAR(dt,'d'),'7',LPAD(TO_CHAR(dt,'fmdd'),2))) Sat
                    FROM ( SELECT TRUNC(SYSDATE,'y')-1+ROWNUM dt
        FROM all_objects
        WHERE ROWNUM <= ADD_MONTHS(TRUNC(SYSDATE,'y'),12) - TRUNC(SYSDATE,'y'))
        GROUP BY TO_CHAR(dt,'fmMonthfm YYYY'), TO_CHAR( dt+1, 'iw' ))
        ORDER BY TO_DATE( MONTH, 'Month YYYY' ), TO_NUMBER(week))
    where to_char(to_date(month,'MON YYYY'),'MON YYYY') = to_char(sysdate,'MON YYYY')
      and fri is not null
      and rownum <= 4) a
where a.dt BETWEEN sysdate+(8 - to_char(sysdate,'d'))-7 
      AND sysdate+(7 - to_char(sysdate,'d'));

要检查第 N 天 - 只需将子句替换rownum <= Nand fri is not null,and <your_day> is not nullSELECT to_date(month||' '||fri,'MON YYYY DD') DT FROM ..即可SELECT to_date(month||' '||<your_day>,'MON YYYY DD') DT FROM ..

因此,只有dayandN值的动态查询可以为您提供所需的结果。像

SELECT 'Y' FROM
      (
       SELECT max(to_date(month||' '||:day,'MON YYYY DD')) DT FROM (
            SELECT LPAD( MONTH, 20-(20-LENGTH(MONTH))/2 ) MONTH,Sun, Mon, Tue,
                   Wed, Thu, Fri, Sat
            FROM (SELECT TO_CHAR(dt,'fmMonthfm YYYY') MONTH,TO_CHAR(dt+1,'iw') week,
                        MAX(DECODE(TO_CHAR(dt,'d'),'1',LPAD(TO_CHAR(dt,'fmdd'),2))) Sun,
                        MAX(DECODE(TO_CHAR(dt,'d'),'2',LPAD(TO_CHAR(dt,'fmdd'),2))) Mon,
                        MAX(DECODE(TO_CHAR(dt,'d'),'3',LPAD(TO_CHAR(dt,'fmdd'),2))) Tue,
                        MAX(DECODE(TO_CHAR(dt,'d'),'4',LPAD(TO_CHAR(dt,'fmdd'),2))) Wed,
                        MAX(DECODE(TO_CHAR(dt,'d'),'5',LPAD(TO_CHAR(dt,'fmdd'),2))) Thu,
                        MAX(DECODE(TO_CHAR(dt,'d'),'6',LPAD(TO_CHAR(dt,'fmdd'),2))) Fri,
                        MAX(DECODE(TO_CHAR(dt,'d'),'7',LPAD(TO_CHAR(dt,'fmdd'),2))) Sat
                        FROM ( SELECT TRUNC(SYSDATE,'y')-1+ROWNUM dt
            FROM all_objects
            WHERE ROWNUM <= ADD_MONTHS(TRUNC(SYSDATE,'y'),12) - TRUNC(SYSDATE,'y'))
            GROUP BY TO_CHAR(dt,'fmMonthfm YYYY'), TO_CHAR( dt+1, 'iw' ))
            ORDER BY TO_DATE( MONTH, 'Month YYYY' ), TO_NUMBER(week))
        where to_char(to_date(month,'MON YYYY'),'MON YYYY') = to_char(sysdate,'MON YYYY')
          and :day is not null
          and rownum <= :num) a
    where a.dt BETWEEN sysdate+(8 - to_char(sysdate,'d'))-7 
          AND sysdate+(7 - to_char(sysdate,'d'));

如果你仔细看,子查询中有一个完整的日历。哪个是-

SELECT LPAD( MONTH, 20-(20-LENGTH(MONTH))/2 ) MONTH,Sun, Mon, Tue,
               Wed, Thu, Fri, Sat
        FROM (SELECT TO_CHAR(dt,'fmMonthfm YYYY') MONTH,TO_CHAR(dt+1,'iw') week,
                    MAX(DECODE(TO_CHAR(dt,'d'),'1',LPAD(TO_CHAR(dt,'fmdd'),2))) Sun,
                    MAX(DECODE(TO_CHAR(dt,'d'),'2',LPAD(TO_CHAR(dt,'fmdd'),2))) Mon,
                    MAX(DECODE(TO_CHAR(dt,'d'),'3',LPAD(TO_CHAR(dt,'fmdd'),2))) Tue,
                    MAX(DECODE(TO_CHAR(dt,'d'),'4',LPAD(TO_CHAR(dt,'fmdd'),2))) Wed,
                    MAX(DECODE(TO_CHAR(dt,'d'),'5',LPAD(TO_CHAR(dt,'fmdd'),2))) Thu,
                    MAX(DECODE(TO_CHAR(dt,'d'),'6',LPAD(TO_CHAR(dt,'fmdd'),2))) Fri,
                    MAX(DECODE(TO_CHAR(dt,'d'),'7',LPAD(TO_CHAR(dt,'fmdd'),2))) Sat
                    FROM ( SELECT TRUNC(SYSDATE,'y')-1+ROWNUM dt
        FROM all_objects
        WHERE ROWNUM <= ADD_MONTHS(TRUNC(SYSDATE,'y'),12) - TRUNC(SYSDATE,'y'))
        GROUP BY TO_CHAR(dt,'fmMonthfm YYYY'), TO_CHAR( dt+1, 'iw' ))
        ORDER BY TO_DATE( MONTH, 'Month YYYY' ), TO_NUMBER(week);
于 2012-08-22T02:20:30.117 回答