SAP SLTを使用して、HANA上のSAP ECC 6.0からOracle 10gウェアハウスにテーブルを複製しています。これを開始して以来、HANAのNOT NULL
列の定義がテーブルのOracleコピーに保持されていることに気付きましたが、HANAは多くの値を空の文字列として格納します。 Oracleは空の(varchar)文字列をNULL
sとして保存しますが、これは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
あなたが述べたことは正常ではありません。
コメントですでに述べたように、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';
_
これはデータの格納方法が原因ではなく、制約に関するものだと思います。
制約の定義を見たいと思うかもしれません。検証されない(既存のデータは制約の作成時にチェックされない)および/または無効(挿入/更新時にデータベースによって強制されない)の制約を定義することが可能です。
これらの制約は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;