简体   繁体   English

Oracle 问题将工作 SQL 移植到过程

[英]Oracle issue porting working SQL to procedure

I have some SQL, which works fine.我有一些 SQL,它工作正常。 Inside the SQL are hard coded dates, which I want to remove and pass them in from a procedure. SQL 内部是硬编码的日期,我想将其从过程中删除并传入。

When I try to create the procedure, which does the inserts I am getting the following error and I don't understand why its happening.当我尝试创建执行插入操作的过程时,我收到以下错误,我不明白为什么会发生这种情况。 It's the same code in the SELECT statement with an INSERT statement right above the SELECT.它与 SELECT 语句中的代码相同,在 SELECT 的正上方有一个 INSERT 语句。

Errors: PROCEDURE CREATE_TIMEOFF_REQUESTS
Line/Col: 7/1 PL/SQL: SQL Statement ignored
Line/Col: 10/19 PL/SQL: ORA-01744: inappropriate INTO

Below is my test case.下面是我的测试用例。 I'm testing in Live SQL so we can both have the same Oracle version.我正在 Live SQL 中进行测试,因此我们可以使用相同的 Oracle 版本。 Any help fixing my code would be greatly appreciated.任何修复我的代码的帮助将不胜感激。

CREATE OR REPLACE TYPE obj_date IS OBJECT (
  date_val DATE
);

CREATE OR REPLACE TYPE nt_date IS TABLE OF obj_date;

create or replace function generate_dates_pipelined(
  p_from  in date,
  p_to    in date
)
  return nt_date 
  pipelined
is
begin
  for c1 in (
    with calendar (start_date, end_date ) as (
      select trunc(p_from), trunc(p_to) from dual
      union all
      select start_date + 1, end_date
      from   calendar
      where  start_date + 1 <= end_date
    )
    select start_date as day
    from   calendar
  ) loop
    pipe row  (obj_date(c1.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('01-AUG-2021 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'August  1st 2021' from dual union all
  select to_date('05-AUG-2021 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'August  5th 2021' from dual
)
SELECT * from dts;

Create table employees(
  employee_id NUMBER(6), 
  first_name VARCHAR2(20),
  last_name VARCHAR2(20),
  card_num VARCHAR2(10),
  work_days VARCHAR2(7)
);

ALTER TABLE employees
ADD (
  CONSTRAINT employees_pk PRIMARY KEY (employee_id)
      );

INSERT INTO employees (
  EMPLOYEE_ID,
  first_name, 
  last_name,
  card_num,
  work_days
)
WITH names AS ( 
  SELECT 1, 'Jane',     'Doe',      'F123456', 'NYYYYYN'   FROM dual UNION ALL 
  SELECT 2, 'Madison', 'Smith', 'R33432','NYYYYYN' FROM dual UNION ALL 
  SELECT 3, 'Justin',   'Case',     'C765341','NYYYYYN' FROM dual UNION ALL 
  SELECT 4, 'Mike',     'Jones',      'D564311','NYYYYYN' FROM dual 
)
SELECT * FROM names;  
  
create table timeoff(
    seq_num integer  GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
  employee_id NUMBER(6),
  timeoff_date DATE,
  timeoff_type VARCHAR2(1) DEFAULT 'V',
  constraint timeoff_chk check (timeoff_date=trunc(timeoff_date, 'dd')),
  constraint timeoff_pk primary key (employee_id, timeoff_date)
);

SQL works: SQL 工作原理:

INSERT INTO timeoff (employee_id, timeoff_date)
SELECT e.employee_id,
       c.date_val
FROM   employees e
       INNER JOIN table(generate_dates_pipelined(date '2021-08-01', DATE '2021-08-10')) c
       PARTITION BY ( e.employee_id )
       ON (SUBSTR(e.work_days, TRUNC(c.date_val) - TRUNC(c.date_val, 'IW') + 1, 1) = 'Y')
WHERE  NOT EXISTS (
         SELECT 1
         FROM   holidays h
         WHERE  c.date_val = h.holiday_date
       )
ORDER BY
      e.employee_id,
      c.date_val
;

SELECT * from timeoff;

SEQ_NUM    EMPLOYEE_ID    TIMEOFF_DATE    TIMEOFF_TYPE
1    1    03-AUG-21    V
2    1    04-AUG-21    V
3    1    06-AUG-21    V
4    1    07-AUG-21    V
5    1    10-AUG-21    V
6    2    03-AUG-21    V
7    2    04-AUG-21    V
8    2    06-AUG-21    V
...
...

TRUNCATE table timeoff;

Procedure with SELECT creates and RUN使用 SELECT 创建和运行的过程

CREATE OR REPLACE PROCEDURE create_timeoff_requests (start_date DATE, end_date DATE)
IS
    type t_date is table of date;
    l_res t_date;

BEGIN

      SELECT 
     c.date_val
     BULK COLLECT INTO l_res
     FROM   employees e
      INNER JOIN  TABLE (generate_dates_pipelined (start_date, end_date))c
    PARTITION BY ( e.employee_id )
            ON (SUBSTR(e.work_days, TRUNC(c.date_val) - TRUNC(c.date_val, 'IW') + 1, 1) = 'Y')
    WHERE  NOT EXISTS (
     SELECT 1
            FROM   holidays h
           WHERE    c.date_val = h.holiday_date
           )
    ORDER BY
       e.employee_id,
        c.date_val;


   -- debug
     for i in 1..l_res.count  loop
        dbms_output.put_line(l_res(i));
       end loop;
END;


EXEC create_timeoff_requests (DATE '2021-08-01', DATE '2021-08-10');

    03-AUG-21
    04-AUG-21
    06-AUG-21
    07-AUG-21
    10-AUG-21
    03-AUG-21
    04-AUG-21
    06-AUG-21
    07-AUG-21
    10-AUG-21
    ...
    ...

Fails to create with added INSERT statement.无法使用添加的 INSERT 语句创建。

CREATE OR REPLACE PROCEDURE create_timeoff_requests (start_date DATE, end_date DATE)
IS
    type t_date is table of date;
    l_res t_date;

BEGIN
  INSERT INTO timeoff (employee_id, timeoff_date)  
       SELECT 
     c.date_val
     BULK COLLECT INTO l_res
     FROM   employees e
      INNER JOIN  TABLE (generate_dates_pipelined (start_date, end_date))c
    PARTITION BY ( e.employee_id )
            ON (SUBSTR(e.work_days, TRUNC(c.date_val) - TRUNC(c.date_val, 'IW') + 1, 1) = 'Y')
    WHERE  NOT EXISTS (
     SELECT 1
            FROM   holidays h
            WHERE  c.date_val = h.holiday_date
           )
    ORDER BY
        e.employee_id,
 c.date_val;


   -- debug
     for i in 1..l_res.count  loop
      dbms_output.put_line(l_res(i));
       end loop;
END;

You do not need an object to wrap a DATE and can just use:您不需要对象来包装DATE ,只需使用:

CREATE TYPE nt_date IS TABLE OF DATE;

You can rewrite your calendar function to:您可以将日历函数重写为:

CREATE 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;

Your function to insert time off almost certainly wants to take an employee as an argument (otherwise you are inserting leave for ALL employees) and you probably want to use MERGE so that employees do not insert multiple requests for the same day if they submit overlapping requests.您插入休假的功能几乎肯定希望将员工作为参数(否则您将为所有员工插入休假)并且您可能想要使用MERGE以便员工在提交重叠请求时不会在同一天插入多个请求.

CREATE PROCEDURE create_timeoff_requests (
  p_employee_id IN TIMEOFF.EMPLOYEE_ID%TYPE,
  p_start_date  IN TIMEOFF.TIMEOFF_DATE%TYPE,
  p_end_date    IN TIMEOFF.TIMEOFF_DATE%TYPE
)
IS
BEGIN
  MERGE INTO timeoff dst
  USING (
    SELECT e.employee_id,
           c.COLUMN_VALUE AS timeoff_date
    FROM   employees e
           INNER JOIN TABLE(
             generate_dates_pipelined(p_start_date, p_end_date)
           ) c
           ON (SUBSTR(e.work_days, TRUNC(c.COLUMN_VALUE) - TRUNC(c.COLUMN_VALUE, 'IW') + 1, 1) = 'Y')
    WHERE  NOT EXISTS (
             SELECT 1
             FROM   holidays h
             WHERE  c.COLUMN_VALUE = h.holiday_date
           )
    AND    e.employee_id = p_employee_id
  ) src
  ON (    src.employee_id  = dst.employee_id
      AND src.timeoff_date = dst.timeoff_date )
  WHEN NOT MATCHED THEN
    INSERT (employee_id, timeoff_date)
    VALUES (src.employee_id, src.timeoff_date);
END;
/

You can insert the results of a query directly into a table;您可以将查询结果直接插入到表中; or you can query them into a collection;或者你可以将它们查询到一个集合中; but you can't do both in the same single statement.但是您不能在同一个语句中同时执行这两项操作。

I need the collection because I am calling a pipelined function that returns 1 or more dates and I didn't know how to capture the results.我需要该集合,因为我正在调用一个返回 1 个或多个日期的流水线函数,但我不知道如何捕获结果。

The pipelined function doesn't need a collection;流水线函数不需要集合; you're already calling that from your "SQL works" standalone version.你已经从你的“SQLworks”独立版本中调用了它。 You can move that directly into your procedure:您可以将其直接移动到您的程序中:

CREATE OR REPLACE PROCEDURE create_timeoff_requests (start_date DATE, end_date DATE)
IS
    type t_date is table of date;
    l_res t_date;

BEGIN
  INSERT INTO timeoff (employee_id, timeoff_date)
  SELECT e.employee_id,
         c.date_val
  FROM   employees e
         INNER JOIN table(generate_dates_pipelined(start_date, end_date)) c
         PARTITION BY ( e.employee_id )
         ON (SUBSTR(e.work_days, TRUNC(c.date_val) - TRUNC(c.date_val, 'IW') + 1, 1) = 'Y')
  WHERE  NOT EXISTS (
           SELECT 1
           FROM   holidays h
           WHERE  c.date_val = h.holiday_date
         );
END create_timeoff_requests;
/

The only difference is changing:唯一的区别是改变:

INNER JOIN table(generate_dates_pipelined(date '2021-08-01', DATE '2021-08-10')) c

to

INNER JOIN table(generate_dates_pipelined(start_date, end_date)) c

so it uses the procedure arguments (though you might consider renaming those as p_start_date and p_end_date to match the prefix pattern used by your generate_dates_pipelined function, and reduce the likelihood of an argument/column name clash down the line).所以它使用过程参数(尽管您可能会考虑将它们重命名为p_start_datep_end_date以匹配generate_dates_pipelined函数使用的前缀模式,并减少参数/列名称在行中发生冲突的可能性)。 And the order by is redundant when inserting.而且插入时order by是多余的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM