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; |
我首先编译了每个过程,而没有调用另一个过程。完成此操作后,我重新编写了插入过程以调用更新过程。我编译了它,一切都很好。当我尝试在更新过程中调用插入过程时,问题就出现了。
我在编译时收到以下错误:
无论如何我能做到这一点吗?或者我应该只在每个过程中编写所有代码而不调用另一个?
在此问题上的任何帮助或指导将不胜感激。
问题是您在两个过程之间存在循环依赖关系。编译程序所依赖的对象会使该程序无效:循环依赖意味着您陷入了无效和重新编译的永久循环中。
一种解决方案是使用包。对象依赖于包规范,所以只要不改变我们就可以对包体做任何事情。
1 2 3 4 |
然后你的身体可以像这样进行引用:
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; |
尽管如此,循环依赖仍然很糟糕。它指出了一个设计缺陷。更好的解决方案是有一个过程,
或者,对于这种情况,有 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; |