【问题标题】:Using case for indexes in oracle在 oracle 中使用大小写索引
【发布时间】:2021-08-03 16:28:15
【问题描述】:

我在 Oracle 中的一张表上定义了以下索引。

CASE  WHEN C IS NOT NULL THEN A||','||B||','||C END

最初,我认为当 C 不为空时,用逗号分隔的 A、B、C 的组合应该是唯一的,但似乎我错了。我也对此进行了一些研究,但找不到很好的解释。

有人可以帮我解决这个问题吗?

提前致谢!!

【问题讨论】:

  • 请在您的问题中进行编辑以提供完整的创建索引语句,以及展示您希望发生的事情的示例数据。哪些列应该是唯一的,什么时候是唯一的?
  • 由于您没有包含完整的语句,这有点猜测,但您可以尝试从“CREATE INDEX”语句中删除“UNIQUE”。祝你好运。

标签: oracle indexing


【解决方案1】:

是的,它可以是独一无二的。 但不要忘记——独特性是额外的成本。 这样的索引几乎没有什么好处,因为当 C 为 NULL 时,字段 A 和 B 的行将重复。

我想可能值得将索引函数更改为:

a||','||b||case when c is not null then ','||c end

如果仅使用列 A 和 B,这将控制重复项。 当然,除非您的表有其他使用列 A 和 B 的唯一索引。

用于演示 - 一个简短的示例,具有唯一的功能索引。

注意请在描述问题/任务时,提供相关对象(表、索引、过程...等)的描述。)

为了测试,请尝试

--ddl
drop table test_tab;
create table test_tab
( a varchar2(3) not null,
  b varchar2(3) not null,
  c varchar2(3),
  idx  varchar2(12) generated always as (case when c is not null then a||','||b||','||c end) virtual
);  
--
create unique index test_tab_i1 on test_tab(idx);

--dml
insert into test_tab(a,b,c) values('a1','b1','c1');
insert into test_tab(a,b,c) values('a1','b2','c2');
insert into test_tab(a,b,c) values('a1','b1',null);
commit;

select * from test_tab;

ANALYZE TABLE test_tab COMPUTE STATISTICS FOR ALL INDEXES;

--get data of idx 
select INDEX_NAME,NUM_ROWS,DISTINCT_KEYS from user_indexes i where i.table_name=upper('test_tab');

--dubt
insert into test_tab(a,b,c) values('a1','b1',null);
commit;
--
ANALYZE TABLE test_tab COMPUTE STATISTICS FOR ALL INDEXES;

--pass2
select * from test_tab;
--get data of idx 
select INDEX_NAME,NUM_ROWS,DISTINCT_KEYS from user_indexes i where i.table_name=upper('test_tab');

--pass3
--dubt
insert into test_tab(a,b,c) values('a1','b1','c1');

Result: 
Table created.

Index created.

1 row(s) inserted.

1 row(s) inserted.

1 row(s) inserted.

Statement processed.

Result Set 31
A   B   C   IDX
a1  b1  c1  a1,b1,c1
a1  b2  c2  a1,b2,c2
a1  b1   -   - 
Download CSV
3 rows selected.

Statement processed.

Result Set 32
INDEX_NAME  NUM_ROWS    DISTINCT_KEYS
TEST_TAB_I1 2   2
Download CSV

1 row(s) inserted.

Statement processed.

Statement processed.

Result Set 33
A   B   C   IDX
a1  b1  c1  a1,b1,c1
a1  b2  c2  a1,b2,c2
a1  b1   -   - 
a1  b1   -   - 
Download CSV
4 rows selected.

Result Set 34
INDEX_NAME  NUM_ROWS    DISTINCT_KEYS
TEST_TAB_I1 2   2
Download CSV

ORA-00001: unique constraint (SQL_XKMHFTSRXNSKMGVEDOYSVXZGL.TEST_TAB_I1) violated ORA-06512: at "SYS.DBMS_SQL", line 1721

【讨论】:

    【解决方案2】:

    即使你的问题很模糊,也很有趣。我会尽量给你一个可能的解释。

    A||','||B 是唯一的,如果您可以保证对于 (A,B) 的 2 个不同值,函数 A||','||B 不会返回相同的值。

    您需要保证 TO_CHAR(A) ||','|| TO_CHAR(B) 是唯一的。

    1. 我正在考虑 A 和 B 不是 NULL。
    2. 如果 A 和 B 是 VARCHAR,A||','||B 是唯一的,但如果您有 DATE 或 TIMESTAMP 数据类型,则情况并非如此。这不是 TRUE,因为 TO_CHAR 函数的默认参数取决于会话中的 nls_parameters。

    测试用例 - 执行相同的查询,但更改 nls_date_format 并尝试创建唯一索引

    drop table test;
    create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
    
    alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
    drop index test;
    truncate table test;
    insert into test(a,b,c) values(sysdate+(1/60),'1','c1');
    insert into test(a,b,c) values(sysdate+(1/160),'1','c1');
    insert into test(a,b,c) values(sysdate+(1/80),'2','c2');
    insert into test(a,b,c) values(sysdate+(1/180),'2','c2');
    commit;
    create unique index test on test (case when c is not null then a||','||b||','||c end);
    select (case when c is not null then a||','||b||','||c end) valfn from test order by B;
    
    alter session set nls_date_format= 'dd/mm/yyyy';
    truncate table test;
    drop index test;
    insert into test(a,b,c) values(sysdate+(1/60),'1','c1');
    insert into test(a,b,c) values(sysdate+(1/160),'1','c1');
    insert into test(a,b,c) values(sysdate+(1/80),'2','c2');
    insert into test(a,b,c) values(sysdate+(1/180),'2','c2');
    create unique index test on test (case when c is not null then a||','||b||','||c end);
    select (case when c is not null then a||','||b||','||c end) valfn from test order by B;
    

    使用 nls_date_format= 'dd/mm/yyyy hh24:mi:ss' 输出

    Index TEST created.
    
    VALFN                    
    ------------------------ 
    15/05/2021 08:40:35,1,c1 
    15/05/2021 08:25:35,1,c1 
    15/05/2021 08:34:35,2,c2 
    15/05/2021 08:24:35,2,c2 
    
    

    使用 nls_date_format= 'dd/mm/yyyy' 输出

    ORA-01452: cannot CREATE UNIQUE INDEX; duplicate keys found
    
    VALFN           
    --------------- 
    15/05/2021,1,c1
    15/05/2021,1,c1 
    15/05/2021,2,c2 
    15/05/2021,2,c2 
    
    

    然后我们可以看到创建索引时发生了什么。我们将查询索引定义来解释这种行为。

    使用 nls_date_format= 'dd/mm/yyyy hh24:mi:ss' 输出

    alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
    drop table test;
    create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
    create unique index test on test (case when c is not null then a||','||b||','||c end);
    select index_name, column_expression from all_ind_expressions where table_name = 'TEST'
    
    
    INDEX_NAME COLUMN_EXPRESSION                                                                             
    ---------- --------------------------------------------------------------------------------------------- 
    TEST       CASE  WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy hh24:mi:ss')||','||"B"||','||"C" END  
    

    使用 nls_date_format= 'dd/mm/yyyy' 输出

    alter session set nls_date_format= 'dd/mm/yyyy';
    drop table test;
    create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
    create unique index test on test (case when c is not null then a||','||b||','||c end);
    select index_name, column_expression from all_ind_expressions where table_name = 'TEST'
    
    INDEX_NAME COLUMN_EXPRESSION                                                                  
    ---------- ---------------------------------------------------------------------------------- 
    TEST       CASE  WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy')||','||"B"||','||"C" END  
    

    如您所见,索引并不相同,因为它取决于您创建索引时的 nls_date_format。我总是建议验证 Oracle 在创建对象时使用的表达式。

    创建虚拟列时也是这样

    alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
    drop table test;
    create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10)
      ,idx  varchar2(50) generated always as (case when c is not null then a||','||b||','||c end) virtual
    );
    select DATA_DEFAULT from all_tab_cols where table_name = 'TEST' and column_name = 'IDX';
    
    DATA_DEFAULT                                                                                  
    --------------------------------------------------------------------------------------------- 
    CASE  WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy hh24:mi:ss')||','||"B"||','||"C" END  
    
    alter session set nls_date_format= 'dd/mm/yyyy';
    drop table test;
    create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10)
      ,idx  varchar2(50) generated always as (case when c is not null then a||','||b||','||c end) virtual
    );
    select DATA_DEFAULT from all_tab_cols where table_name = 'TEST' and column_name = 'IDX';
    
    DATA_DEFAULT                                                                       
    ---------------------------------------------------------------------------------- 
    CASE  WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy')||','||"B"||','||"C" END  
    

    现在,您可以将 C 添加到此等式并验证您的情况的原因:CASE WHEN C IS NOT NULL THEN A||','||B||','||C END is not unique

    大多数时候,默认情况下 nls_date_format 不包括时间。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-20
      • 1970-01-01
      • 2017-12-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-23
      • 1970-01-01
      相关资源
      最近更新 更多