这些空值如何存储在 NOT NULL 列中?

Fre*_*ope 4 oracle oracle-10g sap-hana

我们正在使用 SAP SLT 将 HANA 上的 SAP ECC 6.0 中的表复制到 Oracle 10g 仓库中。自开始以来,我们注意到NOT NULLHANA 中的列定义保留在表的 Oracle 副本中,但 HANA 将许多值存储为空字符串。Oracle 将空(varchar)字符串存储为NULLs 并且不知何故这与NOT NULL列定义不冲突(即我们NULL在列中定义为NOT 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
Run Code Online (Sandbox Code Playgroud)

所以我们可以通过使用或函数 NULL判断这些列中有值,但是查询它们会返回奇怪的结果。NVLDECODE

一旦我们改变了列,我们就会得到正确的结果:

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

但当然我们不能改变列:

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

我不知道这是 Oracle 实现空字符串存储的问题,还是可能只是与 SAP 的 SLT 复制交互的一个怪癖。似乎 Oracle 不应该允许这些带有''NULL值的行,因为复制尝试将它们放在那里,但我们没有看到任何错误表明这一点。


编辑以添加超立方体请求的查询:

SELECT LENGTH(col) FROM warehouse.table GROUP BY LENGTH(col);
 2377222
1 503206
Run Code Online (Sandbox Code Playgroud)

Bal*_*app 6

你描述的情况不正常。

正如评论中已经提到的,在 Oracle 数据库中,空/零长度字符串被视为NULL.

https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements005.htm#i59110

笔记:

Oracle 数据库当前将长度为零的字符值视为空值。但是,这在未来版本中可能不再适用,Oracle 建议您不要将空字符串视为空字符串。

此外,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
Run Code Online (Sandbox Code Playgroud)

到目前为止一切正常。

数据库可以在基于约束执行 SQL 语句时跳过整个步骤。如果您在 上启用、验证、NOT NULL约束col,并且您的谓词为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
Run Code Online (Sandbox Code Playgroud)

现在我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.
Run Code Online (Sandbox Code Playgroud)

然后再次运行查询:

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
Run Code Online (Sandbox Code Playgroud)

返回 0 行,该表根本没有被访问(Starts = 0on Operation Id 3),因为FILTERon Operation Id 2: NULL IS NOT NULL,这显然是FALSE.

如果使用NVL(col, 'N')代替col,则数据库无法使用这种优化,将访问表并返回正确的结果:

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
Run Code Online (Sandbox Code Playgroud)

FILTER这次没有,并且该表已被访问(Starts 1Operation Id 2 - TABLE ACCESS FULL)。

10g 中存在一个错误,该错误导致对传递谓词的CHECK约束进行优化时导致错误结果NULL

错误 5462687 - CHECK 约束可能导致错误结果(文档 ID 5462687.8)

这对我没有帮助,因为我自己破坏了字典。但是,如果您的实际查询比您发布的更复杂(否则不要理会这个),并且有传递谓词,您可以尝试在此编写的解决方法,并通过设置事件 10195 禁用此行为

$ oerr ora 10195
10195, 00000, "CBO don't use check constraints for transitive predicates"
// *Cause:
// *Action:
Run Code Online (Sandbox Code Playgroud)

例子:

alter session set event '10195 trace name context level 1';
Run Code Online (Sandbox Code Playgroud)

我不认为这是由数据的存储方式引起的,而只是与约束有关。