【问题标题】:Can I make two procedures that call each other out in Oracle?我可以在 Oracle 中创建两个相互调用的过程吗?
【发布时间】:2017-03-28 09:10:37
【问题描述】:

我有一个数据库来存储销售信息。我目前正在编写两个程序,一个用于插入有关销售的新细节,另一个用于更新已经存在的细节。如果用户想要插入一个已经存在的细节,我希望我的过程调用更新过程。同样,如果用户想更新一个不存在的细节,我想调用插入过程。我插入新细节的代码如下:

CREATE OR REPLACE procedure EX1.insert_detail
    (id_sale IN INTEGER, id_product IN INTEGER, qty IN INTEGER)
IS
    id_detail INTEGER;
    mi_seq INTEGER;
    u_price FLOAT;
BEGIN
    BEGIN
        select detail.id_sales_order_detail into id_detail from EX1.sales_order_detail detail
            where detail.id_sales_order = id_sale and detail.id_prod = id_product;
    END;
    if id_detail is null then
        select seq_sales_order_detail.nextval into mi_seq from dual;
        select prod.list_price into u_price from EX1.product prod;

        insert into EX1.sales_order_detail values
            (mi_seq, id_sale, id_product, qty, u_price, EX1.calc_discount(qty, u_price));
        BEGIN
            EX1.update_costs(id_sale);
        END;
    else
        EX1.update_detail(id_sale, id_product, qty);
    end if;
END insert_detail;

要更新销售详情:

CREATE OR REPLACE procedure EX1.update_detail
    (id_sale IN INTEGER, id_product IN INTEGER, qty IN INTEGER)
IS
    id_detail INTEGER;
BEGIN
    BEGIN
        select detail.id_sales_order_detail into id_detail from EX1.sales_order_detail detail
            where detail.id_sales_order = id_sale and detail.id_prod = id_product;
    END;
    if id_detail is null then
        BEGIN
            EX1.INSERT_DETAIL(id_sale, id_product, qty);
        END;
    else
        UPDATE EX1.SALES_ORDER_DETAIL
            SET ORDER_QTY = ORDER_QTY + qty
        WHERE ID_SALES_ORDER = id_sale AND id_prod = id_product;
        BEGIN
            EX1.update_costs(id_sale);
        END;
    end if;
END update_detail;

我首先编译了每个过程而不调用另一个过程。完成此操作后,我重写了插入过程以调用更新过程。我编译了它,一切都很好。当我试图在我的更新过程中调用插入过程时,问题就来了。

我在编译时收到以下错误:ex1.insert_detail is invalid。但是,在编译ex1.update_detail之前,所有程序都是有效的。不知何故,让每个过程调用另一个过程会使它们都无效。

无论如何我可以做到这一点吗?还是应该只在每个过程中编写所有代码而不调用另一个?

我们将不胜感激任何有关此问题的帮助或指导。

【问题讨论】:

    标签: oracle stored-procedures plsql


    【解决方案1】:

    问题是您在两个过程之间存在循环依赖关系。编译程序所依赖的对象会使该程序无效:循环依赖意味着您陷入了无效和重新编译的永久循环。

    一种解决方案是使用包。对象依赖于包规范,所以只要不改变我们就可以对包体做任何事情。

     create or replace package pkg_sales as
         procedure insert_detail(id_sale IN INTEGER, id_product IN INTEGER, qty );
        procedure update_detail(id_sale IN INTEGER, id_product IN INTEGER, qty );
    end pkg_sales;
    

    那么你的身体可以像这样进行引用:

    CREATE OR REPLACE  package body pkg_sales as
    
        procedure insert_detail(id_sale IN INTEGER, id_product IN INTEGER, qty IN INTEGER)
        IS
            id_detail INTEGER;
            mi_seq INTEGER;
            u_price FLOAT;
        BEGIN
            BEGIN
                select detail.id_sales_order_detail into id_detail from EX1.sales_order_detail detail
                    where detail.id_sales_order = id_sale and detail.id_prod = id_product;
            END;
            if id_detail is null then
                select seq_sales_order_detail.nextval into mi_seq from dual;
                select prod.list_price into u_price from EX1.product prod;
    
                insert into EX1.sales_order_detail values
                    (mi_seq, id_sale, id_product, qty, u_price, EX1.calc_discount(qty, u_price));
                BEGIN
                    EX1.update_costs(id_sale);
                END;
            else
                update_detail(id_sale, id_product, qty);
            end if;
        END insert_detail;
    
       procedure update_detail(id_sale IN INTEGER, id_product IN INTEGER, qty IN INTEGER)
        IS
            id_detail INTEGER;
        BEGIN
            BEGIN
                select detail.id_sales_order_detail into id_detail from EX1.sales_order_detail detail
                    where detail.id_sales_order = id_sale and detail.id_prod = id_product;
            END;
            if id_detail is null then
                BEGIN
                    INSERT_DETAIL(id_sale, id_product, qty);
                END;
            else
                UPDATE EX1.SALES_ORDER_DETAIL
                    SET ORDER_QTY = ORDER_QTY + qty
                WHERE ID_SALES_ORDER = id_sale AND id_prod = id_product;
                BEGIN
                    update_costs(id_sale);
                END;
            end if;
        END update_detail;
    end pkg_sales;
    

    但循环依赖仍然很糟糕。它指出了一个设计缺陷。更好的解决方案是有一个过程,procedure manage_detail(id_sale IN INTEGER, id_product IN INTEGER, qty IN INTEGER),它执行控制逻辑,并相应地决定是调用插入还是更新子例程。

    另外,对于这种情况,还有 MERGE 语句,它处理在纯 SQL 中是插入还是更新(或者实际上是删除)。 Find out more.


    “我最好只编写代码来单独执行每个过程”

    可能不会。你最终会复制一些代码。模块化是一件好事,但更好地构建逻辑以避免循环依赖。除此之外,您还冒着无限递归的风险。

    【讨论】:

    • 那么,我是否最好只编写代码来独立执行每个过程,而不依赖于其他过程?
    • 我已经用一些建议扩展了我的答案。
    • 这是一个很好的答案,非常感谢。但现在我确实意识到我所做的是一个糟糕的编程解决方案。我会根据你的建议改一下。
    • 我投票赞成使用 MERGE 语句的建议;这让 Oracle 决定是否需要插入或更新。如果您决定坚持使用单独的插入/更新语句,则无需先执行 select 语句 - 假设您在表上有一个主键(如果没有,为什么不呢?!) - 您只需执行插入,如果它因 dup_val_on_index 失败,则处理异常块中的错误并在那里进行更新。或者,如果您期望更新多于插入,请先执行更新,并且仅在更新语句的行数为 0 时才执行插入。
    【解决方案2】:

    使用包:

    CREATE OR REPLACE PACKAGE TEST_CIRCULAR_REFERENCE
    IS
      PROCEDURE A( value NUMBER );
      PROCEDURE B( value NUMBER );
    END;
    /
    
    CREATE OR REPLACE PACKAGE BODY TEST_CIRCULAR_REFERENCE
    IS
      PROCEDURE A( value NUMBER )
      IS
      BEGIN
        DBMS_OUTPUT.PUT_LINE( 'A: ' || value );
        IF value <= 1 THEN
          NULL;
        ELSIF MOD( value, 2 ) = 0 THEN
          A( value / 2 );
        ELSE
          B( value );
        END IF;
      END;
    
      PROCEDURE B( value NUMBER )
      IS
      BEGIN
        DBMS_OUTPUT.PUT_LINE( 'B: ' || value );
        IF value <= 1 THEN
          NULL;
        ELSIF MOD( value, 2 ) = 0 THEN
          A( value );
        ELSE
          B( 3 * value + 1 );
        END IF;
      END;
    END;
    /
    

    所以你的包裹应该是这样的:

    CREATE OR REPLACE PACKAGE EX1.DETAILS
    IS
      FUNCTION get_detail_id ( id_product EX1.sales_order_detail.id_prod%TYPE )
        RETURN EX1.sales_order_detail.id_sales_order_detail%TYPE;
    
      PROCEDURE insert_detail(
        id_sale    IN EX1.sales_order_detail.id_sale%TYPE
        id_product IN EX1.sales_order_detail.id_prod%TYPE,
        qty        IN EX1.sales_order_detail.id_qty%TYPE
      );
    
      PROCEDURE update_detail(
        id_sale    IN EX1.sales_order_detail.id_sale%TYPE
        id_product IN EX1.sales_order_detail.id_prod%TYPE,
        qty        IN EX1.sales_order_detail.id_qty%TYPE
      );
    END;
    /
    

    【讨论】:

      【解决方案3】:

      您实际上并不需要两个程序。 只需使用 MERGE 来代替。像下面这样的东西应该是一个好的开始;

      CREATE OR REPLACE procedure EX1.upd_ins_sales_detail(p_id_sale IN INTEGER, p_id_product IN INTEGER, p_qty IN INTEGER)
      IS
      
      BEGIN
      
          MERGE INTO ex1.sales_order_detail sod
          USING (SELECT exist_sod.id_sales_order_detail, exist_sod.id_prod, prod.list_price
                 FROM   ex1.sales_order_detail exist_sod
                        INNER JOIN
                        ex1.product prod ON (prod.prod_id = exist_sod.prod_id)
                 WHERE  exist_sod.id_sales_order = p_id_sale
                 AND    exist_sod.id_prod = p_id_product
                ) upd
          ON    (    sod.id_sales_order_detail = upd.id_sales_order_detail)
          WHEN MATCHED THEN UPDATE SET sod.qty = sod.qty + p_qty
          WHEN NOT MATCHED THEN INSERT (mi_seq, id_sale, id_product, qty, u_price, EX1.calc_discount(qty, u_price))
                                VALUES (seq_sales_order_detail.nextval,
                                        p_id_sale,
                                        p_id_product,
                                        upd.list_price,
                                        EX1.calc_discount(p_qty, upd.list_price)
      
          EX1.update_costs(id_sale);
      
          COMMIT;
      
      EXCEPTION
      WHEN ..... THEN
        put an exception as required here
      
      END upd_ins_sales_detail; 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-06-29
        • 2014-08-10
        • 1970-01-01
        • 1970-01-01
        • 2019-10-21
        • 1970-01-01
        • 2018-08-10
        相关资源
        最近更新 更多