Oracle 在 DML 中调用一个函数

Oracle calling a function within DML

我有一个接受字符串 'HH:MM:SS' 并将其转换为秒数的函数。见下面的例子

CREATE OR REPLACE FUNCTION CONVERT_TO_SECONDS( 
  i_date_string IN VARCHAR2 
)
RETURN INTEGER            
AS
 l_hours      NUMBER;
 l_minutes  NUMBER;
 l_seconds  NUMBER;
BEGIN
SELECT trim('"'
  FROM regexp_substr(i_date_string,'".*?"|[^:]+',1,1)) hours,
    trim('"'
  FROM regexp_substr(i_date_string,'".*?"|[^:]+',1,2)) minutes,
    trim('"'
  FROM regexp_substr(i_date_string,'".*?"|[^:]+',1,3)) seconds
      INTO l_hours ,
    l_minutes ,
    l_seconds
  FROM dual ;
return
     l_hours*3600 +  
     l_minutes*60 +
     l_seconds;
     
END;
/
    
SELECT CONVERT_TO_SECONDS('08:08:08') FROM DUAL;

CONVERT_TO_SECONDS('08:08:08')
29288

我有一个运行良好的程序,它创建了 emp_attendance 行。

创建行后,我试图用函数返回的秒数更新每行的 end_date。这可能吗?如果是这样,我怎样才能克服更新时的语法错误。

在此先感谢所有回答以及您的帮助、耐心和专业知识。

我的测试案例如下。

     
   ALTER SESSION SET NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';

        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 employees(
          employee_id NUMBER(6), 
          first_name VARCHAR2(20),
          last_name VARCHAR2(20),
         card_num VARCHAR2(10),
          work_days VARCHAR2(7)
       );

        INSERT INTO employees (
         employee_id,
         first_name, 
         last_name,
         card_num,
         work_days
        )
        WITH names AS   ( 
          SELECT 1, 'John',     'Doe',      'D564311','YYYYYNN' FROM dual UNION ALL
          SELECT 2, 'Justin',     'Case',      'C224311','YYYYYNN' FROM dual UNION ALL
        SELECT 3, 'Mike',     'Jones',      'J288811','YYYYYNN' FROM dual UNION ALL
         SELECT 4, 'Jane',     'Smith',      'S564661','YYYYYNN' FROM dual 
       ) SELECT * FROM names; 

  
        CREATE TABLE  emp_attendance(    
      seq_num NUMBER  GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
            employee_id NUMBER(6),
            start_date DATE,
            end_date DATE,
            week_number NUMBER(2));


CREATE OR REPLACE PROCEDURE create_emp_attendance      (
      p_start_date  IN DATE,
      p_end_date    IN DATE
    )
    IS
   BEGIN

     INSERT INTO emp_attendance ( employee_id, start_date, end_date, week_number)
     SELECT  
          employee_id
          , start_date
          , start_date+NUMTODSINTERVAL(FLOOR(DBMS_RANDOM.VALUE(3600,43200)), 'SECOND') AS end_date
          , to_char(start_date,'WW') AS week_number 
     FROM (  -- Need subquery to generate end_date based on start_date. 
             SELECT e.employee_id, d.COLUMN_VALUE + NUMTODSINTERVAL(FLOOR(DBMS_RANDOM.VALUE(0,86399)), 'SECOND') AS start_date
               FROM   employees e
              CROSS JOIN TABLE( generate_dates_pipelined(p_start_date, p_end_date) ) d
          ) ed
          ;
END;
/

EXEC create_emp_attendance(SYSDATE, SYSDATE);


-- Having problem with this update 

update emp_attendance 
set end_date=start_date + NUMTODSINTERVAL
CONVERT_TO_SECONDS('08:08:08'),'SECOND';

-- Once the update is working the query below should show 8hrs 8mins 8sec for each row.

select  e.employee_id,
         e.first_name,
        e.last_name,

        trunc(sum(a.end_date - a.start_date) * 24) hours,

        trunc(mod(sum(a.end_date - a.start_date) * 24 * 60,60)) minutes,

        round(mod(sum(a.end_date - a.start_date) * 24 * 60 * 60,60)) seconds

  from  employees e,
           emp_attendance a

  where a.employee_id = e.employee_id 

AND start_date BETWEEN TRUNC(SYSDATE)
AND
TRUNC(SYSDATE)+ (1-1/24/60/60)
 group by e.employee_id, e.first_name, e.last_name
  order by e.employee_id, e.first_name,
           e.last_name;


NUMTODSINTERVAL 是一个函数。所以需要这样使用:

update emp_attendance
   set end_date=start_date + NUMTODSINTERVAL(CONVERT_TO_SECONDS('08:08:08'), 'SECOND')
;

您可以将函数简化为:

CREATE OR REPLACE FUNCTION CONVERT_TO_SECONDS( 
  i_date_string IN VARCHAR2 
)
RETURN INTEGER DETERMINISTIC
AS
BEGIN
  RETURN ( TO_DATE(i_date_string, 'HH24:MI:SS')
         - TO_DATE('00:00:00', 'HH24:MI:SS')
         ) * 86400;
END;
/

那么你需要在 NUMTODSINTERVAL 函数参数周围加上方括号:

UPDATE emp_attendance 
SET end_date = start_date + NUMTODSINTERVAL( CONVERT_TO_SECONDS('08:08:08'),'SECOND' );

db<>fiddle here

无需创建自定义函数,因为 Oracle 已有:to_dsinterval。但是你还需要提供天数,所以在你的时间之前加一个零:

select
  sysdate,
  sysdate + to_dsinterval('0 ' || '08:08:08') as sysdate_shifted
from dual
SYSDATE             | SYSDATE_SHIFTED    
:------------------ | :------------------
2021-08-15 18:31:49 | 2021-08-16 02:39:57

db<>fiddle here

那么您的更新将是:

update emp_attendance 
set end_date = start_date + to_dsinterval('0 ' || '08:08:08')