Oracle 错误创建过程需要 iNTO [重复]

Posted

技术标签:

【中文标题】Oracle 错误创建过程需要 iNTO [重复]【英文标题】:Oracle error creating procedure expecting iNTO [duplicate] 【发布时间】:2021-07-30 17:33:01 【问题描述】:

我有一些运行良好的 SQL 代码。当我尝试将它包装在一个过程中时,它无法创建并且我收到以下错误。谁能解释一下问题是什么以及如何解决?

以下是我的测试用例。提前感谢所有回答的人。

Errors: PROCEDURE CREATE_ACCESS_HISTORY
Line/Col: 4/1 PLS-00428: an INTO clause is expected in this SELECT statement
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)
                      );

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 locations AS
SELECT level AS location_id,
       'Door ' || level AS location_name,
       CASE round(dbms_random.value(1,3)) 
                    WHEN 1 THEN 'A' 
                    WHEN 2 THEN 'T' 
                    WHEN 3 THEN 'T' 
       END AS location_type
  FROM dual
CONNECT BY level <= 50;

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

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 or replace procedure create_access_history(p_start_date date, p_end_date date)
IS
BEGIN
     WITH cntr AS(SELECT LEVEL - 1 AS n
                    FROM dual
                 CONNECT BY LEVEL <= 25 -- Max number of rows per employee per date
                 )
                 ,got_location_num  AS(SELECT location_id,
                                              ROW_NUMBER() OVER (ORDER BY location_id) AS location_num,
                                              COUNT(*) OVER () AS max_location_num
                                         FROM locations)
                 ,employee_days AS(SELECT e.employee_id,
                                          e.card_num,
                                          d.column_value AS access_date,
                                          dbms_random.value (0, 25) AS rn    -- 0 to max number of rows per employee per date
                                     FROM employees e
                                    CROSS JOIN TABLE (generate_dates_pipelined (p_start_date, p_end_date)) d)
                 ,employee_n_days AS (SELECT ed.employee_id,
                                             ed.card_num,
                                             ed.access_date,
                                             dbms_random.value (0, 1) AS lrn
                                        FROM employee_days ed
                                        JOIN cntr c ON c.n <= ed.rn
                                     )
     SELECT n.employee_id,
            n.card_num,
            l.location_id,
            n.access_date + NUMTODSINTERVAL(FLOOR(DBMS_RANDOM.VALUE(0,86399)), 'SECOND') AS ACCESS_DATE 
       FROM employee_n_days n
       JOIN got_location_num l ON l.location_num = CEIL (n.lrn * l.max_location_num); 
       END;
EXEC  create_access_history (DATE '2021-08-01',  DATE '2021-08-10');

【问题讨论】:

@Ankit 不,它没有。我的问题是为什么需要INTO?当不在过程中时,SQL 可以完美运行,并且 p_start_date 和 p_end_date 是 SQL 中的硬编码日期 在您的程序 create_access_history 中,您只选择数据,而在 Oracle 中,您不能仅通过选择它来返回数据。所以你必须使用其他一些技术,比如 SYS_REFCURSOR 来返回它。由于 System 期望您应该将所选数据存储到变量中,这就是为什么它说 into 子句是强制性的。 来自重复的答案,“在 PLSQL 块中,选择语句的列必须分配给变量,而在 SQL 语句中不是这种情况。”这就是 oracle 的工作原理。没什么好说的了。 SQL 查询(返回结果集)和 PL/SQL 块/过程(执行某些操作 - 在您的情况下使用结果集)之间存在差异。在 PL/SQL 块中,您对查询的结果进行一些处理——唯一的方法是将结果存储在变量中。因此需要INTO。 您不要“将结果直接插入表中”,因为有问题的过程中唯一的语句是select。您可以考虑花一些时间在2Day Developer Guide 上学习Oracle 中的SQL 和PL/SQL 基础知识,并获得更系统和结构化的开发流程。 【参考方案1】:

您的过程 create_access_history 应该看起来相似 -

create or replace procedure create_access_history(p_start_date date,
                                                  p_end_date date,
                                                  result out sys_refcursor)
IS
BEGIN
     OPEN result FOR
     WITH cntr AS(SELECT LEVEL - 1 AS n
                    FROM dual
                 CONNECT BY LEVEL <= 25 -- Max number of rows per employee per date
                 )
                 ,got_location_num  AS(SELECT location_id,
                                              ROW_NUMBER() OVER (ORDER BY location_id) AS location_num,
                                              COUNT(*) OVER () AS max_location_num
                                         FROM locations)
                 ,employee_days AS(SELECT e.employee_id,
                                          e.card_num,
                                          d.column_value AS access_date,
                                          dbms_random.value (0, 25) AS rn    -- 0 to max number of rows per employee per date
                                     FROM employees e
                                    CROSS JOIN TABLE (generate_dates_pipelined (p_start_date, p_end_date)) d)
                 ,employee_n_days AS (SELECT ed.employee_id,
                                             ed.card_num,
                                             ed.access_date,
                                             dbms_random.value (0, 1) AS lrn
                                        FROM employee_days ed
                                        JOIN cntr c ON c.n <= ed.rn
                                     )
     SELECT n.employee_id,
            n.card_num,
            l.location_id,
            n.access_date + NUMTODSINTERVAL(FLOOR(DBMS_RANDOM.VALUE(0,86399)), 'SECOND') AS ACCESS_DATE 
       FROM employee_n_days n
       JOIN got_location_num l ON l.location_num = CEIL (n.lrn * l.max_location_num); 
END;

然后你必须调用你的过程 -

DECLARE resultset SYS_REFCURSOR;

BEGIN
     EXEC create_access_history (DATE '2021-08-01',
                                 DATE '2021-08-10',
                                 resultset);
     FOR I IN 1..resultset.count LOOP
         DBMS_OUTPUT.PUT_LINE(I.employee_id || ' ' || I.card_num || ' ' || I.location_id || ' ' || I.ACCESS_DATE);
     END LOOP;
END;

【讨论】:

以上是关于Oracle 错误创建过程需要 iNTO [重复]的主要内容,如果未能解决你的问题,请参考以下文章

oracle的存储过程中,使用select into 语句的错误

如何解决oracle存储过程select into问题

关于oracle存储过程select into 未找到数据问题

Oracle merge into的优势

Oracle使用merge into 编写存储过程 遇编译错误:PL/SQL: ORA-00926: 缺失 VALUES 关键字

oracle 存储过程 into 没找到数据 解决办法