问题
I have a Oracle stored procedure returning a reference cursor. I want to open the cursor before i return it to check a count and throw an exception if need be, but im having trouble with syntax and how im supposed to do this.
V_ASN_COUNT NUMBER;
OPEN O_CURSOR FOR
SELECT column1, -- a bunch of columns
column2,
COUNT(DISTINCT SI.ASN_NO) OVER (PARTITION BY SI.ASN_NO) AS ASN_COUNT
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
WHERE -- a bunch of criteria
OPEN O_CURSOR;
LOOP
FETCH ASN_COUNT INTO V_ASN_COUNT;
END LOOP;
CLOSE O_CURSOR;
IF(V_ASN_COUNT > 1) THEN
RAISE MULTIPLE_ASNS;
END IF;
回答1:
I think you can do this one:
curid NUMBER;
desctab DBMS_SQL.DESC_TAB;
colcnt NUMBER; -- total number of columns
res NUMBER;
V_ASN_COUNT NUMBER;
BEGIN
OPEN O_CURSOR FOR
SELECT
column1, -- a bunch of columns
column2,
...
COUNT(DISTINCT SI.ASN_NO) OVER (PARTITION BY SI.ASN_NO) AS ASN_COUNT
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
WHERE -- a bunch of criteria
curid := DBMS_SQL.TO_CURSOR_NUMBER (O_CURSOR);
DBMS_SQL.DESCRIBE_COLUMNS(curid, colcnt, desctab);
-- "ASN_COUNT" is the last column, i. e. "colcnt" refers to column number of "ASN_COUNT"
-- or set colcnt directly, e.g. colcnt := 12;
FOR i IN 1..colcnt LOOP
IF desctab(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, V_ASN_COUNT);
ELSIF desctab(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, datevar);
.......
ELSE
DBMS_SQL.DEFINE_COLUMN(curid, i, namevar, 25);
END IF;
END LOOP;
-- I do not know if this loop is needed, perhaps you can simply do
-- DBMS_SQL.DEFINE_COLUMN(curid, colcnt, V_ASN_COUNT);
-- for a single column
res := DBMS_SQL.FETCH_ROWS(curid); -- Fetch only the first row, no loop required
DBMS_SQL.COLUMN_VALUE(curid, colcnt, V_ASN_COUNT); -- Loop over all column not required, you just like to get the last column
IF V_ASN_COUNT > 1 THEN
RAISE MULTIPLE_ASNS;
END IF;
DBMS_SQL.CLOSE_CURSOR(curid);
For further details, check Oracle documentation: DBMS_SQL.TO_CURSOR_NUMBER Function.
However, the problem with open/rewinded cursor remains!
回答2:
Following on from the previous question, if you wanted to open the same cursor multiple times to count over it, you could do something like this:
CREATE OR REPLACE PROCEDURE YOUR_PROC(O_CURSOR OUT SYS_REFCURSOR) is
ASN_NO NUMBER; -- have to define all columns the cursor returns
V_CHECK_ASN_NO NUMBER;
-- local function to generate the cursor, to avoid repeating the text
-- or using dynamic SQL
FUNCTION GET_CURSOR RETURN SYS_REFCURSOR IS
V_CURSOR SYS_REFCURSOR;
BEGIN
OPEN V_CURSOR FOR
SELECT *
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
-- where bunch of stuff
RETURN V_CURSOR;
END;
BEGIN
-- open the cursor for your check; might be better to have a local
-- variable for this rather than touching the OUT parameter this early
O_CURSOR := GET_CURSOR;
LOOP
FETCH O_CURSOR INTO ASN_NO; -- and all other columns!
EXIT WHEN O_CURSOR%NOTFOUND;
IF V_CHECK_ASN_NO IS NOT NULL AND V_CHECK_ASN_NO != ASN_NO THEN
-- means we have two distinct values
CLOSE O_CURSOR;
RAISE MULTIPLE_ASNS;
END IF;
V_CHECK_ASN_NO := ASN_NO;
END LOOP;
-- close the check version of the cursor
CLOSE O_CURSOR;
-- re-open the cursor for the caller
O_CURSOR := GET_CURSOR;
END YOUR_PROC;
You could open the cursor twice with the same SQL string using dynamic SQL, but this version uses a local function to make the cursor SQL static (and thus parsed at compile-time).
The cursor is executed twice, and at least some rows are fetched from the first execution (all rows if there are no duplicates; if there are duplicates then not all may be fetched). The caller gets a fresh result set containing all the rows.
回答3:
How about making sure the first row is expendable for the validation?
This code will only open cursor once - no concurrency issue. The two first rows of the cursor both represent the first row of the intended result set - fetch first copy for validation and return the rest if validation succeeds.
You still have to fetch all the columns though.
V_ASN_COUNT NUMBER;
OPEN O_CURSOR FOR
WITH qry AS ( SELECT column1, -- a bunch of columns
column2,
COUNT(DISTINCT SI.ASN_NO) OVER (PARTITION BY SI.ASN_NO) AS ASN_COUNT
FROM AN_ORDER_INFO OI, AN_SHIPMENT_INFO SI
WHERE -- a bunch of criteria
)
SELECT *
FROM qry
WHERE rownum = 1
UNION ALL
SELECT *
FROM qry;
-- Consume the expendable first row.
FETCH O_CURSOR INTO V_ASN_COUNT; -- and all the other columns!
IF(V_ASN_COUNT > 1) THEN
CLOSE O_CURSOR;
RAISE MULTIPLE_ASNS;
END IF;
来源:https://stackoverflow.com/questions/26388104/oracle-how-to-open-cursor-and-select-one-column-of-many-into-a-variable