PL SQL: Inner Function Within Merge Is Called More Often Than Expected
Introduction
In PL/SQL, the MERGE
statement is a powerful tool for updating or inserting data into a target table based on a source table. However, when using an inner function within a MERGE
statement, it can be called more often than expected, leading to performance issues. In this article, we will discuss this issue and explore possible solutions.
The Problem
Let's consider a scenario where we have a setup where we would like to call a function once per group and apply the result to multiple rows (multiple EANs). However, the function is called as many times as we have EANs. This might be due to the way the MERGE
statement is executed, where the inner function is called for each row in the source table.
Example Use Case
Suppose we have a table orders
with columns order_id
, ean
, and quantity
, and a table products
with columns ean
, price
, and description
. We want to update the price
column in the orders
table based on the price
column in the products
table, but only for each unique ean
group.
CREATE TABLE orders (
order_id NUMBER,
ean VARCHAR2(20),
quantity NUMBER
);
CREATE TABLE products (
ean VARCHAR2(20),
price NUMBER,
description VARCHAR2(100)
);
INSERT INTO orders (order_id, ean, quantity)
VALUES (1, 'EAN1', 10);
INSERT INTO orders (order_id, ean, quantity)
VALUES (2, 'EAN1', 20);
INSERT INTO orders (order_id, ean, quantity)
VALUES (3, 'EAN2', 30);
INSERT INTO products (ean, price, description)
VALUES ('EAN1', 10.99, 'Product 1');
INSERT INTO products (ean, price, description)
VALUES ('EAN2', 9.99, 'Product 2');
We can use a MERGE
statement with an inner function to update the price
column in the orders
table:
CREATE OR REPLACE FUNCTION get_price(p_ean VARCHAR2)
RETURN NUMBER
IS
v_price NUMBER;
BEGIN
SELECT price INTO v_price
FROM products
WHERE ean = p_ean;
RETURN v_price;
END;
/
MERGE INTO orders o
USING (
SELECT ean, quantity, get_price(ean) AS price
FROM orders
GROUP BY ean
) s
ON (o.ean = s.ean)
WHEN MATCHED THEN
UPDATE SET o.price = s.price;
However, when we execute this MERGE
statement, the inner function get_price
is called as many times as we have EANs, leading to performance issues.
Possible Solutions
To avoid calling the inner function as many times as we have EANs, we can use a few possible solutions:
1. Use a Common Table Expression (CTE)
We can use a CTE to group the rows in the orders
table by ean
and then apply the MERGE
statement to the grouped rows.
WITH grouped_orders AS SELECT ean, quantity, get_price(ean) AS price
FROM orders
GROUP BY ean
)
MERGE INTO orders o
USING grouped_orders s
ON (o.ean = s.ean)
WHEN MATCHED THEN
UPDATE SET o.price = s.price;
2. Use a Temporary Table
We can create a temporary table to store the grouped rows and then apply the MERGE
statement to the temporary table.
CREATE GLOBAL TEMPORARY TABLE temp_orders (
ean VARCHAR2(20),
quantity NUMBER,
price NUMBER
) ON COMMIT DELETE ROWS;
INSERT INTO temp_orders (ean, quantity, price)
SELECT ean, quantity, get_price(ean)
FROM orders
GROUP BY ean;
MERGE INTO orders o
USING temp_orders s
ON (o.ean = s.ean)
WHEN MATCHED THEN
UPDATE SET o.price = s.price;
3. Use a PL/SQL Loop
We can use a PL/SQL loop to iterate over the unique ean
groups and apply the MERGE
statement to each group.
DECLARE
CURSOR c_eans IS
SELECT ean
FROM orders
GROUP BY ean;
v_ean VARCHAR2(20);
BEGIN
FOR v_ean IN c_eans LOOP
MERGE INTO orders o
USING (
SELECT ean, quantity, get_price(ean) AS price
FROM orders
WHERE ean = v_ean
) s
ON (o.ean = s.ean)
WHEN MATCHED THEN
UPDATE SET o.price = s.price;
END LOOP;
END;
Conclusion
Introduction
In our previous article, we discussed the issue of an inner function within a MERGE
statement being called more often than expected. We explored possible solutions, including using a CTE, a temporary table, and a PL/SQL loop. In this article, we will answer some frequently asked questions related to this issue.
Q: What is the main cause of the inner function being called more often than expected?
A: The main cause of the inner function being called more often than expected is the way the MERGE
statement is executed. When the MERGE
statement is executed, the inner function is called for each row in the source table, leading to multiple calls to the function.
Q: Can I use a subquery instead of a function to avoid the issue?
A: Yes, you can use a subquery instead of a function to avoid the issue. However, this may lead to performance issues if the subquery is complex or if it involves multiple joins.
Q: How can I optimize the performance of the inner function?
A: To optimize the performance of the inner function, you can consider the following:
- Use an index on the column used in the function.
- Use a CTE or a temporary table to reduce the number of calls to the function.
- Use a PL/SQL loop to iterate over the unique groups and apply the function to each group.
- Use a parallel query to execute the function in parallel.
Q: Can I use a stored procedure instead of a function to avoid the issue?
A: Yes, you can use a stored procedure instead of a function to avoid the issue. However, this may lead to performance issues if the stored procedure is complex or if it involves multiple calls to other procedures.
Q: How can I debug the issue?
A: To debug the issue, you can use the following steps:
- Use the
EXPLAIN PLAN
statement to analyze the execution plan of theMERGE
statement. - Use the
DBMS_XPLAN.DISPLAY
function to display the execution plan. - Use the
DBMS_XPLAN.DISPLAY_CURSOR
function to display the execution plan for a specific cursor. - Use the
DBMS_XPLAN.DISPLAY_AW
function to display the execution plan in a graphical format.
Q: Can I use a materialized view to avoid the issue?
A: Yes, you can use a materialized view to avoid the issue. A materialized view is a pre-computed result set that is stored in a physical table. You can create a materialized view that contains the result of the inner function, and then use the materialized view in the MERGE
statement.
Q: How can I maintain the materialized view?
A: To maintain the materialized view, you can use the following steps:
- Use the
DBMS_MVIEW.REFRESH
procedure to refresh the materialized view. - Use the
DBMS_MVIEW.REFRESH
procedure with theON COMMIT
option to refresh the materialized view on commit. - Use the
DBMS_M.REFRESH
procedure with theON DEMAND
option to refresh the materialized view on demand.
Conclusion
In this article, we answered some frequently asked questions related to the issue of an inner function within a MERGE
statement being called more often than expected. We provided possible solutions, including using a CTE, a temporary table, and a PL/SQL loop. We also discussed how to optimize the performance of the inner function and how to debug the issue.