SELECT COUNT(*) vs 명시적 커서로 두 번 가져오기
저는 스티븐 포이어스타인과 빌 프리빌의 "Oracle PL SQL Programming" (제2판)이라는 책을 읽은 적이 있습니다.99페이지에는 다음과 같은 주장이 있습니다.
전체 "히트" 수를 알아야 하는 경우가 아니라면 표에서 "카운트(*)를 선택"하지 마십시오.일치하는 항목이 두 개 이상 있는지만 알면 되는 경우 명시적 커서를 사용하여 두 번 가져오기만 하면 됩니다.
예를 들어 이 점을 좀 더 설명해 줄 수 있는 사람이 있습니까?감사해요.
업데이트:
Steven Feuerstein & Bill Pribyl이 테이블의 레코드가 존재하는지 여부를 확인하기 위해 SELECT COUNT()를 사용하지 말 것을 권고한 것처럼, 누군가 명시적 커서를 사용하여 SELECT COUNT(*)를 사용하지 않도록 아래 코드를 편집하는 것을 도와줄 수 있습니까?이 코드는 Oracle 저장 프로시저에 기록되어 있습니다.
제공된 직원 ID를 확인하기 위해 테이블 emp(emp_id, emp_name, ...)가 있습니다.
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;
개발자가 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;
메서드로 선택
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;
ROWNUM 방법으로 카운트(*) 선택
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;
두 개 이상의 행이 있는지 여부를 확인해야 합니다.
(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;
메서드로 선택
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;
ROWNUM 방법으로 카운트(*) 선택
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;
어떤 방법을 사용하느냐는 대체로 선호도의 문제입니다. (그리고 일부 종교적 열정!)Steven Feuerstein은 항상 암시적인 커서보다 명시적인 커서를 선호했습니다(루프의 경우 SELECT IN 및 커서).Tom Kyte는 암시적 커서를 선호합니다(그리고 저는 그의 말에 동의합니다).
중요한 점은 ROW COUNT를 제한하지 않고 COUNT(*)를 선택하는 것은 비용이 많이 들기 때문에 카운트가 꼭 필요한 경우에만 수행해야 한다는 것입니다.
명시적 커서로 다시 쓰는 방법에 대한 추가 질문에 대해:
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;
이는 다음과 같습니다.
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;
그러나 실제로 이 예에서는 기본 키를 선택하고 있으며 Oracle은 한 번만 가져오면 된다는 것을 알 수 있을 정도로 영리하기 때문에 더 좋을 것도 더 나쁠 것도 없습니다.
두 개가 당신이 관심 있는 전부라면, 시도해 보세요.
SELECT 'THERE ARE AT LEAST TWO ROWS IN THE TABLE'
FROM DUAL
WHERE 2 =
(
SELECT COUNT(*)
FROM TABLE
WHERE ROWNUM < 3
)
수동 커서 방식보다 코드가 덜 걸리고 더 빠를 수 있습니다.
로넘 트릭은 행이 두 개 있으면 해당 행을 가져오는 것을 중지하는 것을 의미합니다.
카운트(*)를 제한하지 않으면 행 수에 따라 완료하는 데 시간이 오래 걸릴 수 있습니다.이 경우 커서 루프를 사용하여 테이블에서 2개의 행을 수동으로 읽는 것이 더 빠릅니다.
이것은 프로그래머들이 다음과 유사한 코드를 작성하는 것에서 비롯됩니다 (이것은 psuedo 코드!).
고객이 두 개 이상의 주문을 가지고 있는지 확인하려고 합니다.
if ((select count(*) from orders where customerid = :customerid) > 1)
{
....
}
그것은 대단히 비효율적인 일을 하는 방법입니다.마크 브래디가 말했듯이, 만약 당신이 항아리에 페니가 들어 있는지 알고 싶다면, 당신은 그 항아리에 있는 모든 페니를 셀 것입니까, 아니면 단지 1개(또는 당신의 예에서 2개)가 있는지 확인할 것입니까?
이것은 다음과 같이 쓰는 것이 좋습니다.
if ((select 1 from (select 1 from orders where customerid = :customerid) where rownum = 2) == 1)
{
....
}
이렇게 하면 Oracle이 2개의 행을 가져온 다음 완료하기 때문에 "모든 코인을 세는" 딜레마를 방지할 수 있습니다.이전 예제에서는 Oracle이 모든 행을 검색(인덱스 또는 테이블)한 다음 완료합니다.
그는 커서를 열고 첫 번째 레코드뿐만 아니라 두 번째 레코드도 가져오면 두 개 이상의 레코드가 있다는 것을 알게 됩니다.
저는 을 알 가 없을 것 같기 때문입니다.SELECT COUNT(*)이라>= 2왜 이것이 SQL 변형에서 유용한 관용구인지 모르겠습니다.기록이 없거나 적어도 하나, 물론 두 개 이상은 아닙니다.그고어쨌, 상항리가 있어요.EXISTS.
오라클의 옵티마이저가 상당히 빈약한 것 같다는 사실도... - 이 기술의 타당성에 의문을 제기합니다.
SoftwareJedi의 의견을 처리하기 위해:
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
적절하게 색인화하면 SQL Server에서 이와 같은 전체 쿼리에 대해서도 성능 문제가 발생한 적이 없습니다.그러나 Oracle Optimizer 문제에 대한 의견은 다른 사이트에서도 꾸준히 나오고 있습니다.
Oracle에 대한 제 자신의 경험은 좋지 않았습니다.
OP의 논평은 꽉 찬 것처럼 보입니다.COUNT(*)테이블에서 옵티마이저로 잘 처리되지 않습니다. 즉, 다음과 같습니다.
IF EXISTS (SELECT COUNT(*) FROM table_name HAVING COUNT(*) >= 2)
BEGIN
END
(기본 키가 존재하는 경우 단순 인덱스 스캔으로 축소할 수 있습니다. 극도로 최적화된 경우 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
그것은 저에게 읽기 쉽고, 휴대하기 쉬우며, 유지보수하기 쉬운 코드가 될 것입니다.
Steven Feuerstein의 제안을 너무 심각하게 받아들이기 전에 약간의 벤치마크를 수행하십시오.당신의 경우 카운트(*)가 명시적 커서보다 눈에 띄게 느립니까?아닌가요? 그렇다면 간단하고 읽을 수 있는 코드가 가능한 구조를 사용하는 것이 좋습니다.대부분의 경우 "v_cnt에 카운트(*) 선택...(v_cnt>0인 경우...)"가 됩니다.
PL/SQL은 매우 읽기 쉬운 프로그램을 허용합니다.나노 최적화를 위해 낭비하지 마십시오.
DB에 따라 대략적인 카운트를 저장하고 일정한 시간에 쿼리할 수 있는 시스템 테이블이 있을 수 있습니다.테이블의 행 수가 20개인지, 20,000개인지, 20,000,000개인지 알고 싶은 경우 유용합니다.
SQL 서버:
if 2 = (
select count(*) from (
select top 2 * from (
select T = 1 union
select T = 2 union
select T = 3 ) t) t)
print 'At least two'
또한, 커서를 절대 사용하지 마세요.만약 여러분이 정말로 그것들이 필요하다고 생각한다면, 여러분의 마음이 바뀔 때까지 삽으로 스스로를 때리세요.고대의 유물을 고대의 유물로 남겨둡시다.
테이블의 행 수를 가져오려면 count(*)를 사용하지 마십시오. count(0)는 0이 기본 키 열의 열 인덱스입니다.
언급URL : https://stackoverflow.com/questions/297671/select-count-vs-fetching-twice-with-an-explicit-cursor
'codememo' 카테고리의 다른 글
| mysqdump는 이진 데이터를 안정적으로 처리합니까? (0) | 2023.09.10 |
|---|---|
| 우체부 내선이 응답을 얻지만, 나의 jquery 요청은 그렇지 않습니다. (0) | 2023.09.10 |
| 리소스 u'tokenizers/punkt/english.pickle'을(를) 찾을 수 없습니다. (0) | 2023.09.05 |
| 이렇게 생긴 XMLHttpRequest에 데이터 본문을 보내는 방법은 무엇입니까? (0) | 2023.09.05 |
| VBA 정밀도 문제에서 두 배 비교 (0) | 2023.09.05 |