web-dev-qa-db-ja.com

複数の日付列を使用したパーティションプルーニング

Oracle 11gデータベースに数年分の履歴データを保持する大きなテーブルがあるので、年ごとに分割したいと思います。問題は、テーブルに複数の日付列があり、それらがすべてクエリで使用されるため、1つの日付列を選択してそれをパーティションキーとして使用することはできません。

ほとんどの時間の日付は互いに近いため、各年のパーティションと、年の境界を越える行を保持する1つの「オーバーフロー」パーティションを作成しました。これは簡単な例です:

create table t (
  start_year int,
  end_year int,
  partition_year int as (case when start_year=end_year then start_year else 0 end),
  data blob 
)
partition by range(partition_year) (
  partition poverflow values less than (1000),
  partition p2000 values less than (2001),
  partition p2001 values less than (2002),
  partition p2002 values less than (2003),
  partition p2003 values less than (2004),
  partition p2004 values less than (2005)
);

このアプローチの問題は、partition_yearをクエリで明示的に参照する必要があるか、または partition pruning (テーブルが大きいため非常に望ましい)が有効にならないことです。このテーブルは、複数のユーザーによるアドホック集計クエリに使用されます。彼ら全員がこの論理を覚えているとは期待できません。

これはビューで解決できます

create or replace view v as
select *
from t
where partition_year=start_year 
  and partition_year=end_year 
  and partition_year>1000
union all
select *
from t partition (poverflow);

今、このようなクエリ

select * from v where start_year >= 2003 and end_year <= 2004;

正しいパーティションを使用してください(以下の計画では5-6 + 1):

---------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |      |     1 |  4030 |     2   (0)| 00:00:01 |       |       |
|   1 |  VIEW                      | V    |     1 |  4030 |     2   (0)| 00:00:01 |       |       |
|   2 |   UNION-ALL                |      |       |       |            |          |       |       |
|   3 |    PARTITION RANGE ITERATOR|      |     1 |  2041 |     2   (0)| 00:00:01 |     5 |     6 |
|*  4 |     TABLE ACCESS FULL      | T    |     1 |  2041 |     2   (0)| 00:00:01 |     5 |     6 |
|   5 |    PARTITION RANGE SINGLE  |      |     1 |  2041 |     2   (0)| 00:00:01 |     1 |     1 |
|*  6 |     TABLE ACCESS FULL      | T    |     1 |  2041 |     2   (0)| 00:00:01 |     1 |     1 |
---------------------------------------------------------------------------------------------------

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

   4 - filter("START_YEAR">=2003 AND "END_YEAR"<=2004 AND "END_YEAR">=2003 AND 
              "START_YEAR"<=2004 AND "PARTITION_YEAR"<=2004 AND "PARTITION_YEAR"="START_YEAR" AND 
              "PARTITION_YEAR"="END_YEAR")
   6 - filter("START_YEAR">=2003 AND "END_YEAR"<=2004)

問題は、int型を日付に置き換えると、これが機能しなくなることです。日付から年コンポーネントを抽出し、対応する制約をビューに追加しようとしましたが、パーティションが整理されていません。 partition_yearのタイプを日付に変更しても、効果がありませんでした。

テーブルに複数の日付列を設定しても、パーティションプルーニングを使用できる方法はありますか?

5
sjk

部分的な解決策を見つけました

ビューを次のように定義することにより

create or replace view v as
select *
from t
where partition_date between start_date and end_date 
  and partition_date > date'1000-01-01'
union all
select *
from t partition (poverflow);

次のクエリは正しく機能し、パーティション1、4、5にのみアクセスします

select * from v where start_date >= date'2002-01-01' and end_date <= date'2003-01-01';

ただし、クエリ

select * from v where start_date = date'2002-01-01';

1と4の代わりにパーティション1、4〜6をスキャンします(代わりにend_dateを使用すると、パーティション1〜4にアクセスします)。私たちの場合、これは重大な制限ではありません。通常のクエリは最近の年のみにアクセスし、特定の日付と過去の日付範囲に対するクエリはまれであるためです。

このアプローチの少し異なるバージョンは、partition_date列を次のように定義することです。

case when trunc(start_date,'YEAR')=trunc(end_date,'YEAR') then greatest(start_date,end_date) 
else to_date('01.01.0001') end

そしてビューとして

create or replace view v as
select *
from t
where partition_date >= start_date and partition_date >= end_date
  and partition_date > date'1000-01-01'
union all
select *
from t partition (poverflow);

これは同様のパフォーマンスですが、start_dateとend_dateはどちらもより最近の年にアクセスします。このように要件が緩和された場合(過去数年のみのプルーニングが許可されます)、オーバーフローパーティションは実際には不要になり、ソリューションは次のように簡素化されます。

create table t (
  start_date date,
  end_date date,
  partition_date date as (greatest(start_date,end_date)),
  data blob
)
partition by range(partition_date) (
  partition p2000 values less than (date'2001-01-01'),
  partition p2001 values less than (date'2002-01-01'),
  partition p2002 values less than (date'2003-01-01'),
  partition p2003 values less than (date'2004-01-01'),
  partition p2004 values less than (date'2005-01-01')
);

create or replace view v as
select *
from t
where partition_date >= start_date and partition_date >= end_date;
0
sjk

Oracleは、パーティション化された列に関数が適用されている場合、パーティションのプルーニングを実行できません。 ドキュメントから

オプティマイザがプルーニングを実行できない場合がいくつかあります。一般的な理由の1つは、パーティション列の上で演算子を使用する場合です。これは、明示的な演算子(関数など)でも、ステートメントの実行に必要なデータ型変換の一部としてOracleによって導入された暗黙的な演算子でもかまいません。

あなたの見解は、それらが同じ年かどうかを判断するために、開始日と終了日に何らかの形の関数を適用する必要があるので、このアプローチでは運が悪いと思います。

同様の問題に対する私たちの解決策は、実表に異なるパーティションキーを指定して、実表にマテリアライズドビューを作成することでした。

共通の基本クエリに一致するように調整したので、クエリの書き換えの利点も得られます。クエリの書き換えに依存するのではなく、ユーザーがMVを直接使用して、パーティションのプルーニングが必要に応じて機能するようにする必要がある場合があります。

(誤った例を削除し、関数をパーティション列に適用することに関する情報を追加するために更新されました)

1
Chris Saxon

私はChrisが提供するソリューションを次のデータでテストしました。

insert into t (start_year,end_year) values (date'2011-01-01',date'2011-01-01');
insert into t (start_year,end_year) values (date'2011-01-01',date'2011-01-02');

ビューに対してクエリを実行した場合:

select * from v;

最初の行だけが返されます。これは、ビューに等価述語があるが、パーティション定義にextract(year)関数があるためです。

ビューを変更して抽出関数を含める場合:

create or replace view v as
select *
from t
where extract(year from partition_year)=extract(year from start_year)
  and extract(year from partition_year)=extract(year from end_year)
  and partition_year>date'2000-01-01'
union all
select *
from t partition (poverflow);

正しい結果が得られましたが、パーティションのプルーニングは発生しなくなりました。

0
sjk