Oracle,如何打开游标并将其中的一列选择为变量
Posted
技术标签:
【中文标题】Oracle,如何打开游标并将其中的一列选择为变量【英文标题】:Oracle, how to open cursor and select one column of many into a variable 【发布时间】:2014-10-15 17:05:18 【问题描述】:我有一个返回引用游标的 Oracle 存储过程。我想在返回光标之前打开光标以检查计数并在需要时抛出异常,但我在语法和我应该如何做这件事上遇到了问题。
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;
【问题讨论】:
由于FETCH
消耗行,如果您在返回游标之前获取一些行,则调用者将不再通过返回的游标获取这些行。这可以接受吗?
我没有意识到这一点。调用者需要这个 select 语句获取的所有内容,除了 asn_count。在这种情况下,我应该如何获取 asn_count 以进行错误处理?
您的具体要求是什么? "如果 any ASN_COUNT > 1
那么应该处理 not any 行并且应该立即引发异常" ?或者,如果/当您遇到一些ASN_COUNT > 1
时,开始处理然后中止和ROLLBACK
是否可以接受?
【参考方案1】:
我认为你可以做到这一点:
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);
更多详细信息,请查看 Oracle 文档:DBMS_SQL.TO_CURSOR_NUMBER 函数。
但是,打开/后退光标的问题仍然存在!
【讨论】:
也许你可以在最后做类似O_CURSOR := DBMS_SQL.TO_REFCURSOR(curid);
的事情。或者可能是在获取光标之前的curid
值的副本。【参考方案2】:
从上一个问题开始,如果你想多次打开同一个光标来计数,你可以这样做:
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;
您可以使用动态 SQL 使用相同的 SQL 字符串打开游标两次,但此版本使用本地函数使游标 SQL 静态化(因此在编译时解析)。
游标被执行两次,并且至少从第一次执行中获取了一些行(如果没有重复,则获取所有行;如果有重复,则可能不会获取所有行)。调用者得到一个包含所有行的新结果集。
【讨论】:
无论事务隔离级别如何,是否保证两个游标返回相同的行? @SylvainLeroux - 不,这是一个很好的观点,尤其是在数据不稳定的情况下。我不确定有没有办法在不改变隔离级别或做一些锁定的情况下防止这种情况发生?我不认为有任何方法可以回退光标,即使使用动态 SQL,是否存在? 不,您不能倒回光标。恐怕OP在这种情况下至少需要Repeatable read
——或者,如果行数不多,将他的代码包装在一个表函数和BULK COLLECT
结果集中为了检查数据...【参考方案3】:
如何确保第一行可用于验证?
此代码只会打开一次游标 - 没有并发问题。游标的前两行都代表预期结果集的第一行 - 获取第一个副本进行验证,如果验证成功则返回其余副本。
您仍然需要获取所有列。
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;
【讨论】:
以上是关于Oracle,如何打开游标并将其中的一列选择为变量的主要内容,如果未能解决你的问题,请参考以下文章