タイトルロゴ

記載日 2006/02/23
はじめに

 OracleにはDUAL表と言う作業用のテーブルが存在しています。 DUMMYと言うただ一つの列を持ち、その列の型はVARCHAR2(1)です。実際に検索してみると、'X'と言う文字列 が返されます。

 このDUAL表は、SYSユーザの持ち物ですが、データベースに存在する全ユーザが参照で きるようにシノニムが作成されています。

 一行しか結果を返さないことを利用し、DUAL表は様々な局面で利用されています。 簡単な数式演算を実行する場合、シーケンスオブジェクトの値を一つ進める場合 、また、 SYSDATE関数から現在の年月日時分秒を取得する場合にも用いられます。

  SQL > DESC DUAL    Name Null? Type    ----------------------------------------- -------- ----------------------------    DUMMY VARCHAR2(1)   SQL > SELECT 2*110 FROM DUAL;    2*110   ----------    220   1 row selected.   SQL >   
 DUAL表は、必ず一行しか結果を返しませんが、プログラミングしている過程で、複数行の結果が欲しくなる ことはないでしょうか?

 今回は、いずれ、DUAL表に取って代る(カナ?)複数行を返す作業用のテーブル を考えてみたいと思います。また、その作業用テーブルの利用方法の例をあげます。



セクション















検証環境

 
ページのTOPへ

 Oracleには、ネスト表を戻り値とする表関数を作成することができます。 ネスト表とは、PL/SQLで使用する配列の一種です。

 まずは、NUMBER型のオブジェクトタイプを宣言し、その型を用いたネスト表を宣言します。 CREATE TYPE文は、以下のようになります。
CREATE TYPE  ← こちらをクリック

/* -------------------------------- */
/* NUMBER型のオブジェクトタイプ定義 */
/* -------------------------------- */
CREATE OR REPLACE TYPE NUM_TYPE IS OBJECT ( N NUMBER );
/
/* -------------------------------- */
/* NUM_TYPE型のネスト表定義         */
/* -------------------------------- */
CREATE OR REPLACE TYPE NUM_TABLE IS TABLE OF NUM_TYPE;
/

 次に、ネスト表を戻り値とする表関数を作成します。  CREATE FUNCTION文は、以下のようになります。
CREATE FUNCTION  ← こちらをクリック

/* ------------------------------------------ */
/* 行戻し関数                                 */
/* 引 数:IP_NUM NUMBER                      */
/* 戻り値:NUM_TABLE                          */
/* 説 明:引数に指定した数値分の行を戻す     */
/* ------------------------------------------ */
CREATE OR REPLACE FUNCTION GET_ROWS(
      IP_NUM  NUMBER 
) RETURN NUM_TABLE
IS
    WK_NUM NUMBER := TRUNC(IP_NUM);  /* 引数の数値を整数に丸めます。 */
    RET_NUM_TABLE  NUM_TABLE;  /* 戻り値を用意 */
BEGIN
    /* 引数が0であれば、一行も戻さない。 */
    IF WK_NUM = 0 THEN
        RETURN RET_NUM_TABLE;
    END IF;

    /* 戻り値の初期化 */
    RET_NUM_TABLE := NUM_TABLE(NUM_TYPE(NULL));
    RET_NUM_TABLE.EXTEND(ABS(WK_NUM)-1);

    IF WK_NUM > 0 THEN
        /* 引数が正の数値 */
        FOR IDX IN 1..WK_NUM LOOP
            RET_NUM_TABLE(IDX) := NUM_TYPE(IDX);
        END LOOP;
    ELSE
        /* 引数が負の数値 */
        FOR IDX IN 1..ABS(WK_NUM) LOOP
            RET_NUM_TABLE(IDX) := NUM_TYPE(-IDX);
        END LOOP;
    END IF;

    RETURN RET_NUM_TABLE;
END GET_ROWS;
/

 上で紹介したタイプとファンクションをコンパイルすれば、表関数のGET_ROWS関数は作成完了です。

 実際にGET_ROWS関数を使用して、動作を確認してみました。GET_ROWS関数が戻すネスト表は TABLE関数を使用することで SELECT文のFROM句で使用できます。下の例では、GET_ROWS関数の引数に「5」を渡しているので、5行の検 索結果が返されます。

SQL> SELECT N FROM TABLE(GET_ROWS(5));

         N
----------
         1
         2
         3
         4
         5

5 rows selected.

SQL>

 GET_ROWS関数の引数に「0」を渡した場合は、一行も検索結果が返されません。

SQL> SELECT N FROM TABLE(GET_ROWS(0));

no rows selected

SQL>

 GET_ROWS関数の引数に「-3」を渡した場合は、3行の検索結果が返されます。返される値を負の値にしてますが CREATE FUNCTION文を改造すれば、好みの仕様にカスタマイズできるでしょう。

SQL> SELECT N FROM TABLE(GET_ROWS(-3));

         N
----------
        -1
        -2
        -3

3 rows selected.

SQL>

 GET_ROWS関数の引数に「2.5」を渡した場合は、2行の検索結果が返されます。GET_ROWS関数に 小数点以下の数値を含む引数を渡した場合は、関数内で小数点以下を切捨てて処理します。

SQL> SELECT N FROM TABLE(GET_ROWS(2.5));

         N
----------
         1
         2

2 rows selected.

SQL>


 
ページのTOPへ

 上で紹介したGET_ROWS関数は、大量のデータを返す場合に、それ相応のメモリを使用します。 パイプライン表関数を使用すれば、データをメモリに貯えることなく、その都度データを吐き出すので、 メモリ圧迫の心配がなくなります。

 GET_ROWS関数をパイプライン表関数に変更してみました。使用方法は先に紹介した表関数と変わりません。  CREATE FUNCTION文は、以下のようになります。
CREATE FUNCTION  ← こちらをクリック

/* ------------------------------------------ */
/* 行戻し関数                                 */
/* 引 数:IP_NUM NUMBER                      */
/* 戻り値:NUM_TABLE                          */
/* 説 明:引数に指定した数値分の行を戻す     */
/* ------------------------------------------ */
CREATE OR REPLACE FUNCTION GET_ROWS(
      IP_NUM  NUMBER 
) RETURN NUM_TABLE PIPELINED
IS
    WK_NUM NUMBER := TRUNC(IP_NUM);  /* 引数の数値を整数に丸めます。 */
BEGIN
    /* 引数が0であれば、一行も戻さない。 */
    IF WK_NUM = 0 THEN
        RETURN;
    END IF;

    IF WK_NUM > 0 THEN
        /* 引数が正の数値 */
        FOR IDX IN 1..WK_NUM LOOP
            PIPE ROW ( NUM_TYPE(IDX) );
        END LOOP;
    ELSE
        /* 引数が負の数値 */
        FOR IDX IN 1..ABS(WK_NUM) LOOP
            PIPE ROW ( NUM_TYPE(-IDX) );
        END LOOP;
    END IF;

    RETURN;
END GET_ROWS;
/


 
ページのTOPへ

 上で作成したGET_ROWS関数の利用例を紹介します。今まで、できそうでできなかったいくつかの演算が簡単に 実現できます。

 ■ 偶数・奇数の一覧を作成する

 まずは、GET_ROWS関数を利用して偶数・奇数の一覧を取得するSQLを紹介します。前者のSQLは、 小さい方から五つの偶数・奇数をそれぞれ取得するSQLです。後者のSQLは、 1〜16の範囲に存在する奇数を取得するSQLです。
(※偶数とは一の位が0,2,4,6,8の整数を指し、奇数とは一の位が1,3,5,7,9の整数を指します。)

SQL> SELECT 2*(N-1) AS "偶数", 2*N-1 AS "奇数" FROM TABLE(GET_ROWS(5));

      偶数       奇数
---------- ----------
         0          1
         2          3
         4          5
         6          7
         8          9

5 rows selected.

SQL> SELECT N AS "奇数(1〜16)" FROM TABLE(GET_ROWS(16)) WHERE MOD(N,2) = 1;

奇数(1〜15)
-----------
          1
          3
          5
          7
          9
         11
         13
         15

8 rows selected.

SQL>


 ■ 整数n の約数一覧を作成する

 整数n の約数を一覧にします。下の例は、10の約数を一覧取得する例です。

SQL> SELECT N AS "10の約数" FROM TABLE(GET_ROWS(10)) WHERE MOD(10,N) = 0;

  10の約数
----------
         1
         2
         5
        10

4 rows selected.

SQL>


 ■ 素数の一覧を作成する

 素数を一覧にします。前者のSQLは、 1〜30の範囲に存在する素数を取得するSQLです。後者のSQLは、 299が素数かどうか判別するSQLです。299は素数ではありません。先に説明した"約数"を求めるSQLで確認できます。
(※素数とは1とその数以外に約数を持たない1より大きな整数を指します。)

SQL> SELECT GR1.N AS "素数"
  2 FROM TABLE(GET_ROWS(30)) GR1, TABLE(GET_ROWS(30)) GR2
  3 WHERE GR1.N > 1 AND MOD(GR1.N, GR2.N) = 0
  4 GROUP BY GR1.N
  5 HAVING COUNT(*) = 2;

      素数
----------
         2
         3
         5
         7
        11
        13
        17
        19
        23
        29

10 rows selected.

SQL> SELECT DECODE(COUNT(*),2,'Yes','No') AS "素数?"
  2 FROM TABLE(GET_ROWS(299))
  3 WHERE MOD(299, N) = 0;

素数?
-----
No

1 row selected.

SQL>


 ■ 自然対数の底e を求めてみる

 自然対数の底とは、次の数式で定義される無理数です。数学が得意な人は ご存知のはずです。

  

 Oracleには、自然対数の底の累乗を求める関数EXP関数が用意されているので、 この関数の引数に1を渡せば、自然対数の底を求めることができます。しかし、 ここでは、GET_ROWS関数の使用例の紹介と言うことで、実際、上の数式で紹介した通り、n→∞に近い作業を 行い、(1+1/n)nの値の収束する様子を観察する実験をしてみたいと思います。

SQL> SET NUMWIDTH 30;
SQL> SELECT EXP(1) AS "自然対数の底" FROM DUAL;

                  自然対数の底
------------------------------
2.7182818284590452353602874714

1 row selected.

SQL> SELECT N, POWER((1+1/N),N) AS "自然対数の底" FROM TABLE(GET_ROWS(10000));

                             N                   自然対数の底
------------------------------ ------------------------------
                             1                              2
                             2                           2.25
                             3 2.3703703703703703703703703704
                             4                     2.44140625
                             5                        2.48832
                             6  2.521626371742112482853223594
                             7 2.5464996970407131139479055738
                             8     2.565784513950347900390625
                             9 2.5811747917131971819900315081
                            10                   2.5937424601
…
                          9998 2.7181458996419532266572757188
                          9999 2.7181459132349482202572127477
                         10000 2.7181459268252248640376646749
※nの値をどこまで大きくすれば値の収束を確認できるのでしょうか。

10000 rows selected.

SQL> SELECT POWER(10,N) AS N, POWER((1+1/POWER(10,N)),POWER(10,N)) AS "自然対数の底"
   2 FROM TABLE(GET_ROWS(25));

                             N                   自然対数の底
------------------------------ ------------------------------
                            10                   2.5937424601
                           100 2.7048138294215260932671947108
                          1000 2.7169239322358924573830881219
                         10000 2.7181459268252248640376646749
                        100000 2.7182682371744896680350648244
                       1000000 2.7182804693193768838197997085
                      10000000 2.7182816925449662711985502258
                     100000000  2.718281814867636217652977243
                    1000000000 2.7182818270999043223766440239
                   10000000000 2.7182818283231311439497940012
                  100000000000 2.7182818284454538262181168328
                 1000000000000 2.7182818284576860944460591767
                10000000000000 2.7182818284589093212688646501
               100000000000000 2.7182818284590316439511445637
              1000000000000000 2.7182818284590438762193732418
             10000000000000000 2.7182818284590450994461960484
            100000000000000000 2.7182818284590452217688783291
           1000000000000000000 2.7182818284590452340011465571
          10000000000000000000 2.7182818284590452352243733799
         100000000000000000000 2.7182818284590452353466960622
        1000000000000000000000 2.7182818284590452353602874714 ※この辺りで、収束すると予想されます。
       10000000000000000000000 2.7182818284590452353602874714
      100000000000000000000000 2.7182818284590452353602874714
     1000000000000000000000000 2.7182818284590452353602874714
    10000000000000000000000000 2.7182818284590452353602874714

25 rows selected.

SQL>


 ■ 数列の和を求めてみる(買Vグマ)

 高校数学で、数列の和を求める時に、萩L号を使用した 以下の数式を良くお見かけしたと思います。

  

 これは、(k2+k)の "k" を1〜10まで変化させて計算し、その全ての値を足し合わせることを表す数式 です。これも、(12+1) + (22+2) + … + (102+10)と言う風に一つずつ計算 するのではなく、GET_ROWS関数と集合関数SUM関数を用いて計算できます。

SQL> SELECT SUM( POWER(N,2) + N ) AS "(K2+K) , k=1〜10" FROM TABLE(GET_ROWS(10));

(K2+K) , k=1〜10
-------------------
                440

1 row selected.

SQL>


 
ページのTOPへ

 Oracleのシーケンスオブジェクトは、作成時にINCREMENT BY句にシーケンスの増分 を指定します。指定せずにシーケンスオブジェクトを作成した場合は、デフォルトとして増分は 1となります。

 シーケンスは、場合によって、カレントの番号から一気に10前進させたい場合もあると思います。 この様な場合は、通常、DUAL表を用いてシーケンス.NEXTVALを10回セレクトすればこと足りますが、GET_ROWS関数を使用して 一気に10前進させることができます。

SQL>SELECT TEST_SEQ.CURRVAL FROM DUAL;

   CURRVAL
----------
         1

1 row selected.

SQL>DECLARE
  2     WK_SEQ NUMBER;
  3 BEGIN
  4     FOR IDX IN 1..10 LOOP
  5         SELECT TEST_SEQ.NEXTVAL INTO WK_SEQ FROM DUAL;
  6     END LOOP;
  7 END;
  8 /

PL/SQL procedure successfully completed.

SQL>SELECT TEST_SEQ.CURRVAL FROM DUAL;

   CURRVAL
----------
        11

1 row selected.

SQL>SELECT TEST_SEQ.NEXTVAL FROM TABLE(GET_ROWS(10));

   NEXTVAL
----------
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

10 rows selected.

SQL>SELECT TEST_SEQ.CURRVAL FROM DUAL;

   CURRVAL
----------
        21

1 row selected.

SQL>



 
ページのTOPへ

 Oracleにはカレンダーを表示する機能がないので、自前でカレンダー表を作成するか、 TO_DATE関数SYSDATE関数を駆使して作成するのが通常です。

 以下は、努力の後が見られるカレンダーSQLです。いつ実行しても、今月のカレンダーが表示されます。

SQL> SELECT TRUNC(SYSDATE,'MM') + ROWNUM - 1 AS "今月のカレンダー"
   2 FROM ( SELECT 1 FROM DUAL UNION ALL
   3        SELECT 2 FROM DUAL UNION ALL
   4        SELECT 3 FROM DUAL UNION ALL
   5        SELECT 4 FROM DUAL UNION ALL
   6        SELECT 5 FROM DUAL UNION ALL
   7        SELECT 6 FROM DUAL UNION ALL
   8        SELECT 7 FROM DUAL UNION ALL
   9        SELECT 8 FROM DUAL UNION ALL
  10        SELECT 9 FROM DUAL UNION ALL
  11        SELECT 10 FROM DUAL UNION ALL
  12        SELECT 11 FROM DUAL UNION ALL
  13        SELECT 12 FROM DUAL UNION ALL
  14        SELECT 13 FROM DUAL UNION ALL
  15        SELECT 14 FROM DUAL UNION ALL
  16        SELECT 15 FROM DUAL UNION ALL
  17        SELECT 16 FROM DUAL UNION ALL
  18        SELECT 17 FROM DUAL UNION ALL
  19        SELECT 18 FROM DUAL UNION ALL
  20        SELECT 19 FROM DUAL UNION ALL
  21        SELECT 20 FROM DUAL UNION ALL
  22        SELECT 21 FROM DUAL UNION ALL
  23        SELECT 22 FROM DUAL UNION ALL
  24        SELECT 23 FROM DUAL UNION ALL
  25        SELECT 24 FROM DUAL UNION ALL
  26        SELECT 25 FROM DUAL UNION ALL
  27        SELECT 26 FROM DUAL UNION ALL
  28        SELECT 27 FROM DUAL UNION ALL
  29        SELECT 28 FROM DUAL UNION ALL
  30        SELECT 29 FROM DUAL UNION ALL
  31        SELECT 30 FROM DUAL UNION ALL
  32        SELECT 31 FROM DUAL )
  33  WHERE ROWNUM <= TO_NUMBER(TO_CHAR(LAST_DAY(SYSDATE),'DD'));

今月のカレンダー
-------------------
2006/02/01
2006/02/02
2006/02/03
2006/02/04
2006/02/05
2006/02/06
2006/02/07
2006/02/08
2006/02/09
2006/02/10
2006/02/11
2006/02/12
2006/02/13
2006/02/14
2006/02/15
2006/02/16
2006/02/17
2006/02/18
2006/02/19
2006/02/20
2006/02/21
2006/02/22
2006/02/23
2006/02/24
2006/02/25
2006/02/26
2006/02/27
2006/02/28

28 rows selected.

SQL>


 上のSQLが、GET_ROWS関数を使えばかなりすっきりします。

SQL> SELECT TRUNC(SYSDATE,'MM') + N - 1 AS "今月のカレンダー"
   2 FROM TABLE(GET_ROWS(TO_NUMBER(TO_CHAR(LAST_DAY(SYSDATE),'DD'))));

今月のカレンダー
-------------------
2006/02/01
2006/02/02
2006/02/03
2006/02/04
2006/02/05
2006/02/06
2006/02/07
2006/02/08
2006/02/09
2006/02/10
2006/02/11
2006/02/12
2006/02/13
2006/02/14
2006/02/15
2006/02/16
2006/02/17
2006/02/18
2006/02/19
2006/02/20
2006/02/21
2006/02/22
2006/02/23
2006/02/24
2006/02/25
2006/02/26
2006/02/27
2006/02/28

28 rows selected.

SQL>



 
ページのTOPへ

 次の様なシステムを想像します。

  [システム背景]

   ・交差点を行き交う車の交通量を調べ、集中管理するデータベースシステム。
   ・調査対象の交差点は、10000箇所あります。
   ・交通量調査は毎日24時間実施され、そのデータは60分単位で24分割して集中管理サーバに送付されてきます。
   ・集中管理サーバでは、調査ポイントとなる交差点にナンバリングして管理しています。
   ・交差点に付与されたナンバーに従って、パーティション分割されたテーブルにデータがロードされます。
   ・データは2年間保存され、保存期間が過ぎたデータは削除されます。

 上記システム背景を見れば、大規模データベースであることが容易に想像できた思います。

 交通量データテーブルから、交差点番号が"1"で、 調査時刻が2006/01/01 00:00:00〜2006/01/07 23:00:00の一週間分のデータを抽出する必要 が出てきました。その場合、実行されるSQLはこんな感じになります。
※調査時刻はDATE型の変数で、分以下は全て0に丸めてテーブルに格納されています。
例えば、2006/01/01 10:00:00 と言うレコードがあれば、そのレコードは2006/01/01 10:00:00〜2006/01/01 10:59:59 までの交通量を指すレコードです。



    SELECT
          調査時刻
        , 交通量
    FROM
          交通量データテーブル
    WHERE
        交差点番号 = 1
    AND 調査時刻 BETWEEN TO_DATE('2006/01/01 00:00:00') AND TO_DATE('2006/01/07 23:00:00');

 では、交差点番号が"1"で、調査時刻が2006/01/01 00:00:00〜2006/01/07 23:00:00の一週間分のデータ の中から各日10:00:00のデータのみを抽出する必要が出てきた場合はどうするでしょうか? SQL自体、記載の仕方は色々あるので、これが正解だと言う答えはありませんが、代表的な記載方法は、次の 三つになると思います。


■ SQL 1) OR演算子を使用する方法

    SELECT
          調査時刻
        , 交通量
    FROM
          交通量データテーブル
    WHERE
        交差点番号 = 1
    AND (  調査時刻 = TO_DATE('2006/01/01 10:00:00') OR 調査時刻 = TO_DATE('2006/01/02 10:00:00')
        OR 調査時刻 = TO_DATE('2006/01/03 10:00:00') OR 調査時刻 = TO_DATE('2006/01/04 10:00:00')
        OR 調査時刻 = TO_DATE('2006/01/05 10:00:00') OR 調査時刻 = TO_DATE('2006/01/06 10:00:00')
        OR 調査時刻 = TO_DATE('2006/01/07 10:00:00') );

■ SQL 2) IN演算子を使用する方法

    SELECT
          調査時刻
        , 交通量
    FROM
          交通量データテーブル
    WHERE
        交差点番号 = 1
    AND 調査時刻 IN ( TO_DATE('2006/01/01 10:00:00'), TO_DATE('2006/01/02 10:00:00')
        , TO_DATE('2006/01/03 10:00:00'), TO_DATE('2006/01/04 10:00:00')
        , TO_DATE('2006/01/05 10:00:00'), TO_DATE('2006/01/06 10:00:00')
        , TO_DATE('2006/01/07 10:00:00') );

■ SQL 3) 交通量データテーブルの"調査時刻"に関数を使用する

    SELECT
          調査時刻
        , 交通量
    FROM
          交通量データテーブル
    WHERE
        交差点番号 = 1
    AND 調査時刻 BETWEEN TO_DATE('2006/01/01 00:00:00') AND TO_DATE('2006/01/07 23:00:00')
    AND TO_CHAR(調査時刻,'HH24') = '10';

 SQL3は一番クリアなSQL文ですが、テーブルの列自体に関数(※TO_CHAR)を 使用することは、嫌われます。テーブルの"調査時刻"列にインデックスが作成されていても、 関数の働きによって、そのインデックスが使用できないからです。ファンクションインデックス を作成する方法(※TO_CHAR(調査時刻,'HH24')した状態のインデックスを作成する方法)も ありますが、ほとんどの場合は、ファンクションインデックスを作成するよりも、SQL3をSQL1もしくはSQL2の形に直す のだと思います。

 SQL1、SQL2は、"調査時刻"列にインデックスがある場合、インデックス検索に なるはずです。SQL1、SQL2のいずれかが採用されるでしょう。

 しかし、 交差点番号が"1"で、調査時刻が2006/01/01 00:00:00〜2006/01/31 23:00:00の一ヶ月分のデータ の中から各日10:00:00のデータのみを抽出する必要が出てきた場合には、どうしますか? 検索する期間が変化するたびに、OR演算子をSQL文に連結するのでしょうか?IN演算子の括弧の中に日付リストを付足すのでしょうか? 面倒くさいですよね。

 ここで、GET_ROWS関数を使用しましょう。次の様な形でデータ取得できます。

■ SQL 4) GET_ROWS関数を使用する方法

    SELECT
          調査時刻
        , 交通量
    FROM
          交通量データテーブル
        , TABLE(GET_ROWS(7))  --// 一ヶ月の検索期間の場合は、7 → 31
    WHERE
        交差点番号 = 1
    AND TO_DATE('2006/01/01 10:00:00') + N -1 = 調査時刻;
 ※任意の月の日数を求めるには、TO_NUMBER(TO_CHAR(LAST_DAY("年月"),'DD'))を使用します。 "年月"をLAST_DAY関数を使用して月末日に変換し、その月末日の日付部分をTO_CHAR(**,'DD')関数で抜出し、 最後に、TO_NUMBER関数で数値に変換すれば、月の日数が取得できます。

 上で紹介した話は、動的に検索する日付の期間が変わる場合であっても柔軟に対応できる例として紹介 しました。

 さらに、次に紹介する 「交通量データテーブルを外部結合」 した例は、大規模データベースシステムにコンパイルされているPL/SQL プロシジャー/ファンクションのパフォーマンスを向上させるかもしれません。

■ SQL 5) 外部結合を利用したGET_ROWS関数の使用方法

    SELECT
          調査時刻
        , 交通量
    FROM
          交通量データテーブル
        , TABLE(GET_ROWS(7))  --// 一ヶ月の検索期間の場合は、7 → 31
    WHERE
        交差点番号 = 1
    AND TO_DATE('2006/01/01 10:00:00') + N -1 = 調査時刻(+);

 このSQLはPL/SQLにおいて威力を発揮します。と言いますのも、PL/SQLでカーソルを実行した場合、 その検索結果を配列に格納することがよくあります。格納方法は、以下に紹介する通り、 カーソルFORループ(@) を用いる場合もあれば、バルクコレクト(A)を用いる場合もあります。
 ※通常、バルクコレクトを用いた方法の方が高速に処理できます。

DECLARE
    /* 型定義 */
    TYPE TYPE_交通量データ IS RECORD(
          調査時刻    交通量データテーブル.調査時刻%TYPE
        , 交通量      交通量データテーブル.交通量%TYPE
    );
    TYPE TBLTYPE_交通量データ IS TABLE OF TYPE_交通量データ INDEX BY BINARY_INTEGER;

    /* 配列 */
    交通量データ TBLTYPE_交通量データ;

    /* 交通量データテーブル検索カーソル */
    CURSOR 交通量検索カーソル(
          IP_検索開始日 DATE
        , IP_検索期間 NUMBER
    )
    IS
        SELECT
              調査時刻
            , 交通量
        FROM
              交通量データテーブル
            , TABLE(GET_ROWS(IP_検索期間))
        WHERE
            交差点番号 = 1
        AND IP_検索開始日 + N -1 = 調査時刻
        ORDER BY 調査時刻 ASC;

BEGIN

    ・・・    

    /* @カーソルFORループで配列に格納する例 */
    FOR REC IN 交通量検索カーソル(TO_DATE('2006/01/01 10:00:00'), 7) LOOP
    
        i := i + 1;
        交通量データ(i).調査時刻 := REC.調査時刻;
        交通量データ(i).交通量 := REC.交通量;

    END LOOP;

    ・・・    

    /* Aバルクコレクトで配列に格納する例 */
    OPEN 交通量検索カーソル(TO_DATE('2006/01/01 10:00:00'), 7);
    FETCH 交通量検索カーソル BULK COLLECT INTO 交通量データ;
    CLOSE 交通量検索カーソル;

    ・・・    

END;
/

 上の@、Aどちらの場合も同じですが、作成される配列の要素はいくつになるでしょうか? 一週間のデータを検索対象としていますが、必ず7件と言うわけではありません。データが欠損している場合も ありえますので、極端な話ですが、0件と言う場合だって考えられるでしょう。

 配列の中から"2006/01/05 10:00:00"のデータを参照したい場合は、配列の中から"2006/01/05 10:00:00"のデータを 探す作業が必要になります。

    ・・・    

    wkaddress := 0;

    /* "2006/01/05 10:00:00"のデータ番地を探す。見つからないかもしれません。*/
    FOR IDX 交通量データ.FIRST..交通量データ.LAST LOOP
        IF 交通量データ(IDX).調査時刻 = TO_DATE('2006/01/05 10:00:00') THEN
            wkaddress := IDX;  --// "2006/01/05 10:00:00"のデータ番地を特定。
        END IF;
    END LOOP;

    /* "2006/01/05 10:00:00"のデータが配列から見つかったかどうか調べます。*/
    IF wkaddress <> 0 THEN
        (交通量データ(wkaddress)を使ってなんらかの処理を実行) 
    ELSE
        NULL;  --// "2006/01/05 10:00:00"のデータが見つからない場合だってあります。
    END IF;

    ・・・    

 ここで、「SQL 5) 外部結合を利用したGET_ROWS関数の使用方法」 のSQLを使用してみます。検索結果は必ず7件なので、"2006/01/05 10:00:00"のデータは 配列の5番目に存在していることは既知です。配列の5番地の交通量がNULLではないかチェックするだけで 良いことになります。

DECLARE
    /* 型定義 */
    TYPE TYPE_交通量データ IS RECORD(
          調査時刻    交通量データテーブル.調査時刻%TYPE
        , 交通量      交通量データテーブル.交通量%TYPE
    );
    TYPE TBLTYPE_交通量データ IS TABLE OF TYPE_交通量データ INDEX BY BINARY_INTEGER;

    /* 配列 */
    交通量データ TBLTYPE_交通量データ;

    /* 必ず7件検索できる交通量データテーブル検索カーソル */
    CURSOR 交通量検索カーソル(
          IP_検索開始日 DATE
        , IP_検索期間 NUMBER
    )
    IS
        SELECT
              調査時刻
            , 交通量
        FROM
              交通量データテーブル
            , TABLE(GET_ROWS(IP_検索期間))
        WHERE
            交差点番号 = 1
        AND IP_検索開始日 + N -1 = 調査時刻(+)
        ORDER BY 調査時刻 ASC;

BEGIN

    ・・・    

    FOR REC IN 交通量検索カーソル(TO_DATE('2006/01/01 10:00:00'), 7) LOOP  --// ループは必ず7周します。
    
        i := i + 1;
        交通量データ(i).調査時刻 := REC.調査時刻;
        交通量データ(i).交通量 := REC.交通量;

    END LOOP;

    ・・・    

    /* 5番地のデータのNULLチェックをするだけで良いです。 */
    IF 交通量データ(5).交通量 IS NOT NULL THEN
        (交通量データ(5)を使ってなんらかの処理を実行) 
    ELSE
        NULL;  --// "2006/01/05 10:00:00"のデータが見つからない場合だってあります。
    END IF;

    ・・・    

END;
/

 ここでは7件のデータを対象にした例を紹介しました。配列に取込むレコードが 数百万以上で、配列の中から必要な要素を取出す処理が頻繁にある場合は、GET_ROW関数は 想像以上のパフォーマンスを提供してくれると思います。

 
ページのTOPへ
 複数行を返すGET_ROWS関数をシステムで用意しておけば、利用する場面が多々出てきそうです。 開発の前に、プログラミング担当に使い方を周知する講習会があっても良いかもしれません。