sqloracle-databaseplsqldatabase-cursor

SQL cursor take too long to complete


DECLARE
  orderid NUMBER;
  customerid NUMBER;
  channel VARCHAR2(20);
  amount NUMBER;

  CURSOR orders_cursor IS
    SELECT
      order_id,
      customer_id,
      channel,
      FINAL_PROFIT(order_id) AS amount
    FROM temp_orders
     FOR UPDATE ;
BEGIN
  OPEN orders_cursor;
    LOOP
    FETCH orders_cursor INTO orderid, customerid, channel, amount;
    EXIT WHEN orders_cursor%NOTFOUND;

    IF amount < 0 THEN
      INSERT INTO deficit (orderid, customerid, channel, amount)
        VALUES (orderid, customerid, channel, -amount);
    ELSE
      INSERT INTO profit (orderid, customerid, channel, amount)
        VALUES (orderid, customerid, channel, amount);
    END IF;
  END LOOP;
  CLOSE orders_cursor;
END;

I have this cursor that takes order_id,customer_id,channel and a value from the function FINAL_PROFIT and then checks if amount is positive or negative and puts it in the rigth table .The cursor Works but it takes way to long to complete(30+minutes).I need it to be faster. This is the FINAL_PROFIT function

create or replace FUNCTION FINAL_PROFIT (o_id NUMBER)
RETURN NUMBER 
IS  
    f_profit FLOAT := 0;
    v_delay NUMBER := 0;
BEGIN
    v_delay := MAX_DELAY(o_id);

    SELECT SUM(o.price - o.cost - (v_delay * (0.001 * TO_NUMBER(p.list_price,'9999.99')))) as FINAL_PROFT
    INTO f_profit  
    FROM ORDERS o 
    JOIN PRODUCTS p ON o.product_id = p.product_id 
    WHERE o_id = o.order_id
    GROUP BY o_id ;
    
    RETURN f_profit;  
END FINAL_PROFIT;

I tried FOR UPDATE because i thougth would make it faster but i didnt notice any diference in time


Solution

  • There's so very few times you'll encounter a need to run SQL inside of a loop that you can pretty much just make it a rule that if you find yourself thinking "I can run this SQL in a loop" to immediately think "so I must be thinking about this wrong".

    Instead, two insert statements will take care of this:

    INSERT INTO deficit (orderid, customerid, channel, amount)
    SELECT
      order_id,
      customer_id,
      channel,
      FINAL_PROFIT(order_id) * -1 AS amount
    FROM temp_orders
    WHERE amount < 0;
    
    INSERT INTO deficit (orderid, customerid, channel, amount)
    SELECT
      order_id,
      customer_id,
      channel,
      FINAL_PROFIT(order_id) AS amount
    FROM temp_orders
    WHERE amount >= 0
    

    Likely that function can be changed as well to just be part of your SQL so you aren't calling a sql function over and over and over again as well.