简体   繁体   中英

With statement select and function into cursor

it is possible to use the with structure with a function inside a cursor, I don't know if I am declaring it inappropriately, I am getting the following error using with function inside procedure pl sql statement is not supported

CURSOR c_detail IS
WITH 
FUNCTION CALC_NUMBER(FOB_ITEM    NUMBER DEFAULT 0, 
                     FOB_TOTAL   NUMBER DEFAULT 0, 
                     WEIGHT NUMBER DEFAULT 0) RETURN NUMBER
     IS
     PESO_BRUTO_ITEM NUMBER :=0;
     BEGIN
         IF( (FOB_ITEM > 0) AND (FOB_TOTAL > 0 AND WEIGHT> 0 )) THEN
             PESO_BRUTO_ITEM := (FOB_ITEM * WEIGHT) / FOB_TOTAL;
         END IF;
         RETURN PESO_BRUTO_ITEM;
END CALC_NUMBER;  
test_data AS
(
SELECT 36.25 AS FOB_I, 12536.36 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
SELECT 15.36 AS FOB_I, 3678.65 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
SELECT 878.77 AS FOB_I, 89653.13 AS FOB_TOTAL, 362 AS W FROM dual
)
SELECT TD.FOB_I,
       TD.FOB_TOTAL,
       CALC_NUMBER(TD.FOB_I, TD.FOB_TOTAL, TD.W) WEIGHT
  FROM test_data TD

[TL;DR] You can declare a function in a sub-query factoring clause but (as Justin Cave points out ) it only works when you are executing the query as dynamic SQL and support for using functions in static SQL within a cursor may be available in future database versions.


This sub-query factoring clause with a function works outside of a cursor:

WITH
  FUNCTION with_function(
    p_id IN NUMBER
  ) RETURN NUMBER
  IS
  BEGIN
    RETURN 42 + p_id;
  END;
test_data ( id ) AS (
  SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3 
)
SELECT id,
       with_function( id )
FROM   test_data;

Trying to put it into a cursor (in Oracle 18c):

DECLARE
  p_id NUMBER;
  p_fn NUMBER;

  CURSOR c_detail IS
  WITH
    FUNCTION with_function(
      p_id IN NUMBER
    ) RETURN NUMBER
    IS
    BEGIN
      RETURN 42 + p_id;
    END;
  test_data ( id ) AS (
    SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3 
  )
  SELECT id,
         with_function( id )
  FROM   test_data;
BEGIN
  OPEN c_detail;
  LOOP
    FETCH c_detail INTO p_id, p_fn;
    EXIT WHEN c_detail%NOTFOUND;

    DBMS_OUTPUT.PUT_LINE( p_id || ', ' || p_fn );
  END LOOP;

  CLOSE c_detail;
END;
/

Outputs the error:

\nORA-06550: line 7, column 14: \nPL/SQL: ORA-00905: missing keyword \nORA-06550: line 6, column 3: \nPL/SQL: SQL Statement ignored \nORA-06550: line 13, column 5: \nPLS-00103: Encountered the symbol "END" when expecting one of the following: \n\n   begin function pragma procedure subtype type <an identifier> \n   <a double-quoted delimited-identifier> current cursor delete \n   exists prior\n

Removing the function then the cursor works:

DECLARE
  p_id NUMBER;

  CURSOR c_detail IS
  WITH
  test_data ( id ) AS (
    SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3 
  )
  SELECT id
  FROM   test_data;
BEGIN
  OPEN c_detail;
  LOOP
    FETCH c_detail INTO p_id;
    EXIT WHEN c_detail%NOTFOUND;

    DBMS_OUTPUT.PUT_LINE( p_id );
  END LOOP;

  CLOSE c_detail;
END;
/

So it is not an issue with using a sub-query factoring clause in a cursor.

Executing the cursor as dynamic SQL query:

DECLARE
  p_id NUMBER;
  p_fn NUMBER;
  c_detail SYS_REFCURSOR;

  p_sql VARCHAR2(4000) := 'WITH
    FUNCTION with_function(
      p_id IN NUMBER
    ) RETURN NUMBER
    IS
    BEGIN
      RETURN 42 + p_id;
    END;
  test_data ( id ) AS (
    SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3 
  )
  SELECT id,
         with_function( id )
  FROM   test_data';
BEGIN
  OPEN c_detail FOR p_sql;
  LOOP
    FETCH c_detail INTO p_id, p_fn;
    EXIT WHEN c_detail%NOTFOUND;

    DBMS_OUTPUT.PUT_LINE( p_id || ', ' || p_fn );
  END LOOP;

  CLOSE c_detail;
END;
/

Works and outputs:

\n1, 43 \n2, 44 \n3, 45 \n

So it appears that, yes, you can declare a function in a sub-query factoring clause but it only works when you are executing the query as dynamic SQL.

db<>fiddle here

The error message clearly says you can't do what you are after, but then you will not have to if your cursor looks like following

CURSOR c_detail IS
WITH test_data AS(
    SELECT 36.25 AS FOB_I, 12536.36 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
    SELECT 15.36 AS FOB_I, 3678.65 AS FOB_TOTAL, 362 AS W FROM dual UNION ALL
    SELECT 878.77 AS FOB_I, 89653.13 AS FOB_TOTAL, 362 AS W FROM dual
)
SELECT TD.FOB_I,
       TD.FOB_TOTAL,
       CASE WHEN TD.FOB_I > 0 AND TD.FOB_TOTAL > 0 AND TD.W > 0 
       THEN (TD.FOB_I * TD.W) / TD.FOB_TOTAL
       ELSE 0
       END WEIGHT
FROM test_data TD
;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM