甲骨文计算支付日期

Oracle calculate pay DATE

我们使用双月工资单,员工在每个月的第 15 天和最后一天领取工资。

如果这些日子是星期六或星期日,那么我们会在前一天收到付款(如果那一天没有定义为节假日)。

我设法让部分查询工作,我可以在其中排除周末和节假日,但我可以使用一些帮助来确定应该在什么日期向人们付款。这些是我想在输出中看到的唯一几天

提前感谢所有回答者以及您的耐心、帮助和专业知识。

以下是我目前的资料。


CREATE OR REPLACE TYPE nt_date IS TABLE OF DATE;
/

CREATE OR REPLACE FUNCTION generate_dates_pipelined(
  p_from IN DATE,
  p_to   IN DATE
)
  RETURN nt_date PIPELINED DETERMINISTIC
IS
  v_start DATE := TRUNC(LEAST(p_from, p_to));
  v_end   DATE := TRUNC(GREATEST(p_from, p_to));
BEGIN
  LOOP
    PIPE ROW (v_start);
    EXIT WHEN v_start >= v_end;
    v_start := v_start + INTERVAL '1' DAY;
  END LOOP;
  RETURN;
END generate_dates_pipelined;
/

create table holidays(
          holiday_date DATE not null,
          holiday_name VARCHAR2(20),
          constraint holidays_pk primary key (holiday_date),
          constraint is_midnight check ( holiday_date = trunc ( holiday_date ) )
        );

INSERT into holidays (HOLIDAY_DATE,HOLIDAY_NAME)
        WITH dts as (
          select to_date('15-APR-2022 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'Passover 2022' from dual union all
          select to_date('31-DEC-2022 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'New Year Eve 2022' from dual
        )
        SELECT * from dts;


SELECT
        COLUMN_VALUE 
  FROM   
TABLE(generate_dates_pipelined(DATE '2022-01-01',
DATE '2022-12-31')) c
where 
to_char(COLUMN_VALUE, 'DY') NOT IN ('SAT', 'SUN') 
AND NOT EXISTS (
             SELECT 1
             FROM   holidays h
             WHERE  c.COLUMN_VALUE = h.holiday_date
           );

您可以使用下面的方法生成不包括星期六和星期日的日期日历。假期列表已建立并过滤。

主查询

with cte_h as
(
select to_date('15-APR-2022','DD-MON-YYYY') h_dt, 
'PASSOVER' h_name from dual union all
select to_date('31-DEC-2022','DD-MON-YYYY') h_dt, 
'New Year eve' h_name from dual
)
,cte_cal as (
select
decode(mod(level-1,2),
0,
case 
when add_months(trunc(sysdate,'YEAR'),ceil(level/2)-1)+14 
IN
(select h_dt from cte_h) 
then add_months(trunc(sysdate,'YEAR'),ceil(level/2)-1)+13
else add_months(trunc(sysdate,'YEAR'),ceil(level/2)-1)+14 END,
case 
when last_day(add_months(trunc(sysdate,'YEAR'),ceil(level/2)-1)) 
IN
(select h_dt from cte_h) 
then last_day(add_months(trunc(sysdate,'YEAR'),ceil(level/2)-1)) - 1 
else last_day(add_months(trunc(sysdate,'YEAR'),ceil(level/2)-1)) END ) day
from dual
connect by level < 25),
cte_pd as 
(select 
DAY as date_orig_w_h_adj,
case
when instr(to_char(day,'DAY'),'SUNDAY')>0 then day-2
when instr(to_char(day,'DAY'),'SATURDAY')>0 then day-1
else day END as pay_date, to_char(day,'DAY') dow_orig
from cte_cal)
select * from cte_pd;

上面的输出-

DATE_ORIG_W_H_ADJ     PAY_DATE  DOW_ORIG
--------------------- --------- ------------
15-JAN-22             14-JAN-22 SATURDAY
31-JAN-22             31-JAN-22 MONDAY
15-FEB-22             15-FEB-22 TUESDAY
28-FEB-22             28-FEB-22 MONDAY
15-MAR-22             15-MAR-22 TUESDAY
31-MAR-22             31-MAR-22 THURSDAY
14-APR-22             14-APR-22 THURSDAY
30-APR-22             29-APR-22 SATURDAY
15-MAY-22             13-MAY-22 SUNDAY
31-MAY-22             31-MAY-22 TUESDAY
15-JUN-22             15-JUN-22 WEDNESDAY
30-JUN-22             30-JUN-22 THURSDAY
15-JUL-22             15-JUL-22 FRIDAY
31-JUL-22             29-JUL-22 SUNDAY
15-AUG-22             15-AUG-22 MONDAY
31-AUG-22             31-AUG-22 WEDNESDAY
15-SEP-22             15-SEP-22 THURSDAY
30-SEP-22             30-SEP-22 FRIDAY
15-OCT-22             14-OCT-22 SATURDAY
31-OCT-22             31-OCT-22 MONDAY
15-NOV-22             15-NOV-22 TUESDAY
30-NOV-22             30-NOV-22 WEDNESDAY
15-DEC-22             15-DEC-22 THURSDAY
30-DEC-22             30-DEC-22 FRIDAY

24 rows selected.

根据您目前的情况:

WITH days AS
(
  SELECT days.column_value
    FROM TABLE(generate_dates_pipelined(DATE '2022-01-01', DATE '2022-12-31')) days
           LEFT JOIN holidays holy ON days.column_value = holy.holiday_date
          WHERE holy.holiday_date IS NULL
   WHERE TO_CHAR(days.column_value, 'DY') NOT IN ('SAT', 'SUN')
)
SELECT payment_day
  FROM (SELECT MAX(days.column_value) AS payment_day, TRUNC(days.column_value,'MM') 
          FROM days
         WHERE days.column_value <= TRUNC(days.column_value,'MM') + 14
         GROUP BY TRUNC(days.column_value,'MM')
         UNION
        SELECT MAX(days.column_value) , TRUNC(days.column_value,'MM') 
          FROM days
         WHERE days.column_value <= ADD_MONTHS(TRUNC(days.column_value,'MM'),1) - 1
         GROUP BY TRUNC(days.column_value,'MM')
        )

我用我的这个变体做了一些测试:

WITH holidays AS
(
  SELECT TO_DATE('15-APR-2022 00:00:00', 'DD-MON-YYYY HH24:MI:SS') AS dt,
         'Passover 2022' AS NAME
    FROM DUAL
   UNION ALL
  SELECT TO_DATE('31-DEC-2022 00:00:00', 'DD-MON-YYYY HH24:MI:SS'),
         'New Year Eve 2022'
    FROM DUAL 
),
workdays AS
(
  SELECT TRUNC(wknd.dt) AS dt
    FROM (SELECT SYSDATE + ROWNUM - 1 dt
            FROM dual
           WHERE 1 = 1 /* you can limit those such as in your pipelined function here */
         CONNECT BY LEVEL <= 366) wknd
   WHERE TO_CHAR(wknd.dt, 'FMDAY', 'NLS_DATE_LANGUAGE=AMERICAN') NOT IN ('SUNDAY','SATURDAY')
)
SELECT payment_day
  FROM (SELECT MAX(dt) AS payment_day, TRUNC(dt,'MM')
          FROM (SELECT wd.*
                  FROM workdays wd
                       LEFT JOIN holidays hd ON wd.dt = hd.dt
                 WHERE hd.dt IS NULL) first_payroll
         WHERE first_payroll.dt <= TRUNC(first_payroll.dt,'MM') + 14
         GROUP BY TRUNC(dt,'MM')
         UNION
        SELECT MAX(dt), TRUNC(dt,'MM')
          FROM (SELECT wd.*
                  FROM workdays wd
                       LEFT JOIN holidays hd ON wd.dt = hd.dt
                 WHERE hd.dt IS NULL) second_payroll
         WHERE second_payroll.dt <= ADD_MONTHS(TRUNC(second_payroll.dt,'MM'),1) - 1
         GROUP BY TRUNC(dt,'MM'));

希望它能给你一些有用的想法。

建立日历和假期 table 后,您需要参考它们两次,一次是确定 'nominal' 发薪日(每个月的第 15 天和最后一天)并确定如果这是一个假期或周末,另一个时间来查找该日期之前的最近工作日:

With Cal as
  (SELECT TRUNC (SYSDATE - ROWNUM) dt
   FROM DUAL CONNECT BY ROWNUM < 366),
PayCal as (
  select c.dt as THIS_DATE
        , CASE WHEN h.holiday_date is not null then 'Y' else 'N' end as IS_HOLIDAY
        , CASE WHEN to_char(dt, 'DY') IN ('SAT', 'SUN') then 'Y' else 'N' end as IS_SAT_OR_SUN
  from Cal c

    left join
      -- determine if c is a public holiday
        holidays h
        on  c.dt = h.holiday_date
)
select 
    c.THIS_DATE
    , case when IS_HOLIDAY='Y' or IS_SAT_OR_SUN='Y' then
                   (select max(c1.THIS_DATE) 
                    from PayCal c1
                    where c1.THIS_DATE < c.THIS_DATE  
                    and c1.IS_HOLIDAY='N'
                    and c1.IS_SAT_OR_SUN='N')
                else c.THIS_DATE
          end as PAY_ON_THIS_DATE
from PayCal c
where EXTRACT(DAY FROM c.THIS_DATE)=15
      OR
      LAST_DAY(c.THIS_DATE)=c.THIS_DATE

我使用了在 SO 中找到的日历数据生成逻辑版本,但您可以用您的 looping-function.

替换我的 Cal table


create table holidays(
          holiday_date DATE not null,
          holiday_name VARCHAR2(20),
          constraint holidays_pk primary key (holiday_date),
          constraint is_midnight check ( holiday_date = trunc ( holiday_date ) )
        );

INSERT into holidays (HOLIDAY_DATE,HOLIDAY_NAME)
        WITH dts as (
          select to_date('15-APR-2022 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'Passover 2022' from dual union all
          select to_date('31-DEC-2022 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'New Year Eve 2022' from dual
        )
        SELECT * from dts;



WITH  parameters (first_month, num_months)  AS
(
    SELECT TO_DATE ('JAN-2022', 'MON-YYYY')    -- First month wanted
    ,    4                       -- Total number of months
    FROM    dual
)
,  all_scheduled_paydays  AS
(
    SELECT  ADD_MONTHS (first_month + 14,       LEVEL - 1) AS payday_1
    ,    ADD_MONTHS (LAST_DAY (first_month), LEVEL - 1) AS payday_2
    FROM    parameters
    CONNECT BY LEVEL <= num_months
)
SELECT  c.actual_payday
,     a.scheduled_payday    -- For debugging
FROM     all_scheduled_paydays
UNPIVOT  ( scheduled_payday
     FOR col IN (payday_1, payday_2)
     ) a
CROSS APPLY (
               SELECT a.scheduled_payday + 1 - LEVEL AS actual_payday
        FROM    dual
        WHERE    CONNECT_BY_ISLEAF = 1
        CONNECT BY TO_CHAR ( a.scheduled_payday + 2 - LEVEL
                    , 'DY'
                  , 'NLS_DATE_LANGUAGE=ENGLISH'
                  ) IN ('SAT', 'SUN')
            OR  a.scheduled_payday + 2 - LEVEL
               IN (
                     SELECT holiday_date
                  FROM  holidays
                   )
         ) c
ORDER BY scheduled_payday
;