我可以在 Oracle 中创建两个相互调用的过程吗?

Can I make two procedures that call each other out in Oracle?

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;

要更新销售明细:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 之前,所有过程都是有效的。不知何故,让每个过程调用另一个过程会使它们都无效。

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

在此问题上的任何帮助或指导将不胜感激。


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

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

1
2
3
4
 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;

然后你的身体可以像这样进行引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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 中是插入还是更新(或确实删除)。了解更多。

"am I better just writing the code to perform each procedure on its own"

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


使用包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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;
/

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
/

你真的不需要两个程序。
只需用 MERGE 做一个。像下面这样的东西应该是一个好的开始;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;