Oracle中通配符索引

bq3bfh9z  于 2023-04-20  发布在  Oracle
关注(0)|答案(2)|浏览(117)

我有一个列测试,具有以下值:AAABBBCCC、A1ABBBCCC、AAADDDCCC
我可以为列创建一个索引,以更快地找到确切的值,但用户可以输入类似AAAxxxCCC的内容。在这种情况下,x就像通配符,第一个和第三个值都应该被检索。或者AxABBBCCC,那么第一个和第二个应该被检索
我可以为列创建基于函数的索引吗?或者我如何在Oracle中实现这种行为?

3npbholx

3npbholx1#

正如已经写过的那样,数据的分布可能是不均匀的,这取决于数据列中的位置,并且索引访问可能根本不值得。
因此,最好将此估计留给数据库,数据库会对其进行统计。这意味着为数据列的每个位置构建索引:

-- Create Table with random data
create table my_table (idx number, pattern varchar2(4));
insert into my_table select level, dbms_random.string('U', 4) from dual connect by level <= 1000000;

-- Column 1 is unevenly distributed, 50% = A
update my_table set pattern= substr(pattern,1,1)||'A'||substr(pattern,3,2) where rownum <= 500000;

-- Column 2 is unevenly distributed, almost completely = B
update my_table set pattern= substr(pattern,2,1)||'B'||substr(pattern,3,2) where rownum <= 999000;

-- Build indexes
create index s1 on my_table (substr(pattern, 1, 1));
create index s2 on my_table (substr(pattern, 2, 1));
create index s3 on my_table (substr(pattern, 3, 1));
create index s4 on my_table (substr(pattern, 4, 1));

根据频率,执行计划为:

select max(idx)from my_table where substr(pattern, 1, 1) = 'A' and substr(pattern, 3, 1) = 'C';
----------------------------------------------------------------------------------------------
| Id  | Operation                              | Name     | Rows  | Bytes  | Cost | Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |          |     1 |      9 |  263 | 00:00:01 |
|   1 |   SORT AGGREGATE                       |          |     1 |      9 |      |          |
|   2 |    TABLE ACCESS BY INDEX ROWID BATCHED | MY_TABLE | 20326 | 182934 |  263 | 00:00:01 |
|   3 |     BITMAP CONVERSION TO ROWIDS        |          |       |        |      |          |
|   4 |      BITMAP AND                        |          |       |        |      |          |
|   5 |       BITMAP CONVERSION FROM ROWIDS    |          |       |        |      |          |
| * 6 |        INDEX RANGE SCAN                | S3       | 37358 |        |   14 | 00:00:01 |
|   7 |       BITMAP CONVERSION FROM ROWIDS    |          |       |        |      |          |
| * 8 |        INDEX RANGE SCAN                | S1       | 37358 |        |  203 | 00:00:01 |
----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 6 - access(SUBSTR("PATTERN",3,1)='C')
* 8 - access(SUBSTR("PATTERN",1,1)='A')

select max(idx) from my_table where substr(pattern, 2, 1) = 'B' and substr(pattern, 3, 1) = 'C';
----------------------------------------------------------------------------------------------
| Id  | Operation                              | Name     | Rows  | Bytes  | Cost | Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |          |     1 |      9 |  415 | 00:00:01 |
|   1 |   SORT AGGREGATE                       |          |     1 |      9 |      |          |
| * 2 |    TABLE ACCESS BY INDEX ROWID BATCHED | MY_TABLE | 37358 | 336222 |  415 | 00:00:01 |
| * 3 |     INDEX RANGE SCAN                   | S3       | 37358 |        |   14 | 00:00:01 |
----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 2 - filter(SUBSTR("PATTERN",2,1)='B')
* 3 - access(SUBSTR("PATTERN",3,1)='C')
jhkqcmku

jhkqcmku2#

如果通配符位于中间或末尾,并且通配符之前的前导字符匹配的记录数量相对较少,则应使用LIKE运算符。将“x”替换为_

WHERE column LIKE 'AAA___CCC'

如果你的用户不能使用_,并且坚持使用'x'通配符,那么在SQL中将其替换为_

WHERE column LIKE REPLACE('AAAxxxCCC','x','_')

这是我的主要答案。但是,如果你有大量以AAA开头的行,它不会很快。如果你遇到这个问题,那么根据常见的模式,有一些可能性。如果你总是看前三个字符和后三个字符,你可以将它们SUBSTR出来并索引该函数:

CREATE INDEX i_first3 on mytable(SUBSTR(mycolumn,1,3));
CREATE INDEX i_last3 on mytable(SUBSTR(mycolumn,-3,3));

SELECT * 
  FROM mytable 
 WHERE SUBSTR(mycolumn,1,3) = 'AAA'
   AND SUBSTR(mycolumn,-3,3) = 'CCC'

很明显,如果用户可以在任意位置使用通配符,而你又无法预测是哪个位置,那么这是行不通的。但是你可以做一些比较前卫的事情,比如为每个最后的字符数创建一个单独的基于函数的索引,然后根据用户提供的值,找到最后一个通配符之后的字符数,并在其上使用一个子字符串,在其余的字符上加上一个LIKE:

CREATE INDEX i_last1 on mytable(SUBSTR(mycolumn,-1),mycolumn);
CREATE INDEX i_last2 on mytable(SUBSTR(mycolumn,-2),mycolumn);
CREATE INDEX i_last3 on mytable(SUBSTR(mycolumn,-3),mycolumn);
CREATE INDEX i_last4 on mytable(SUBSTR(mycolumn,-4),mycolumn);
CREATE INDEX i_last5 on mytable(SUBSTR(mycolumn,-5),mycolumn);
CREATE INDEX i_last6 on mytable(SUBSTR(mycolumn,-6),mycolumn);
CREATE INDEX i_last7 on mytable(SUBSTR(mycolumn,-7),mycolumn);
...etc..

SELECT * 
  FROM mytable 
 WHERE mycolumn LIKE REPLACE('AxABBBCCC','x','_')
   AND SUBSTR(mycolumn,-7) = SUBSTR('AxABBBCCC',-7)

这将使用i_last7对最后7位数字进行查找(二进制搜索),然后扫描叶节点,对整个mycolumn列(索引中的第二列)应用LIKE运算符。它的性能比LIKE好得多,在通配符之前只有一个字符。
您还可以使用REVERSE和index that反转值,然后决定是否在正常值上使用LIKE(如果通配符在值的更深处),或者在值的反转上使用LIKE(如果通配符靠近前面)。

CREATE INDEX i_reverse on mytable(REVERSE(mycolumn));

SELECT * 
  FROM mytable 
 WHERE REVERSE(mycolumn) LIKE REVERSE(REPLACE('xxABBBCCC','x','_'))

对比

SELECT * 
  FROM mytable 
 WHERE mycolumn LIKE REPLACE('AAABBBCxx','x','_')

无论如何,你明白了-你的选择取决于你的查询模式和数据。希望没有任何基于函数的索引的简单LIKE能满足你的需求,你不需要所有这些额外的东西。你不会知道,直到你尝试它,看看它是如何执行的。
最后,希望不要太频繁,您可以向表添加一个并行属性,以便如果Oracle在字符串的开头看到通配符并进行全表扫描,至少它将与直接路径读取并行读取。

相关问题