有效地检查文本列中是否存在文本

时间:2009-01-29 12:05:24

标签: sql database oracle

我有一张约2,000,000行的表格。我需要查询其中一列以检索字符串作为值的一部分存在的行。

当我运行查询时,我将知道字符串的位置,但不是在手之前。因此,采用子字符串的视图不是一种选择。

据我所知,我有三个选择

  1. 使用类似'%%'
  2. 使用instr
  3. 使用substr
  4. 如果我对dba很好,我可以选择创建基于函数的索引。

    目前所有查询都需要大约两秒钟。有没有人有经验,哪些选项最有效,或者有其他选择? select将用于每隔几秒删除一次,通常会选择10行。

    使用更多信息进行修改

    当我们使用表来存储具有任意键和值的对象时,问题就出现了。这些对象来自我们系统之外,因此我们控制它们的范围有限,因此文本列类似于'key1 = abc,key2 = def,keyn = ghi'我知道这是非常非规范化但我们不知道是什么密钥将(在某种程度上)是一种存储和检索值的可靠方法。在我们搜索已编制索引的整个列时,检索行的速度相当快。但是,如果我们想要使用key2 = def。

    检索行,则性能不佳

    我们可以创建一个包含最常用键列的表,但我想知道是否有办法通过现有设置来提高性能。

8 个答案:

答案 0 :(得分:2)

您可以使用Tom Kyte's runstats package来比较不同实现的性能 - 在循环中每次运行1000次。例如,我只是将LIKE与SUBSTR进行了比较,它说LIKE速度更快,大约占SUBSTR的80%。

请注意,“col LIKE'%xxx%'”与“SUBSTR(col,5,3)='xxx'”不同。等效的LIKE将是:

col LIKE '____xxx%'

使用一个'_'表示每个前导字符都被忽略。

我认为无论你采用哪种方式,结果都是相似的 - 它总是涉及一个完整的表(或者可能是全索引)扫描。只有在创建索引时知道子字符串的偏移量时,基于函数的索引才有用。

当你说“选择将每隔几秒用于删除”时,我很担心。这确实暗示了某处的设计缺陷,但不知道要求很难说。

<强>更新

如果您的列值类似于'key1 = abc,key2 = def,keyn = ghi',那么您可以考虑添加另一个这样的表:

 create table key_values
    ( main_table_id references main_table
    , key_value varchar2(50)
    , primary key (fk_col, key_value)
    );

 create index key_values_idx on key_values (key_value);

将键值拆分并将其存储在此表中,如下所示:

main_table_id key_value
123           key1=abc
123           key2=def
123           key3=ghi

(例如,这可以在main_table上的AFTER INSERT触发器中完成)

然后你的删除可能是:

delete main_table
where id in (select main_table_id from key_values
             where key_value = 'key2=def');

答案 1 :(得分:2)

在Oracle 10中:

CREATE TABLE test (tst_test VARCHAR2(200));

CREATE INDEX ix_re_1 ON test(REGEXP_REPLACE(REGEXP_SUBSTR(tst_test, 'KEY1=[^,]*'), 'KEY1=([^,]*)', '\1'))

SELECT  *
FROM    TEST
WHERE   REGEXP_REPLACE(REGEXP_SUBSTR(TST_TEST, 'KEY1=[^,]*'), 'KEY1=([^,]*)', '\1') = 'TEST'

这将使用新选择的索引。

您需要的数据索引与数据中的KEY一样多。

INDEX的存在当然会影响效果,但它很少依赖于REGEXP

SQL> CREATE INDEX ix_test ON test (tst_test)
  2  /
Index created
Executed in 0,016 seconds

SQL> INSERT
  2  INTO   test (tst_test)
  3  SELECT 'KEY1=' || level || ';KEY2=' || (level + 10000)
  4  FROM   dual
  5  CONNECT BY
  6     LEVEL <= 1000000
  7  /
1000000 rows inserted
Executed in 47,781 seconds

SQL> TRUNCATE TABLE test
  2  /
Table truncated
Executed in 2,546 seconds

SQL> DROP INDEX ix_test
  2  /
Index dropped
Executed in 0 seconds

SQL> CREATE INDEX ix_re_1 ON test(REGEXP_REPLACE(REGEXP_SUBSTR(tst_test, 'KEY1=[^,]*'), 'KEY1=([^,]*)', '\1'))
  2  /
Index created
Executed in 0,015 seconds

 SQL> INSERT
      2  INTO   test (tst_test)
      3  SELECT 'KEY1=' || level || ';KEY2=' || (level + 10000)
      4  FROM   dual
      5  CONNECT BY
      6     LEVEL <= 1000000
      7  /
1000000 rows inserted
Executed in 53,375 seconds

正如您所看到的,在我的速度不是很快的机器(Core2 43001 Gb RAM)上,您可以每秒将20000条记录插入索引字段,此速率几乎不依赖正在使用的INDEX类型:普通或基于函数。

答案 2 :(得分:1)

我建议重新考虑你的逻辑。

不是查找字符串存在的位置,而是检查其长度是否为> 0并且不是字符串可能会更快。

您可以使用oracle中的TRANSLATE函数将所有非字符串字符转换为空值,然后检查结果是否为空。

答案 3 :(得分:1)

您能提供更多信息吗?

您是在查询字符串列的任意子字符串,还是在列中的字符串存储上有一些语法可以进行一些预处理以最大限度地减少重复工作?

您是否已对三个选项进行了任何时序测试,以确定它们对您查询的数据的相对表现?

答案 4 :(得分:1)

单独回答对表格设计的评论。

你不能至少拥有一个KEY / VALUE结构,所以不是存储在一个列中,'key1 = abc,key2 = def,keyn = ghi'你会有一个像

这样的子表
KEY     VALUE
key1    abc
key2    def
key3    ghi

然后你可以在键和值上创建一个索引,并且你的查询更简单(因为我认为你实际上是在寻找一个给定键值的完全匹配)。

有些人可能会评论说这是一个糟糕的设计,但我认为它比你现在拥有的要好。

答案 5 :(得分:0)

如果你总是要寻找相同的子串,那么使用INSTR和基于函数的索引对我来说是有意义的。如果您有一小组常量子串,您也可以这样做,为每个子串创建一个FBI。

Quassnoi的REGEXP理念看起来也很有希望。我还没有在Oracle中使用正则表达式。

我认为Oracle Text将是另一种方式。关于here

的信息

答案 6 :(得分:0)

不确定改进现有的设置内容,但Lucene(全文搜索库;移植到许多平台)可以提供帮助。将索引与数据库同步存在额外负担,但如果您在某种编程语言中有任何类似于服务层的内容,这将变得很容易。

答案 7 :(得分:0)

类似于Anton Gogolev的回应,Oracle确实合并了一个文档搜索引擎here

还有可扩展的索引,因此您可以构建自己的索引结构,记录为here

如你所知,这是一个非常糟糕的数据结构,我认为你很难达到每隔几秒删除一些东西的目的。根据这些数据的输入方式,我会考虑在加载时正确构建数据,至少在“parent_id”,“key_name”,“key_value”行的范围内。