Oracle INSERT 预计 SELECT

Oracle INSERT expecting SELECT

我有一个测试用例,它工作正常。

在这种情况下,有一段代码带有 COMMENT FAILED,我试图将这些行插入到 table 中,最终提示 ignore_row_on_dupkey_index.

在尝试 INSERT 时,我得到了预期 SELECT 的错误,我不明白为什么会有 SELECT。我显然遗漏了一些东西,但我无法弄清楚如何解决这个问题。

测试的设置有点冗长(我很抱歉)但是您可以剪切和粘贴设置的所有内容,因为除了失败评论开始的最后部分外,它都可以正常工作。

如果有人能告诉我为什么 INSERT 不起作用并提供修复,我们将不胜感激。预先感谢所有回复的人和您的专业知识。顺便说一句,如果你想模拟我的环境,我正在现场测试 SQL


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

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 access_history(
      seq_num integer  GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
       employee_id NUMBER(6), 
       card_num varchar2(10),
       location_id number(4),
       access_date date,
       processed NUMBER(1) default 0
    );

CREATE TABLE locations AS
    SELECT level AS location_id,
       'Door ' || level AS location_name,

    CASE round(dbms_random.value(1,3)) 
            WHEN 1 THEN 'G' 
            WHEN 2 THEN 'G' 
            WHEN 3 THEN 'G' 
         END AS location_type

    FROM   dual
    CONNECT BY level <= 5;

     ALTER TABLE locations 
         ADD ( CONSTRAINT locations_pk
       PRIMARY KEY (location_id));

create table schedule_assignment(
       seq_num NUMBER  GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
       schedule_id number(4),
       schedule_date DATE,
       employee_id NUMBER(6) DEFAULT 0,
constraint sa_chk check (schedule_date=trunc(schedule_date, 'dd')),
          constraint sa_pk primary key (schedule_id, schedule_date,employee_id));


CREATE OR REPLACE PROCEDURE  create_schedule_assignment (
     p_schedule_id IN NUMBER,
      p_start_date  IN DATE,
      p_end_date   IN DATE,
      p_employee_id  IN NUMBER DEFAULT 0
    )
    IS
   BEGIN
      merge into schedule_assignment s
       using (select p_schedule_id as schedule_id,
p_employee_id  as employee_id,
                     column_value  as schedule_date
              from table(generate_dates_pipelined(p_start_date, p_end_date))
            ) x
      on (    x.schedule_id = s.schedule_id
          and x.schedule_date = s.schedule_date
         and
x.employee_id =
s.employee_id 
         )
       when not matched then insert (schedule_id, schedule_date, employee_id)
         values (x.schedule_id, x.schedule_date, 
x.employee_id);
   END;
/

create table schedule(
      seq_num NUMBER  GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
       schedule_id NUMBER(4),
       location_id number(4),
       base_date DATE,
       start_date DATE,
       end_date DATE,
          constraint schedule_pk primary key (schedule_id, location_id, base_date),
         CONSTRAINT start_min check (start_date=trunc(start_date,'MI')),   
       CONSTRAINT end_min check (end_date=trunc(end_date,'MI')),
 CONSTRAINT end_gt_start CHECK (end_date >= start_date)
      );


CREATE OR REPLACE PROCEDURE CREATE_SCHEDULE(
  i_schedule_id IN PLS_INTEGER,
  -- i_base_date   IN DATE,
  p_start_date in date,
  p_end_date in date,
  i_offset      IN PLS_INTEGER DEFAULT 0, 
  i_incr        IN PLS_INTEGER DEFAULT 10,
  i_duration    IN PLS_INTEGER DEFAULT 5
)
AS 
  l_offset   interval day to second;
  l_incr     interval day to second;
  l_duration interval day to second;
  
  i_base_date date;
BEGIN
  l_offset   := NUMTODSINTERVAL(i_offset, 'SECOND') ;
  l_incr     := NUMTODSINTERVAL(i_incr, 'MINUTE') ;
  l_duration := NUMTODSINTERVAL(i_duration, 'MINUTE') ;

  for i in ( select column_value each_date from TABLE(generate_dates_pipelined(p_start_date, p_end_date)) )
  loop

    i_base_date := i.each_date;

    MERGE INTO schedule dst
    USING (
      SELECT   i_schedule_id AS schedule_id,
               l.location_id,
               i_base_date AS base_date,
               i_base_date + l_offset + (l_incr * (ROWNUM - 1))
                 AS start_date,
               i_base_date + l_offset + (l_incr * (ROWNUM - 1)) + l_duration
                 AS end_date
      FROM     locations l
      where  location_id in ( 
        select location_id
        from   locations
        where  location_type = 'G'
      ) 

    ) src
    ON (   src.schedule_id = dst.schedule_id
       AND src.location_id = dst.location_id
       AND src.base_date   = dst.base_date
    )
    WHEN NOT MATCHED THEN
      INSERT (
        schedule_id,
        location_id,
        base_date,
        start_date,
        end_date
      ) VALUES (
        src.schedule_id,
        src.location_id,
        src.base_date,
        src.start_date,
        src.end_date
      );
  end loop;      
END;
/



    CREATE OR REPLACE PROCEDURE CREATE_ACCESS_HISTORY_INTERVAL
 (
  i_start_date IN DATE,
  i_end_date  IN DATE, 
  i_interval         IN PLS_INTEGER DEFAULT 10
) AS 
 
BEGIN
  
INSERT INTO access_history (employee_id, card_num, location_id, access_date)
WITH date_rows ( start_date, end_date ) AS (
 SELECT i_start_date,
                i_end_date
 FROM   DUAL
UNION ALL
 SELECT start_date + 
NUMTODSINTERVAL(i_interval, 'MINUTE'),
        end_date
 FROM   date_rows
 WHERE  start_date +
NUMTODSINTERVAL(i_interval, 'MINUTE') < end_date
)
SELECT     e.employee_id
,        e.card_num
,       l.location_id
,       d.start_date
FROM       employees e
CROSS JOIN locations l
CROSS JOIN date_rows d;
END;
/

CREATE table schedule_history(
      seq_num INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
       schedule_id NUMBER(4),
       base_date  DATE,
       employee_id NUMBER(6),
       location_id NUMBER(4),
       start_date DATE,
       end_date DATE,
       access_date DATE,
       status VARCHAR2(1),
constraint schedule_history_pk primary key (schedule_id, start_date, end_date)
       );


EXEC create_schedule_assignment(1,TRUNC(SYSDATE),TRUNC(SYSDATE)+2,1);
/

 
EXEC CREATE_ACCESS_HISTORY_INTERVAL(TRUNC(SYSDATE)+NUMTODSINTERVAL(CONVERT_TO_SECONDS('23:00:10'),'SECOND'),TRUNC(SYSDATE+1)+NUMTODSINTERVAL(CONVERT_TO_SECONDS('02:30:10'),'SECOND')) 


EXEC CREATE_SCHEDULE(1,SYSDATE,SYSDATE+2,CONVERT_TO_SECONDS('23:00:00'));
/

WITH  sch  AS
(

    SELECT  s.schedule_id, 
sa.schedule_date,
sa.employee_id,
s.location_id, s.start_date,
s.end_date
    ,       ROW_NUMBER () OVER ( PARTITION BY  s.schedule_id,  s.location_id, TRUNC (s.start_date)
        ORDER BY      s.start_date
   )      AS rn
    ,       TRUNC (s.start_date)                AS this_day
    ,       TRUNC (s.start_date) + 1            AS next_day
    ,             s.start_date - INTERVAL '2' MINUTE  AS early_date
    ,       
s.end_date + INTERVAL '2' MINUTE  AS late_date    FROM    schedule s
-- need JOIN to get employee_id 
JOIN schedule_assignment sa
           ON s.schedule_id = sa.schedule_id
          AND TRUNC(s.start_date) = sa.schedule_date
),    ah    AS
(

    SELECT  a.employee_id, a.location_id, a.access_date

    ,       ROW_NUMBER () OVER ( PARTITION BY  a.employee_id, a.location_id, TRUNC (a.access_date)

          ORDER BY      a.access_date

    )     AS rn
    FROM    access_history a
) 
select  sch.schedule_id,
sch.schedule_date,
sch.employee_id,
sch.location_id, sch.start_date, 
sch.end_date,   ah.access_date,
CASE
                            WHEN  ah.access_date  <  sch.early_date       THEN  'E'

WHEN  
ah.access_date >=
sch.early_date AND
ah.access_date  <= sch.late_date        THEN  'G'

WHEN  ah.access_date  > sch.late_date        THEN  'L'
                                          WHEN  ah.access_date  IS NULL                THEN  'M'
          END  AS status


FROM sch
LEFT OUTER JOIN                ah  ON   ah.employee_id  = sch.employee_id
                                   AND  ah.location_id  = sch.location_id
                                   AND  ah.access_date  >= sch.this_day
                                   AND  ah.access_date  <  sch.next_day 
ORDER BY  
sch.employee_id, sch.start_date;


--  FAILED --
WITH  sch  AS
(

    SELECT  s.schedule_id, 
sa.schedule_date,
sa.employee_id,
s.location_id, s.start_date,
s.end_date
    ,       ROW_NUMBER () OVER ( PARTITION BY  s.schedule_id,  s.location_id, TRUNC (s.start_date)
        ORDER BY      s.start_date
   )      AS rn
    ,       TRUNC (s.start_date)                AS this_day
    ,       TRUNC (s.start_date) + 1            AS next_day
    ,             s.start_date - INTERVAL '2' MINUTE  AS early_date
    ,       
s.end_date + INTERVAL '2' MINUTE  AS late_date    FROM    schedule s
-- need JOIN to get employee_id 
JOIN schedule_assignment sa
           ON s.schedule_id = sa.schedule_id
          AND TRUNC(s.start_date) = sa.schedule_date
),    ah    AS
(

    SELECT  a.employee_id, a.location_id, a.access_date

    ,       ROW_NUMBER () OVER ( PARTITION BY  a.employee_id, a.location_id, TRUNC (a.access_date)

          ORDER BY      a.access_date

    )     AS rn
    FROM    access_history a

) 

  INSERT INTO SCHEDULE_HISTORY(
       schedule_id
       ,base_date
       ,employee_id
       ,location_id
       ,start_date
       ,end_date
       ,access_date
       ,status
 )
SELECT sch.schedule_id,
sch.schedule_date,
sch.employee_id,
sch.location_id, sch.start_date, 
sch.end_date,   ah.access_date,
CASE
                            WHEN  ah.access_date  <  sch.early_date       THEN  'E'

WHEN  
ah.access_date >=
sch.early_date AND
ah.access_date  <= sch.late_date        THEN  'G'

WHEN  ah.access_date  > sch.late_date        THEN  'L'
                                          WHEN  ah.access_date  IS NULL                THEN  'M'
          END  AS status

FROM sch
LEFT OUTER JOIN                ah  ON   ah.employee_id  = sch.employee_id
                                   AND  ah.location_id  = sch.location_id
                                   AND  ah.access_date  >= sch.this_day
                                   AND  ah.access_date  <  sch.next_day;

您的失败陈述可以概括为:

WITH  sch  AS
(
....
),    ah    AS
(
...
)
INSERT INTO SCHEDULE_HISTORY(
...
)
SELECT ...
FROM sch
LEFT OUTER JOIN ah ON ...

您插入的位置有误;它应该组织为:

INSERT INTO SCHEDULE_HISTORY(
...
)
WITH  sch  AS
(
....
),    ah    AS
(
...
)
SELECT ...
FROM sch
LEFT OUTER JOIN ah ON ...

... 将 CTE 作为 select 的一部分,在插入之后,而不是在插入之前。这就是您对之前的 INSERT INTO employees 语句所做的。

解析器期望 selectwith 子句之后;因此出现了您看到的错误。

db<>fiddle,尽管它仍然因数据的唯一约束错误而失败。您可以在仅查询版本中看到重复项 - 因此您需要修复该查询。