【问题标题】:Oracle INSERT expecting SELECTOracle INSERT 期待 SELECT
【发布时间】:2021-10-27 10:14:40
【问题描述】:

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

在这种情况下,有一段代码带有 COMMENT FAILED,我试图最终将行插入到表中,提示为 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;

【问题讨论】:

  • 将近 400 行代码。哪一行失败,确切的错误信息是什么?
  • 您发布的几乎所有代码都与实际错误无关;当然,如果您知道这一点,那么您可能能够找出问题所在。但是您可以系统地简化失败的语句,直到它起作用 - 然后您知道您更改的最后一件事就是问题;或者您不能再简化它,然后只能发布简化的代码,这将需要更少的依赖项。请阅读有关为将来的问题提供minimal reproducible example 的信息,重点是minimal。在执行此操作的过程中,您通常会发现问题。

标签: sql oracle select insert


【解决方案1】:

你的失败陈述可以总结为:

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 作为选择的一部分,在插入之后,而不是之前。这就是您对之前的 INSERT INTO employees 语句所做的。

解析器期待selectwith 子句之后;因此你看到的错误。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-11
    • 1970-01-01
    • 2018-01-29
    • 2014-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多