codememo

SELECT COUNT(*) vs 명시적 커서로 두 번 가져오기

tipmemo 2023. 9. 5. 20:32
반응형

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

반응형