web-dev-qa-db-ja.com

これらのNULL値はNOT NULL列にどのように格納されますか?

SAP SLTを使用して、HANA上のSAP ECC 6.0からOracle 10gウェアハウスにテーブルを複製しています。これを開始して以来、HANAのNOT NULL列の定義がテーブルのOracleコピーに保持されていることに気付きましたが、HANAは多くの値を空の文字列として格納します。 Oracleは空の(varchar)文字列をNULLsとして保存しますが、これはNOT NULL列の定義と競合しません(つまり、NOT NULLとして定義された列にNULLがあります)。 。

これらのテーブルをクエリすると、奇妙な結果が生じます。

SELECT COUNT(*) FROM warehouse.table WHERE col IS NULL;
0
SELECT COUNT(*) FROM warehouse.table WHERE col = '';
0
SELECT COUNT(*) FROM warehouse.table GROUP BY NVL(col,'N');
X 503206
N 2377222

したがって、NULL関数またはNVL関数を使用してこれらの列にareDECODE値があることを確認できますが、それらをクエリすると奇妙な結果が返されます。

列を変更すると、適切な結果が得られます。

ALTER TABLE warehouse.table MODIFY (col NULL);
Table altered.
SELECT COUNT(*) FROM warehouse.table WHERE col IS NULL;
390986

ただし、もちろん列を元に戻すことはできません。

ALTER TABLE warehouse.table MODIFY (col NOT NULL);
ERROR at line 1:
ORA-02296: cannot enable (warehouse.) - null values found

これがOracleの空の文字列ストレージの実装に関する問題なのか、SAPのSLTレプリケーションとの相互作用の問題なのかはわかりません。レプリケーションがそれらを配置しようとするため、Oracleは''またはNULLの値を持つこれらの行を許可しないようですが、これを示すエラーは確認されていません。


ハイパーキューブによって要求されたクエリを追加するための編集:

SELECT LENGTH(col) FROM warehouse.table GROUP BY LENGTH(col);
 2377222
1 503206
4
Fred Shope

あなたが述べたことは正常ではありません。

コメントですでに述べたように、Oracleデータベースでは、空/長さゼロの文字列はNULLとして扱われます。

https://docs.Oracle.com/cd/B19306_01/server.102/b14200/sql_elements005.htm#i5911

注意:

Oracle Databaseは現在、長さがゼロの文字値をNULLとして扱います。ただし、これは今後のリリースでは当てはまらない可能性があるため、空の文字列をnullと同じように処理しないことをお勧めします。

また、_WHERE col = ''_は基本的に_WHERE col = NULL_であるため、結果を返すことはありません。

データディクショナリを手動で破損させることでこれらの誤った結果を再現するのはかなり簡単ですが、この情報だけでは、環境内で何が原因であるのかはわかりません。

_SQL> create table t1 (c1 varchar2(20) not null disable);

Table created.

SQL> select constraint_name from user_constraints where table_name = 'T1';

CONSTRAINT_NAME
------------------------------
SYS_C005148

SQL> insert into t1(c1) values ('');

1 row created.

SQL> commit;

Commit complete.

SQL> select count(*) from t1 where c1 is null;

  COUNT(*)
----------
         1
_

これまでのところ、すべてが正常です。

データベースは、制約に基づいてSQLステートメントを実行している間、ステップ全体をスキップできます。 colに対して有効で検証済みの_NOT NULL_制約があり、述語が_col is null_である場合、データベースは列にNULLを含めることができないことを認識しているため、関連するステップを実際に実行せずに0行を返します。列に有効な検証済みの制約がある場合、その列の_NULL$_辞書テーブルの_COL$_列は1に設定されますが、無効で検証されていない制約があっても、_NULL$_は、データベースが誤った結果を返すのに十分です。

その制約を有効にする適切な方法は次のとおりです(明らかに失敗します)。

_SQL> alter table t1 modify constraint SYS_C005148 enable validate;
alter table t1 modify constraint SYS_C005148 enable validate
                                 *
ERROR at line 1:
ORA-02293: cannot validate (SYS.SYS_C005148) - check constraint violated
_

ここで_NULL$_を手動で設定します:

_SQL> update col$ set null$ = 1 where obj# = (select object_id from user_objects where object_name = 'T1') and name = 'C1';

1 row updated.

SQL> commit;

Commit complete.
_

次に、もう一度クエリを実行します。

_SQL> select /*+ gather_plan_statistics */ count(*) from t1 where c1 is null;

  COUNT(*)
----------
         0

SQL> select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  fjf8bcs2hhb7b, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from t1 where c1 is null

Plan hash value: 4294799605

----------------------------------------------------------------------------
| Id  | Operation           | Name | Starts | E-Rows | A-Rows |   A-Time   |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |      1 |        |      1 |00:00:00.01 |
|   1 |  SORT AGGREGATE     |      |      1 |      1 |      1 |00:00:00.01 |
|*  2 |   FILTER            |      |      1 |        |      0 |00:00:00.01 |
|   3 |    TABLE ACCESS FULL| T1   |      0 |      1 |      0 |00:00:00.01 |
----------------------------------------------------------------------------

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

   2 - filter(NULL IS NOT NULL)

Note
-----
   - dynamic sampling used for this statement
_

_Starts = 0_:_Operation Id 3_のFILTERのため、テーブルはまったくアクセスされませんでした(_Operation Id 2_)(_NULL IS NOT NULL_)。 FALSE

colの代わりにNVL(col, 'N')を使用すると、データベースはこの種の最適化を使用できず、テーブルにアクセスして正しい結果を返します。

_SQL> select /*+ gather_plan_statistics */ nvl(c1, 'N'), count(*) from t1 group by nvl(c1, 'N');

NVL(C1,'N')            COUNT(*)
-------------------- ----------
N                             1

SQL> select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------
SQL_ID  0zfyk18knxtfk, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ nvl(c1, 'N'), count(*) from t1 group by nvl(c1, 'N')

Plan hash value: 136660032

----------------------------------------------------------------------------------------------------------------
| Id  | Operation          | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |      1 |        |      1 |00:00:00.01 |       3 |       |       |          |
|   1 |  HASH GROUP BY     |      |      1 |      1 |      1 |00:00:00.01 |       3 |  1156K|  1156K|  323K (0)|
|   2 |   TABLE ACCESS FULL| T1   |      1 |      1 |      1 |00:00:00.01 |       3 |       |       |          |
----------------------------------------------------------------------------------------------------------------

Note
-----
   - dynamic sampling used for this statement
_

今回はFILTERはなく、テーブルにアクセスしました(_Starts 1_で_Operation Id 2 - TABLE ACCESS FULL_)。

遷移述語のCHECKを使用したNULL制約の最適化で誤った結果を引き起こす10gのバグがありました:

Bug 5462687-CHECK制約は誤った結果を引き起こす可能性があります(ドキュメントID 5462687.8)

辞書を自分で壊したので、これは私には役に立ちません。ただし、実際のクエリが投稿したクエリよりも複雑で(そうでない場合はこれに悩まされない)、推移的な述語がある場合は、これに記述されている回避策を試し、イベント10195を設定してこの動作を無効にすることができます。

_$ oerr ora 10195
10195, 00000, "CBO don't use check constraints for transitive predicates"
// *Cause:
// *Action:
_

例:

_alter session set event '10195 trace name context level 1';
_

これはデータの格納方法が原因ではなく、制約に関するものだと思います。

6
Balazs Papp

制約の定義を見たいと思うかもしれません。検証されない(既存のデータは制約の作成時にチェックされない)および/または無効(挿入/更新時にデータベースによって強制されない)の制約を定義することが可能です。

これらの制約はRELYとしてマークすることもできます。つまり、オプティマイザはそれらが適用されると想定します。通常、この状況は、データが制約に準拠していることを確認できるデータウェアハウス/レポート環境でのみ必要です。

ロードメカニズムが制約を無効にし、データをロード/追加してから、novalidate設定で再度有効にした可能性があります。

オプティマイザはNULL値がないと想定しているため、NULL値が見つからない計画を選択する場合があります。

create table test_rely
    (id number,
    val varchar2(10)); 
alter table test_rely add constraint test_rely_val_nn check(val is not null) rely disable novalidate;

insert into test_rely values(10,null);

alter table test_rely modify constraint test_rely_val_nn enable novalidate;
1
Gary