【问题标题】:Oracle 18c - Alternative to REGEXP_REPLACEOracle 18c - REGEXP_REPLACE 的替代品
【发布时间】:2020-01-14 19:21:24
【问题描述】:

迁移到 Oracle 18c 企业版后,无法创建基于函数的索引。

这是我的索引 DDL:

CREATE INDEX my_index ON my_table
(UPPER( REGEXP_REPLACE ("DEPT_NUM",'[^[:alnum:]]',NULL,1,0)))
TABLESPACE my_tbspace
PCTFREE    10
INITRANS   2
MAXTRANS   255
STORAGE    (
            INITIAL          64K
            MINEXTENTS       1
            MAXEXTENTS       UNLIMITED
            PCTINCREASE      0
            BUFFER_POOL      DEFAULT
           );

我收到以下错误:

ORA-01743: only pure functions can be indexed
01743. 00000 -  "only pure functions can be indexed"
*Cause:    The indexed function uses SYSDATE or the user environment.
*Action:   PL/SQL functions must be pure (RNDS, RNPS, WNDS, WNPS).  SQL
           expressions must not use SYSDATE, USER, USERENV(), or anything
           else dependent on the session state.  NLS-dependent functions
           are OK.

这是 18c 中的已知错误吗?如果不再支持这个基于函数的索引,那么这个函数的另一种写法是什么?

【问题讨论】:

  • 这可能是 18c 中的一个错误。我没有安装这个来搞乱,所以我使用了 dbfiddle.uk。我能够创建this dbfiddle,它使用自定义确定性函数来创建基于函数的索引,并且在 11g 下运行良好。但是,当我将数据库切换到 18c 并尝试运行它时,页面就会挂起。如果安装了 18c 的人可以检查一下这将是一个帮助。 Oracle 18c Express Edition 中可能不允许基于函数的索引?文档检查是否定的。
  • @BobJarvis - 我刚刚测试了你的 dbfiddle,它也在 18c 上运行。
  • 好吧,那好吧..! :-)
  • 好吧,我收到错误 ORA-01743...
  • 由于发布的错误消息表明 NLS 相关函数正常,不妨尝试使用NLS_UPPER 而不是UPPER

标签: regex oracle indexing ddl oracle18c


【解决方案1】:

REGEXP_REPLACE 很可能会导致问题,请参阅Find out if a string contains only ASCII characters。您可以使用用户定义的函数绕过限制(感谢 Bob Jarvis)

CREATE OR REPLACE FUNCTION KEEP_ALNUM(strIn IN VARCHAR2)
  RETURN VARCHAR2
  DETERMINISTIC
AS
BEGIN
  RETURN UPPER(REGEXP_REPLACE(strIn, '[^[:alnum:]]', NULL, 1, 0));
END KEEP_ALNUM;
/

CREATE INDEX DEPTS_1 ON DEPTS(KEEP_ALNUM(DEPT_NUM));

只要确保函数有关键字DETERMINISTIC,你就可以定义如下甚至无用的函数并在其上创建函数索引

CREATE OR REPLACE FUNCTION SillyValue RETURN VARCHAR2 DETERMINISTIC
AS
BEGIN
  RETURN DBMS_RANDOM.STRING('p', 20);
END;
/

【讨论】:

  • 您是否错过了 OP 的这条评论? 不想创建函数怎么办,如何重写索引DDL?
  • 如果非字母数字字符的数量有限且已知,可以使用REPLACE
【解决方案2】:

有几种解决方法。

第一个是 hack。 您可能知道,当您创建 FBI 时,Oracle 会在其上创建隐藏列和索引。 此外,您甚至可以指定该列的名称而不是 FBI 表达式,Oracle 将使用索引。

set lines 70 pages 70
column column_name format a15
column data_type format a15

drop table my_table;

create table my_table(dept_num, dept_descr) as select rownum||'*', 'dummy' from dual connect by level <= 1e6; 

create index my_index
   on my_table(upper(regexp_replace(dept_num, '[^[:alnum:]]', null, 1, 0)));

select column_name, data_type from user_tab_cols where table_name = 'MY_TABLE';

explain plan for
select * from my_table where upper(regexp_replace(dept_num, '[^[:alnum:]]', null, 1, 0)) = '666';
select * from table(dbms_xplan.display(format => 'BASIC'));

explain plan for
select * from my_table where SYS_NC00003$ = '666';
select * from table(dbms_xplan.display(format => 'BASIC'));

输出

Table dropped.
Table created.
Index created.

COLUMN_NAME     DATA_TYPE      
--------------- ---------------
DEPT_NUM        VARCHAR2       
DEPT_DESCR      CHAR           
SYS_NC00003$    VARCHAR2       

3 rows selected.
Explain complete.

PLAN_TABLE_OUTPUT                                                     
----------------------------------------------------------------------
Plan hash value: 2234884270                                           

--------------------------------------------------------              
| Id  | Operation                           | Name     |              
--------------------------------------------------------              
|   0 | SELECT STATEMENT                    |          |              
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| MY_TABLE |              
|   2 |   INDEX RANGE SCAN                  | MY_INDEX |              
--------------------------------------------------------              

9 rows selected.
Explain complete.

PLAN_TABLE_OUTPUT                                                     
----------------------------------------------------------------------
Plan hash value: 2234884270                                           

--------------------------------------------------------              
| Id  | Operation                           | Name     |              
--------------------------------------------------------              
|   0 | SELECT STATEMENT                    |          |              
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| MY_TABLE |              
|   2 |   INDEX RANGE SCAN                  | MY_INDEX |              
--------------------------------------------------------              

9 rows selected.

因此,要模仿 FBI,您可以创建一个隐藏列并在其上创建一个索引。 这可以在 Oracle 11g 中使用dbms_stats.create_extended_stats 完成。

drop index my_index;

begin
   for i in (select dbms_stats.create_extended_stats
             (user, 'my_table', '(upper(regexp_replace("DEPT_NUM", ''[^[:alnum:]]'', null, 1, 0)))') as col_name
               from dual)
   loop
      execute immediate(utl_lms.format_message('alter table %s rename column "%s" to my_hidden_col','my_table', i.col_name));
   end loop;
end;
/

select column_name, data_type from user_tab_cols where table_name = 'MY_TABLE';

create index my_index on my_table(my_hidden_col);

explain plan for
select * from my_table where upper(regexp_replace(dept_num, '[^[:alnum:]]', null, 1, 0)) = '666';
select * from table(dbms_xplan.display(format => 'BASIC'));

explain plan for
select * from my_table where MY_HIDDEN_COL = '666';
select * from table(dbms_xplan.display(format => 'BASIC'));

输出

Index dropped.
PL/SQL procedure successfully completed.

COLUMN_NAME     DATA_TYPE      
--------------- ---------------
DEPT_NUM        VARCHAR2       
DEPT_DESCR      CHAR           
MY_HIDDEN_COL   VARCHAR2       

3 rows selected.
Index created.
Explain complete.

PLAN_TABLE_OUTPUT                                                     
----------------------------------------------------------------------
Plan hash value: 2234884270                                           

--------------------------------------------------------              
| Id  | Operation                           | Name     |              
--------------------------------------------------------              
|   0 | SELECT STATEMENT                    |          |              
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| MY_TABLE |              
|   2 |   INDEX RANGE SCAN                  | MY_INDEX |              
--------------------------------------------------------              

9 rows selected.
Explain complete.

PLAN_TABLE_OUTPUT                                                     
----------------------------------------------------------------------
Plan hash value: 2234884270                                           

--------------------------------------------------------              
| Id  | Operation                           | Name     |              
--------------------------------------------------------              
|   0 | SELECT STATEMENT                    |          |              
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| MY_TABLE |              
|   2 |   INDEX RANGE SCAN                  | MY_INDEX |              
--------------------------------------------------------              

9 rows selected.

从 Oracle 12c 开始,隐藏列已记录,因此变得更加简单。

alter table my_table add (my_hidden_col invisible as 
(upper(regexp_replace(dept_num, '[^[:alnum:]]', null, 1, 0))) virtual);
create index my_index on my_table(my_hidden_col);

另一种方法是在没有正则表达式的情况下实现相同的逻辑。

create index my_index on my_table(
translate(upper(dept_num, '_'||translate(dept_num, '_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', '_'), '_')));

但在这种情况下,您必须确保谓词中带有正则表达式的所有表达式都被替换为新表达式。

【讨论】:

    【解决方案3】:

    我发现最简单的解决方法是使用 NLS_UPPER 而不是 UPPER 创建索引:

    CREATE INDEX my_index ON my_table
    ( REGEXP_REPLACE (NLS_UPPER("DEPT_NUM"),'[^[:alnum:]]',NULL,1,0)))
    TABLESPACE my_tbspace
    PCTFREE    10
    INITRANS   2
    MAXTRANS   255
    STORAGE    (
                INITIAL          64K
                MINEXTENTS       1
                MAXEXTENTS       UNLIMITED
                PCTINCREASE      0
                BUFFER_POOL      DEFAULT
               );
    

    【讨论】:

      【解决方案4】:

      问题是regexp_replace 不是确定性的。更改 NLS 设置时出现问题:

      alter session set nls_language = english;
      
      with rws as (
        select 'STÜFF' v
        from   dual
      )
        select regexp_replace ( v, '[A-Z]+', '#' )
        from   rws;
      
      REGEXP_REPLACE(V,'[A-Z]+','#')   
      #Ü#  
      
      alter session set nls_language = german;
      
      with rws as (
        select 'STÜFF' v
        from   dual
      )
        select regexp_replace ( v, '[A-Z]+', '#' )
        from   rws;
      
      REGEXP_REPLACE(V,'[A-Z]+','#')   
      #     
      

      U-umlaut 位于英文字母表的末尾。但是在德语中的 U 之后。所以第一条语句不会替换它。第二个。

      在 Oracle 数据库 12.1 及更早版本中,regexp_replace错误地标记为确定性。 12.2 通过使其具有不确定性来解决此问题。

      仔细考虑是否有任何变通方法可以正确管理变音符号。

      MOS 注释 2592779.1 进一步讨论了这一点。

      【讨论】:

        猜你喜欢
        • 2019-07-10
        • 1970-01-01
        • 2021-01-14
        • 1970-01-01
        • 2018-08-03
        • 2018-08-14
        • 2021-12-29
        • 2012-09-25
        • 2020-10-30
        相关资源
        最近更新 更多