【问题标题】:Oracle PLSQL problems creating a procedureOracle PLSQL 创建过程的问题
【发布时间】:2021-10-12 17:18:12
【问题描述】:

我正在尝试将一些 SQL 包装到 PLSQL 过程中,以便用户可以传递参数而不是手动编辑 WHERE 子句,这会使他们有可能破坏工作代码。我要移植的 SQL 代码嵌入在 PROCEDURE 中,但 INTO 子句除外。

我知道在 PLSQL 中为了选择行需要一个 INTO 子句。环顾四周后,我看到了一个示例,它创建了一个对象和表类型。我不想做的事情,对我来说似乎过于复杂。如果可能的话,我希望将所有内容都保留在该过程的本地。

我也愿意在 access_history 表上使用 BULK collect,如果这会是一种更有效的方法。

当我尝试创建下面的过程时它不起作用,这是我可以使用一些帮助和 PLSQL 专业知识来指导我以最佳方向生成所需数据的地方。

其次,有没有办法使用 DEFAULT 值来确定要检索的行数。

如果过程是这样调用的:

EXEC LAST_EMPLOYEE_HISTORY(1) 这意味着获取employee_id=1 的最后20(DEFAULT)行,其中20 是默认值。

如果过程是这样调用的:

EXEC LAST_EMPLOYEE_HISTORY(1, 50) 表示获取employee_id=1 的最后50 行。

在解释和帮助我解决问题方面的任何帮助和专业知识将不胜感激。提前感谢所有回答的人。

下面是我的测试用例。

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


    CREATE OR REPLACE TYPE access_history_obj AS OBJECT(
    employee_id    NUMBER(6), 
    first_name       VARCHAR2(20),
     last_name         VARCHAR2(20),
     card_num         VARCHAR2(10),
     location_id        NUMBER(6),
    location_name  VARCHAR2(30),
    access_date      DATE
    );
 

    CREATE OR REPLACE TYPE access_history_table IS TABLE OF access_history_obj;

    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 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 <= 5;
       
      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
        );

   INSERT INTO       access_history(
      employee_id,
       card_num,
       location_id,
       access_date
     )
 WITH rws AS   ( 
      SELECT 1,'J11111',2,TO_DATE('2021/08/15 08:30:25', 'YYYY/MM/DD HH24:MI:SS') FROM dual UNION ALL
    SELECT 1,'J11111',3,TO_DATE('2021/08/15 18:30:35', 'YYYY/MM/DD HH24:MI:SS') FROM dual UNION ALL
   SELECT 2,'E11111',2,TO_DATE('2021/08/15 11:20:35', 'YYYY/MM/DD HH24:MI:SS') FROM dual) SELECT * FROM rws; 

            CREATE OR REPLACE PROCEDURE LAST_EMPLOYEE_HISTORY( 
    p_employee_id IN NUMBER,
     p_rws IN number)
     AS
    BEGIN
     with rws as (
         select e.employee_id, 
              e.first_name,
              e.last_name,
              e.card_num,
              l.location_id,
              l.location_name,
              a.access_date,
              row_number () over 
          (
           partition by e.employee_id
            order by a.access_date DESC
          ) rn  
         FROM  employees    e
        JOIN     access_history a ON a.employee_id = e.employee_id
 JOIN  locations     l ON l.location_id = a.location_id
         )

        select employee_id, 
                first_name,
                last_name,
                card_num,
                location_id,
                location_name,
               access_date INTO access_history_table
      from rws 
      where 
         employee_id = p_employee_id AND
         rn <= p_rws
         order by employee_id,  access_date desc;
      END;

    EXEC LAST_EMPLOYEE_HISTORY(1)

【问题讨论】:

  • 您可以考虑使用table function 而不是过程。
  • @sticky bit 我不想插入行,我只需要为employee_id 选择最后一个N。我希望格式包含我在示例中指定的值
  • 我很困惑...我对表函数的建议与INSERTing 有什么关系?

标签: sql oracle stored-procedures plsql


【解决方案1】:

将光标作为OUT 参数并在过程的签名中使用DEFAULT

CREATE PROCEDURE LAST_EMPLOYEE_HISTORY( 
  i_employee_id IN EMPLOYEES.EMPLOYEE_ID%TYPE,
  i_rws         IN PLS_INTEGER DEFAULT 20,
  o_cursor      OUT SYS_REFCURSOR
)
AS
BEGIN
  OPEN o_cursor FOR
    SELECT e.employee_id, 
           e.first_name,
           e.last_name,
           e.card_num,
           l.location_id,
           l.location_name,
           a.access_date
    FROM   employees    e
           INNER JOIN access_history a
           ON a.employee_id = e.employee_id
           INNER JOIN locations l
           ON l.location_id = a.location_id
    WHERE  e.employee_id = i_employee_id
    ORDER BY access_date DESC
    FETCH FIRST i_rws ROWS ONLY;
END;
/

然后在 SQL/Plus 或 SQL Developer 中:

VARIABLE cur REFCURSOR;
EXECUTE LAST_EMPLOYEE_HISTORY(1, 50, :cur);
PRINT cur;

db小提琴here

注意:从 Oracle 12 开始,您可以使用 FETCH FIRST n ROWS ONLY 语法。

【讨论】:

  • @MTO 您的解决方案完美运行,但有没有办法将所有代码封装到过程中,而不是使用块来选择它们。这是为一群非技术人员准备的,他们要做的越少越好。他们可以轻松处理 1 行 EXEC 命令。也许将所有内容封装到一个包中并调用它?下一步是相同的输出,但指定 location_id。再次感谢您的响应和专业知识。
  • @Beefstu 我不确定我是否理解您的意思,因为在我上面的回答中,所有代码都在程序内,您可以使用 EXEC 调用程序(如果您正在使用的用户界面支持它并且您已经定义了一个 REFCURSOR 变量;但是 dbfiddle 不支持它)。
  • @MTO 感谢您的解释。我希望将所有代码封装到一个过程中,或者可能制作匿名块 GENERIC,以便另一个过程可以使用它来获取 LAST_LOCATION_HISTORY(尚未编码)并显示相同的格式。匿名块调用了 LAST_EMPLOYEE_HISTORY,因此除非某种 CASE 语句可用于调用任一过程,否则不会使其成为通用块。
  • @Beefstu 我仍然不明白,因为您提出的 ANY 解决方案必须调用该过程,并且您希望返回结果集(或对象),因此您在使用此解决方案时遇到的问题与使用 EVERY 其他解决方案时遇到的问题相同。如果您使用的是 SQL/plus(或 SQL Developer),那么只需使用 VARIABLE 语句,然后使用 EXEC 语句,然后使用 PRINT 语句。如果您使用的是其他用户界面,请查看有关如何显示光标的文档。
  • @MTO 我想说的是我需要一个匿名块的包装器。如果传入“E”,则使用参数调用 LAST_EMPLOYEE_HISTORY。如果传入“L”,则使用参数调用 LAST_LOCATION_HISTORY。第一个参数是employee_id 或location_id 的数字,第二个参数是行数的覆盖。这样,我可以调用任一过程并保留其余代码,这些代码似乎是通用的。我认为我需要做的就是将块转换为过程以将参数传递到正确的过程中。这有意义吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-02
  • 2011-06-01
  • 1970-01-01
相关资源
最近更新 更多