11

Steven Feuerstein&BillPribylによる「OraclePLSQLProgramming」(第2版)というタイトルの本を読みました。99ページに、

「ヒット」の総数を本当に知る必要がない限り、テーブルから「SELECTCOUNT(*)」を実行しないでください。一致するものが複数あるかどうかだけを知る必要がある場合は、明示カーソルを使用して2回フェッチするだけです。

例を挙げて、この点をもっと説明してもらえますか?ありがとうございました。

アップデート:

StevenFeuersteinとBillPribylは、テーブル内のレコードが存在するかどうかを確認するために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;
4

8 に答える 8

22

開発者が PL/SQL プログラムでテーブルから COUNT(*) を選択する理由はいくつかあります。

1) 彼らは、テーブルに何行あるかを本当に知る必要があります。

この場合、選択の余地はありません。COUNT(*) を選択し、結果を待ちます。これは多くのテーブルではかなり高速ですが、大きなテーブルでは時間がかかる場合があります。

2) 行が存在するかどうかを知る必要があるだけです。

これは、テーブル内のすべての行を数えることを保証するものではありません。いくつかのテクニックが可能です:

a) 明示カーソル方式:

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;

b) SELECT INTO メソッド

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;

c) ROWNUM メソッドを使用した SELECT COUNT(*)

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;

3) 複数の行が存在するかどうかを知る必要があります。

(2)作業のテクニックのバリエーション:

a) 明示カーソル方式:

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;

b) SELECT INTO メソッド

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;

c) ROWNUM メソッドを使用した SELECT COUNT(*)

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 INTO およびカーソル FOR ループ) よりも明示的カーソルを好んでいました。Tom Kyte は暗黙のカーソルを支持しています (私も彼に同意します)。

重要な点は、ROWCOUNT を制限せずに 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は一度だけフェッチする必要があることを十分に認識しているため、良くも悪くもありません。

于 2008-11-18T10:28:41.627 に答える
5

2つだけに興味がある場合は、試してみてください

SELECT 'THERE ARE AT LEAST TWO ROWS IN THE TABLE'
FROM DUAL
WHERE 2 =
(
    SELECT COUNT(*)
    FROM TABLE
    WHERE ROWNUM < 3
)

手動カーソル方式よりもコードが少なくて済み、高速になる可能性があります。

rownumトリックは、行が2つあると、行のフェッチを停止することを意味します。

count(*)になんらかの制限を設けないと、行数によっては終了までに時間がかかる場合があります。その場合、カーソルループを使用して、テーブルから2行を手動で読み取る方が高速です。

于 2008-11-18T03:52:30.053 に答える
3

これは、プログラマーが次のようなコードを作成したことに由来します (これは疑似コードです!)。

顧客が複数の注文を持っているかどうかを確認したいとします。

if ((select count(*) from orders where customerid = :customerid) > 1)
{
    ....
}

それは物事を行うためのひどく非効率的な方法です。Mark Bradyが言うように、瓶にペニーが入っているかどうかを知りたい場合、瓶の中のすべてのペニーを数えますか、それとも 1 (またはあなたの例では 2) であることを確認しますか?

これは次のように書くとよいでしょう:

if ((select 1 from (select 1 from orders where customerid = :customerid) where rownum = 2) == 1)
{
    ....
}

これにより、Oracle は 2 つの行をフェッチして終了するため、「すべてのコインを数える」というジレンマが回避されます。前の例では、Oracle は (インデックスまたはテーブルの) すべての行をスキャンしてから終了します。

于 2008-11-18T03:13:12.177 に答える
1

Steven Feuersteinの提案を真剣に受け止める前に、ちょっとしたベンチマークを行ってください。あなたの場合、count(*)は明示カーソルよりも著しく遅いですか?いいえ?次に、単純で読みやすいコードを可能にする構成をより適切に使用します。これは、ほとんどの場合、「count(*)をv_cntに選択します...v_cnt>0の場合は...」になります。

PL / SQLにより、非常に読みやすいプログラムが可能になります。ナノ最適化のためだけにそれを無駄にしないでください。

于 2009-02-19T13:05:16.803 に答える
1

彼は、カーソルを開いて最初のレコードだけでなく2番目のレコードもフェッチすることを意味します。そうすれば、複数のレコードがあることがわかります。

それを知る必要はないように思われるのでSELECT COUNT(*)>= 2これがSQLバリアントで有用なイディオムである理由がわかりません。レコードがないか、少なくとも1つは確かですが、2つ以上はありません。そしてとにかく、常にあり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

適切にインデックス付けされているため、SQLServerでこのようなユニバース全体のクエリを実行してもパフォーマンスの問題が発生したことはありません。ただし、ここや他のサイトでOracleオプティマイザの問題に関するコメントに一貫して遭遇しています。

私自身の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

それは、私にとって、読みにくく、移植性が低く、保守しにくいコードになるでしょう。

于 2008-11-18T02:36:28.507 に答える
0

DBによっては、概算カウントを格納し、一定時間で照会できるsysテーブルが存在する場合があります。テーブルの行数が20行か、20,000行か20,000,000行かを知りたい場合に便利です。

于 2008-11-18T03:22:13.253 に答える
-1

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'

また、カーソルを使用しないでください。本当に必要だと思う場合は、気が変わるまでシャベルで自分を殴ってください。古代の遺物は古代の遺物のままにしましょう。

于 2008-11-18T04:00:23.627 に答える
-2

テーブル内の行数を取得したい場合は、count(*) を使用しないでください。0 が主キー列の列インデックスである count(0) をお勧めします。

于 2008-12-05T09:27:04.290 に答える