4

迁移到 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 中的已知错误吗?如果不再支持此基于函数的索引,那么编写此函数的另一种方法是什么?

4

4 回答 4

5

问题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 进一步讨论了这一点。

于 2020-04-10T16:10:36.517 回答
2

我发现最简单的解决方法是使用 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
           );
于 2019-09-13T14:57:39.827 回答
1

有几种解决方法。

第一个是黑客。您可能知道,当您创建 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', '_'), '_')));

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

于 2019-09-13T12:02:35.113 回答
1

最有可能是REGEXP_REPLACE导致问题的原因,请参阅找出字符串是否仅包含 ASCII 字符。您可以使用用户定义的函数绕过限制(感谢 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;
/
于 2019-09-13T06:13:55.623 回答