web-dev-qa-db-ja.com

Oracleオプティマイザーが仮想列を使用してパーティションについて調べる

次のような3つの列がある大きなテーブルがあります。

"START_DATE" DATE,
"START_VALUE" NUMBER(10,7)
"START_DATE_VALUE" NUMBER(18,7) 
  GENERATED ALWAYS AS 
  (
    (extract(YEAR FROM START_DATE) * 10000 + 
     extract(MONTH FROM START_DATE)*100 + 
     extract(DAY FROM START_DATE))*power(10,3) + 
     (START_VALUE+180)
  ) VIRTUAL

START_DATE_VALUE columnは、パーティション化に使用される仮想列です。ただし、次のようなクエリがある場合:

select * 
from mytable
where
      start_date > to_date('02-01-2012', 'MM-DD-YYYY')
 and start_value > 120.23452

結果についてすべてのパーティションをスキャンします。 Oracleに仮想列を使用させ、適切なパーティションだけを選択して作業するにはどうすればよいですか?

テーブルの定義が非常に大きいため、ここにコピーすることはできません。

6
Sean Nguyen

仮想列の定義に問題があると思います。あなたの特別な値について2012-02-01(inYYYY-MM-DD形式)および120.23452仮想列の値は

2012*10000+2*100+1*1000+180+120.23452 = 20120000+200+1000+300.23452 =20121500.23452

ではなく

20120201300.23452

あなたが期待したように。

また、仮想列がテーブルのパーティション化に使用されている列かどうかも確認してください。

VLDB and Partitioning Guide から:

仮想列ベースのパーティション分割テーブルは、SQLステートメントで仮想列を定義する式を使用するステートメントのパーティションプルーニングの恩恵を受けます。

select-statementだと思います

select * from mytable where start_date > todate('02-01-2012', 'MM-DD-YYYY') and    start_value > 120.23452

のようなものでなければなりません

select * from mytable where (
  extract(YEAR FROM START_DATE) * 10000 + 
  extract(MONTH FROM START_DATE)*100 + 
  extract(DAY FROM START_DATE))*power(10,3) + 
  (START_VALUE+180)
)  > 20121500.23452

パーティション・プルーニングが行われるようにします。

todate

selectステートメントで使用する関数がOracle SQLに存在しません。関数の名前は

to_date

2
miracle173

「Oracleで仮想列を使用するようにして、適切なパーティションを選択して作業する方法を教えてください。」

あなた(START_DATE, START_VALUE)START_DATE_VALUEの間に関係があることを知っていますが、残念ながらオプティマイザはそうではありません。あなたが知っているのは、あなたがあなたのパーティショニング戦略で機能しない2つのカラムをクエリしているということだけです。

パーティションキーを投稿していないため、このテストケースで何をしているのかを推測しました。

create table t23
    (id number not null primary key
     , start_date date not null
    , start_value number not null
    , start_date_value number GENERATED ALWAYS AS
      (
        (extract(YEAR FROM START_DATE) * 10000 +
        extract(MONTH FROM START_DATE)*100 +
        extract(DAY FROM START_DATE))*power(10,3) +
       (START_VALUE+180)
     ) VIRTUAL
   )
   PARTITION BY range (start_date_value)
   (
     PARTITION range_10  values LESS THAN  (1900000000) ,
     PARTITION range_20  values LESS THAN  (2000000000),
     PARTITION range_30  values LESS THAN  (2100000000),
     PARTITION range_40  values LESS THAN  (2200000000),
     PARTITION range_50  values LESS THAN (11000000000),
     PARTITION range_60  values LESS THAN (12000000000),
     PARTITION range_70  values LESS THAN (13000000000),
     PARTITION range_80  values LESS THAN (to_date('01-JAN-1400')),
     PARTITION range_90  values LESS THAN (15000000000),
     PARTITION range_100 values LESS THAN (16000000000),
     PARTITION range_110 values LESS THAN (17000000000),
     PARTITION range_120 values LESS THAN (18000000000),
     PARTITION range_130 values LESS THAN (19000000000),
     PARTITION range_140 values LESS THAN (20000000000),
     PARTITION range_150 values LESS THAN (21000000000),
     PARTITION range_160 values LESS THAN (22000000000),
     PARTITION range_mx values LESS THAN (maxvalue)
 )
/ 

あなたが説明したシナリオを再現するのは非常に簡単です。クエリのすべての行はパーティションrange_80にありますが、Explainプランはすべてのパーティションの検索を示しています。

SQL> explain plan for
     select * from t23
     where start_date between to_date('31-dec-1390') 
                          and to_date('01-jan-1393')
     and start_value between 1050 and 4999
/
  2    3    4    5    6  
Explained.

SQL> select * from table(dbms_xplan.display)
  2  /

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------
Plan hash value: 4042841927

--------------------------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |     1 |    25 |   250   (1)| 00:00:03 |       |       |
|   1 |  PARTITION RANGE ALL|      |     1 |    25 |   250   (1)| 00:00:03 |     1 |    17 |
|*  2 |   TABLE ACCESS FULL | T23  |     1 |    25 |   250   (1)| 00:00:03 |     1 |    17 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("START_VALUE"<=4999 AND "START_DATE">=TO_DATE(' 1390-12-31 00:00:00',
              'syyyy-mm-dd hh24:mi:ss') AND "START_DATE"<=TO_DATE(' 1393-01-01 00:00:00',
              'syyyy-mm-dd hh24:mi:ss') AND "START_VALUE">=1050)

16 rows selected.

SQL> 

したがって、2つのオプションがあります。最初のオプションは、クエリでSTART_DATE_VALUEを使用することです。おそらく、この明白な解決策を適用しない理由があるでしょう。その列にビジネス上の意味がないためと考えられます。

代替策は、パーティション化戦略を変更することです。これは簡単に実現できます。 START_DATE_VALUE列は、基本的にSTART_DATE内にSTART_VALUEで行を並べ替えます。サブパーティション化でも同じ効果が得られます。

これがテーブルT42です。 START_DATEの範囲パーティションとSTART_VALUEの範囲サブパーティションがあります(テンプレート句は同じパーティションを各パーティションに適用します)。

create table t42
    (id number not null primary key
     , start_date date not null
    , start_value number not null
    )
    PARTITION BY range (start_date)
    SUBPARTITION BY range (start_value)
    SUBPARTITION TEMPLATE(
        SUBPARTITION lowval VALUES LESS THAN (1000) ,
        SUBPARTITION medval VALUES LESS THAN (5000) ,
        SUBPARTITION highval VALUES LESS THAN (MAXVALUE) 
    )
(
     PARTITION range_10  values LESS THAN  (to_date('01-JAN-700')),
     PARTITION range_20  values LESS THAN  (to_date('01-JAN-800')),
     PARTITION range_30  values LESS THAN  (to_date('01-JAN-900')),
     PARTITION range_40  values LESS THAN  (to_date('01-JAN-1000')),
     PARTITION range_50  values LESS THAN (to_date('01-JAN-1100')),
     PARTITION range_60  values LESS THAN (to_date('01-JAN-1200')),
     PARTITION range_70  values LESS THAN (to_date('01-JAN-1300')),
     PARTITION range_80  values LESS THAN (to_date('01-JAN-1400')),
     PARTITION range_90  values LESS THAN (to_date('01-JAN-1500')),
     PARTITION range_100 values LESS THAN (to_date('01-JAN-1600')),
     PARTITION range_110 values LESS THAN (to_date('01-JAN-1700')),
     PARTITION range_120 values LESS THAN (to_date('01-JAN-1800')),
     PARTITION range_130 values LESS THAN (to_date('01-JAN-1900')),
     PARTITION range_140 values LESS THAN (to_date('01-JAN-2000')),
     PARTITION range_150 values LESS THAN (to_date('01-JAN-2100')),
     PARTITION range_160 values LESS THAN (to_date('01-JAN-2200')),
     PARTITION range_mx values LESS THAN (maxvalue)
 )
/ 

何もないことを証明するために、T42にT23のまったく同じデータを入力します。

SQL> insert into t42
     select id, start_date, start_value
     from t23
     /
  2    3    4  
232800 rows created.

SQL> commit;

Commit complete.

SQL>  EXEC DBMS_STATS.gather_table_stats(USER, 'T42')

PL/SQL procedure successfully completed.

SQL> explain plan for
     select * from t42
     where start_date between to_date('31-dec-1390') 
                          and to_date('01-jan-1393')
     and start_value between 1050 and 4999
/
SQL> SQL>   2    3    4    5    6  
Explained.

SQL>

ご覧のとおり、explain planは単一のパーティション内の単一のサブパーティションを選択します。精密剪定!

SQL> select * from table(dbms_xplan.display)
  2  /

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------
Plan hash value: 2460612001

------------------------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     1 |    17 |    27   (0)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE SINGLE |      |     1 |    17 |    27   (0)| 00:00:01 |     8 |     8 |
|   2 |   PARTITION RANGE SINGLE|      |     1 |    17 |    27   (0)| 00:00:01 |     2 |     2 |
|*  3 |    TABLE ACCESS FULL    | T42  |     1 |    17 |    27   (0)| 00:00:01 |    23 |    23 |
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - filter("START_VALUE"<=4999 AND "START_DATE">=TO_DATE(' 1390-12-31 00:00:00',
              'syyyy-mm-dd hh24:mi:ss') AND "START_DATE"<=TO_DATE(' 1393-01-01 00:00:00',
              'syyyy-mm-dd hh24:mi:ss') AND "START_VALUE">=1050)

17 rows selected.

SQL>

「サブパーティションを使いたくないのは、テキストインデックスが原因です。」

関連する質問は、サブパーティションが必要ですか?つまり、テーブル(行数)はどのように選ぶのですか? 1日あたりの行数は? START_DATE日あたり1つのパーティションのみを使用してスクレイピングし、START_VALUEを忘れてしまいませんか?

2
APC