Sam*_*rum 11 oracle plsql database-cursor
我读过一本名为"Oracle PL SQL Programming"(第2版)的书,由Steven Feuerstein和Bill Pribyl撰写.在页99,有一点建议
除非你真的需要知道"命中"的总数,否则不要从表中"选择COUNT(*)".如果您只需要知道是否有多个匹配,只需使用显式游标获取两次.
你能举个例子向我解释这一点吗?谢谢.
正如Steven Feuerstein和Bill Pribyl建议我们不要使用SELECT COUNT()来检查表中的记录是否存在,任何人都可以帮我编辑下面的代码,以避免使用显式游标而不是使用SELECT COUNT(*)吗?此代码是在Oracle存储过程中编写的.
我有一个表emp(emp_id,emp_name,...),所以检查提供的员工ID是否正确:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
v_rows INTEGER;
BEGIN
...
SELECT COUNT(*) INTO v_rows
FROM emp
WHERE emp_id = emp_id_in;
IF v_rows > 0 THEN
/* do sth */
END;
/* more statements */
...
END do_sth;
Run Code Online (Sandbox Code Playgroud)
Ton*_*ews 22
开发人员可以从PL/SQL程序中的表执行选择COUNT(*)的原因有很多:
在这种情况下,没有选择:选择COUNT(*)并等待结果.这在许多桌子上会非常快,但在大桌子上可能需要一段时间.
这不保证计算表中的所有行.许多技术都是可能的:
DECLARE
CURSOR c IS SELECT '1' dummy FROM mytable WHERE ...;
v VARCHAR2(1);
BEGIN
OPEN c;
FETCH c INTO v;
IF c%FOUND THEN
-- A row exists
...
ELSE
-- No row exists
...
END IF;
END;
Run Code Online (Sandbox Code Playgroud)
DECLARE
v VARCHAR2(1);
BEGIN
SELECT '1' INTO v FROM mytable
WHERE ...
AND ROWNUM=1; -- Stop fetching if 1 found
-- At least one row exists
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- No row exists
END;
Run Code Online (Sandbox Code Playgroud)
DECLARE
cnt INTEGER;
BEGIN
SELECT COUNT(*) INTO cnt FROM mytable
WHERE ...
AND ROWNUM=1; -- Stop counting if 1 found
IF cnt = 0 THEN
-- No row found
ELSE
-- Row found
END IF;
END;
Run Code Online (Sandbox Code Playgroud)
(2)工作技术的变化:
DECLARE
CURSOR c IS SELECT '1' dummy FROM mytable WHERE ...;
v VARCHAR2(1);
BEGIN
OPEN c;
FETCH c INTO v;
FETCH c INTO v;
IF c%FOUND THEN
-- 2 or more rows exists
...
ELSE
-- 1 or 0 rows exist
...
END IF;
END;
Run Code Online (Sandbox Code Playgroud)
DECLARE
v VARCHAR2(1);
BEGIN
SELECT '1' INTO v FROM mytable
WHERE ... ;
-- Exactly 1 row exists
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- No row exists
WHEN TOO_MANY_ROWS THEN
-- More than 1 row exists
END;
Run Code Online (Sandbox Code Playgroud)
DECLARE
cnt INTEGER;
BEGIN
SELECT COUNT(*) INTO cnt FROM mytable
WHERE ...
AND ROWNUM <= 2; -- Stop counting if 2 found
IF cnt = 0 THEN
-- No row found
IF cnt = 1 THEN
-- 1 row found
ELSE
-- More than 1 row found
END IF;
END;
Run Code Online (Sandbox Code Playgroud)
你使用哪种方法在很大程度上取决于偏好(以及一些宗教狂热!)Steven Feuerstein总是倾向于使用显式游标而不是隐式游标(SELECT INTO和游标FOR循环); Tom Kyte喜欢隐含的游标(我同意他的观点).
重要的是,选择COUNT(*)而不限制ROWCOUNT是昂贵的,因此只应在需要计数时才能完成.
关于如何用显式游标重写这个问题的补充问题:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
v_rows INTEGER;
BEGIN
...
SELECT COUNT(*) INTO v_rows
FROM emp
WHERE emp_id = emp_id_in;
IF v_rows > 0 THEN
/* do sth */
END;
/* more statements */
...
END do_sth;
Run Code Online (Sandbox Code Playgroud)
那将是:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
CURSOR c IS SELECT 1
FROM emp
WHERE emp_id = emp_id_in;
v_dummy INTEGER;
BEGIN
...
OPEN c;
FETCH c INTO v_dummy;
IF c%FOUND > 0 THEN
/* do sth */
END;
CLOSE c;
/* more statements */
...
END do_sth;
Run Code Online (Sandbox Code Playgroud)
但实际上,在你的例子中,它没有更好或更糟,因为你选择的是主键,Oracle非常聪明,知道它只需要获取一次.
如果你有两个感兴趣,试试吧
SELECT 'THERE ARE AT LEAST TWO ROWS IN THE TABLE'
FROM DUAL
WHERE 2 =
(
SELECT COUNT(*)
FROM TABLE
WHERE ROWNUM < 3
)
Run Code Online (Sandbox Code Playgroud)
它比使用手动光标方法所需的代码更少,并且可能更快.
rownum技巧意味着一旦有两个行就停止获取行.
如果您没有对计数(*)进行某种限制,则可能需要很长时间才能完成,具体取决于您拥有的行数.在这种情况下,使用游标循环来手动从表中读取2行会更快.
他的意思是打开一个游标,不仅获取第一条记录,还获取第二条记录,然后你就会知道有多个记录。
因为我似乎从来不需要知道SELECT COUNT(*)
is >= 2
,所以我不知道为什么这在任何 SQL 变体中都是一个有用的习惯用法。要么没有记录,要么至少有一个,当然,但不是两个或更多。无论如何,总有EXISTS
。
事实上,Oracle 的优化器似乎相当糟糕...... - 我会质疑该技术的相关性。
针对 TheSoftwareJedi 的评论:
WITH CustomersWith2OrMoreOrders AS (
SELECT CustomerID
FROM Orders
GROUP BY CustomerID
HAVING COUNT(*) >= 2
)
SELECT Customer.*
FROM Customer
INNER JOIN CustomersWith2OrMoreOrders
ON Customer.CustomerID = CustomersWith2OrMoreOrders.CustomerID
Run Code Online (Sandbox Code Playgroud)
通过适当的索引,即使在 SQL Server 中进行像这样的全域查询,我也从未遇到过性能问题。然而,我经常在这里和其他网站上遇到有关 Oracle 优化器问题的评论。
我自己使用 Oracle 的经历并不好。
OP 的评论似乎是说COUNT(*)
优化器不能很好地处理表中的完整内容。IE:
IF EXISTS (SELECT COUNT(*) FROM table_name HAVING COUNT(*) >= 2)
BEGIN
END
Run Code Online (Sandbox Code Playgroud)
(当主键存在时,可以简化为简单的索引扫描 - 在极端优化的情况下,可以简单地查询 sysindexes.rowcnt 中的索引元数据 - 查找条目数 - 全部无需游标)通常要避免,有利于:
DECLARE CURSOR c IS SELECT something FROM table_name;
BEGIN
OPEN c
FETCH c INTO etc. x 2 and count rows and handle exceptions
END;
IF rc >= 2 THEN BEGIN
END
Run Code Online (Sandbox Code Playgroud)
对我来说,这会导致代码的可读性、可移植性和可维护性较差。