REGEXP_SUBSTR与INSTR和SUBSTR的性能和可读性

时间:2016-12-15 04:22:44

标签: sql regex oracle

从我的另一个问题Using REGEXP_SUBSTR with Strings Qualifier,我正在尝试决定哪种方法更好用。

结果数据集应仅按正确顺序显示分隔符PLE#ALL之前的字符串。 包中已有的当前查询是这样的(DDL和DML位于帖子的底部):

SELECT  DATA1
      , DECODE(SIGN(0 - instr(DATA1, 'PLE')), -1, SUBSTR(DATA1, 1, instr(DATA1, 'PLE') - 1)) GET_DATA_TILL_FIRST_PLE
      , DECODE(SIGN(0 - instr(DATA1, '#')), -1, SUBSTR(DATA1, 1, instr(DATA1, '#') - 1)) GET_DATA_TILL_FIRST_NUM_SIGN
      , DECODE(SIGN(0 - instr(DATA1, 'ALL')), -1, SUBSTR(DATA1, 1, instr(DATA1, 'ALL') - 1)) GET_DATA_TILL_FIRST_ALL
      , NVL(DECODE(SIGN(0 - instr(DATA1, 'PLE')), -1, SUBSTR(DATA1, 1, instr(DATA1, 'PLE') - 1), 0,
        DECODE(SIGN(0 - instr(DATA1, '#')), -1, SUBSTR(DATA1, 1, instr(DATA1, '#') - 1), 0,
        DECODE(SIGN(0 - instr(DATA1, 'ALL')), -1, SUBSTR(DATA1, 1, instr(DATA1, 'ALL') - 1), DATA1), DATA1), DATA1), DATA1) PUT_THEM_ALL_TOGETHER    
FROM    table_x;    

以下数据集中的哪些结果:

DATA1                   | GET_DATA_TILL_FIRST_PLE | GET_DATA_TILL_FIRST_#_SIGN  | GET_DATA_TILL_FIRST_ALL    |  PUT_THEM_ALL_TOGETHER
----------------------- | ----------------------- | --------------------------- | -------------------------- |  ----------------------
STRING_EXAMPLE          | STRING_EXAM             |                             |                            |  STRING_EXAM
TREE_OF_APPLES          | TREE_OF_AP              |                             |                            |  TREE_OF_AP
FIRST_EXAMPLE           | FIRST_EXAM              |                             |                            |  FIRST_EXAM
IMPLEMENTATION          | IM                      |                             |                            |  IM
PARIS                   |                         |                             |                            |  PARIS
PLEONASM                |                         |                             |                            |  PLEONASM
XXXX 1                  |                         |                             |                            |  XXXX 1 
XXXX YYYYYY 2 FFFFFFFFF |                         |                             |                            |  XXXX YYYYYY 2 FFFFFFFFF
XXXX YYYYYY 5FFFFFFFFF  |                         |                             |                            |  XXXX YYYYYY 5FFFFFFFFF
OPOPOPOPO #09090 APPLE  | OPOPOPOPO #09090 AP     | OPOPOPOPO                   | OPOPOPOPO #                |  OPOPOPOPO #09090 AP
OPOPOPOPO BALL#         |                         | OPOPOPOPO BALL              | OPOPOPOPO B                |  OPOPOPOPO BALL
BALL IS #LIFE           |                         | BALL IS                     | B                          |  BALL IS     

PS。我只需要列PUT_THEM_ALL_TOGETHER,但我还包括其他列以及添加上下文。

我发现查询位有点令人困惑且难以阅读所以我尝试使用REGEXP_SUBSTR并且根据@vkp的建议,我想出了以下查询 结果与上面的数据集相同。

SELECT  DATA1
  , REGEXP_SUBSTR(DATA1, '(.+?)PLE',1,1,null,1) GET_DATA_TILL_FIRST_PLE
  , REGEXP_SUBSTR(DATA1, '(.+?)#',1,1,null,1) GET_DATA_TILL_FIRST_#_SIGN
  , REGEXP_SUBSTR(DATA1, '(.+?)ALL',1,1,null,1) GET_DATA_TILL_FIRST_ALL
  , COALESCE(REGEXP_SUBSTR(DATA1, '(.+?)PLE',1,1,null,1),
             REGEXP_SUBSTR(DATA1, '(.+?)#',1,1,null,1),
             REGEXP_SUBSTR(DATA1, '(.+?)ALL',1,1,null,1),
             DATA1) PUT_THEM_ALL_TOGETHER
FROM    table_x;     

然而,从@ MathGuy的答案来看,似乎INSTRSUBSTR效率更高。 我在一定程度上测试了这个,这就是我得到的:

使用INSTRSUBSTR

SET TIMING ON;    
BEGIN
    UPDATE  table_x
    SET     DATA2 = NVL(DECODE(SIGN(0 - instr(DATA1, 'PLE')), -1, SUBSTR(DATA1, 1, instr(DATA1, 'PLE') - 1), 0,
                    DECODE(SIGN(0 - instr(DATA1, '#')), -1, SUBSTR(DATA1, 1, instr(DATA1, '#') - 1), 0,
                    DECODE(SIGN(0 - instr(DATA1, 'ALL')), -1, SUBSTR(DATA1, 1, instr(DATA1, 'ALL') - 1), DATA1), DATA1), DATA1), DATA1);    
    ROLLBACK;        
END;
/            
  

PL / SQL程序已成功完成。
  经过时间:00:00:00.234

使用REGEXP_SUBSTR

SET TIMING ON;  
BEGIN    
    UPDATE  table_x
    SET     DATA2 = COALESCE(REGEXP_SUBSTR(DATA1, '(.+?)PLE',1,1,null,1)
                            ,REGEXP_SUBSTR(DATA1, '(.+?)#',1,1,null,1)
                            ,REGEXP_SUBSTR(DATA1, '(.+?)ALL',1,1,null,1)
                            ,DATA1);
    ROLLBACK;        
END;
/    
  

PL / SQL程序已成功完成。
  经过时间:00:00:00.236

虽然这是一个非常有限的测试数据,但它表明INSTRSUBSTR的组合比REGEXP_SUBSTR快一点。 为了便于阅读,使用REGEXP_SUBSTR代替INSTRSUBSTR是否可以忽略不计?

DML和DDL:

create table table_x 
(
    data1 varchar2(100)    
   ,data2 varchar2(100)
);

INSERT INTO table_x (DATA1) VALUES ('STRING_EXAMPLE');
INSERT INTO table_x (DATA1) VALUES ('TREE_OF_APPLES');
INSERT INTO table_x (DATA1) VALUES ('FIRST_EXAMPLE');  
INSERT INTO table_x (DATA1) VALUES ('IMPLEMENTATION');   
INSERT INTO table_x (DATA1) VALUES ('PARIS');            
INSERT INTO table_x (DATA1) VALUES ('PLEONASM');        

INSERT INTO table_x (DATA1) VALUES ('XXXX 1');   
INSERT INTO table_x (DATA1) VALUES ('XXXX YYYYYY 2 FFFFFFFFF'); 
INSERT INTO table_x (DATA1) VALUES ('XXXX YYYYYY 5FFFFFFFFF'); 

INSERT INTO table_x (DATA1) VALUES ('OPOPOPOPO #09090 APPLE'); 
INSERT INTO table_x (DATA1) VALUES ('OPOPOPOPO BALL#'); 
INSERT INTO table_x (DATA1) VALUES ('BALL IS #LIFE');   

谢谢。

4 个答案:

答案 0 :(得分:6)

我已经发布了一个答案,展示了如何使用INSTRSUBSTR正确解决此问题。

在这个“答案”中,我解决了另一个问题 - 哪种解决方案更有效率。我将在下面解释测试,但这里是底线:REGEXP解决方案比INSTR/SUBSTR解决方案长40倍

设置:我创建了一个包含150万个随机字符串的表(所有字符串长度均为8个字符,全部为大写字母)。然后我修改了10%的字符串以添加子字符串'PLE',另外10%添加'#',另外10%添加'ALL'。我通过在位置mod(rownum, 9)处分割原始字符串(即0到8之间的数字)并在该位置连接'PLE''#''ALL'来完成此操作。当然,这不是获得我们所需的测试数据的最有效或最优雅的方式,但这是无关紧要的 - 关键是创建测试数据并在我们的测试中使用它。

所以:我们现在有一个只有一列data1的表,其中包含一些150万行的随机字符串。每个10%都有子串PLE#ALL

测试包括在原始帖子中创建新字符串data2。我没有将结果插回表中;无论计算data2的方式如何,插入在表格中的时间应该相同。

相反,我将主查询放在外部查询中,计算结果data2值的长度总和。这样我保证优化器不能采用快捷方式:必须生成所有data2值,必须测量它们的长度,然后将它们相加。

下面是创建基表所需的语句,我称之为table_z,然后是我运行的查询。

create table table_z as
select dbms_random.string('U', 8) as data1 from dual
connect by level <= 1500000;

update table_z 
set data1 = case
when rownum between      1 and 150000 then substr(data1, 1, mod(rownum, 9)) 
                               || 'PLE' || substr(data1, mod(rownum, 9) + 1)
when rownum between 150001 and 300000 then substr(data1, 1, mod(rownum, 9)) 
                               || '#'   || substr(data1, mod(rownum, 9) + 1)
when rownum between 300001 and 450000 then substr(data1, 1, mod(rownum, 9)) 
                               || 'ALL' || substr(data1, mod(rownum, 9) + 1)
          end
where rownum <= 450000;

commit;

INSTR/SUBSTR解决方案

select sum(length(data2))
from (
select data1, 
       case 
         when instr(data1, 'PLE', 2) > 0 then substr(data1, 1, instr(data1, 'PLE', 2) - 1)
         when instr(data1, '#'  , 2) > 0 then substr(data1, 1, instr(data1, '#'  , 2) - 1)
         when instr(data1, 'ALL', 2) > 0 then substr(data1, 1, instr(data1, 'ALL', 2) - 1)
         else data1 end
       as data2
from   table_z
);

SUM(LENGTH(DATA2))
------------------
          10713352

1 row selected.

Elapsed: 00:00:00.73

REGEXP解决方案

select sum(length(data2))
from (
select data1, 
       COALESCE(REGEXP_SUBSTR(DATA1, '(.+?)PLE',1,1,null,1)
                            ,REGEXP_SUBSTR(DATA1, '(.+?)#',1,1,null,1)
                            ,REGEXP_SUBSTR(DATA1, '(.+?)ALL',1,1,null,1)
                            ,DATA1)
       as data2
from   table_z
);

SUM(LENGTH(DATA2))
------------------
          10713352

1 row selected.

Elapsed: 00:00:30.75

在任何人提出这些建议之前:我多次重复这两个问题;第一个解决方案始终在0.75到0.80秒之间运行,第二个查询在30到35秒内运行。慢了40多倍。 (因此,编译器/优化器花费时间来编译查询不是问题;它实际上是执行时间。)此外,这与从基表读取150万个值无关 - 这在两种测试都比处理时间少得多。在任何情况下,我首先运行INSTR/SUBSTR查询,因此如果有任何缓存,REGEXP查询将是受益的。

编辑:我刚刚发现了提议的REGEXP解决方案中的一个低效率。如果我们将搜索模式锚定到字符串的开头(例如'^(.+?)PLE',请注意^锚点),REGEXP查询的运行时间从30秒下降到10秒。显然,Oracle实现不够聪明,不能识别这种等价,并尝试从第二个字符,第三个字符等进行搜索。仍然执行时间几乎是后者的15倍; 15&lt;但这仍然是一个非常大的差异。

答案 1 :(得分:2)

使用INSTR的方法和使用REGEXP_SUBSTR的方法都在进行非常相似的字符串操作。

让我们比较使用基本字符串函数的查询和使用正则表达式的查询中的第二个选择项。以下是您使用INSTR的第一个查询中的该术语:

DECODE(SIGN(0 - instr(DATA1, 'PLE')), -1, SUBSTR(DATA1, 1, instr(DATA1, 'PLE') - 1))

假设DATA1列匹配,则需要两次调用INSTR。在不知道实现细节的情况下,我会假设每次调用INSTR都会涉及在DATA1中沿着字符串从左向右传递,直到找到'PLE'或者直到INSTR结束。到达字符串。在这两种情况下,DATA1只需要在REGEXP_SUBSTR(DATA1, '(.+?)PLE',1,1,null,1) 中对字符串进行一次传递。

这是相同的选择术语,但使用正则表达式:

REGEXP_SUBSTR

我不太熟悉您在调用DATA1时使用的所有参数,但从我可以看到的这只是对'PLE'列中的所有内容进行了一次香草味的正则表达式捕获直到DATA1第一次出现。同样,这也只需要一次通过HttpWebRequest.AllowAutoRedirect字符串,并且你的正则表达式中没有任何东西,例如lookaheads / lookbehinds或其他任何需要多于一遍的东西。

所有选择看起来与此非常相似,所以我认为使用基本Oracle字符串函数与正则表达式相比的性能非常相似,并且您自己的性能测试似乎证实了这一点,至少对于您的特定数据集。

在实践中,正在进行非常相似的字符串操作,只是在高级别上它们的措辞有点不同。就个人而言,我可能会选择正则表达式解决方案,因为它更具可读性,也许更容易维护。

答案 2 :(得分:1)

使用INSTRSUBSTR的解决方案可以重写,因此与基于regexp的解决方案一样简单(或更容易)阅读和维护。关于性能,您可以测试两个版本并做出决定。注意:此解决方案比使用INSTRSUBSTR的尝试更快,因为在CASE表达式中CASE的连续分支仅在第一个TRUE之前进行评估分支 - 不评估剩余的分支。 (而且因为你的解决方案有大量不必要的函数调用 - nvldecodesign,所以不需要这些函数。)

select data1, 
       case when instr(data1, 'PLE') > 0 then substr(data1, 1, instr(data1, 'PLE') - 1)
            when instr(data1, '#')   > 0 then substr(data1, 1, instr(data1, '#'  ) - 1)
            when instr(data1, 'ALL') > 0 then substr(data1, 1, instr(data1, 'ALL') - 1)
            else data1 end
       as data2
from   table_x;

根据您的UPDATE声明进行调整。

编辑:我错过了如果初始结果是NULL,那么表达式应返回原始字符串。这意味着我们不应在字符串的开头搜索'PLE''#''ALL'。但是,如果可能在输入字符串的开头找到“搜索模式”,然后在字符串中稍后再找到 - 例如'ALL IN ALL'。因此,我们需要在INSTR的第二个字符处开始data1搜索,而不是在第一个字符处。我们使用INSTR的第三个(可选)参数。

更新了解决方案:

select data1, 
       case 
          when instr(data1, 'PLE', 2) > 0 then substr(data1, 1, instr(data1, 'PLE', 2) - 1)
          when instr(data1, '#'  , 2) > 0 then substr(data1, 1, instr(data1, '#'  , 2) - 1)
          when instr(data1, 'ALL', 2) > 0 then substr(data1, 1, instr(data1, 'ALL', 2) - 1)
          else data1 end
       as data2
from   table_x;

答案 3 :(得分:1)

您是否测量过您遇到某种性能问题?你在看什么尺寸的数据集?你希望达到什么样的加速?如果您正在寻找,例如,在每个查询中加速10%,那么234ms和236ms之间的差异将没有任何区别,并且您将浪费您作为程序员的时间。

您的SQL查询是否占用了运行代码的大量时间?如果您的应用是80%的网络转移和20%的服务器端,并且您设法将查询速度提高10%,那么您的应用只会增加2%。花些时间减少通过网络获取数据所需的时间会更好。

另一种思考方式:想象一下,你发现在现实生活中,你每个月花的钱比你每月花的钱多。你开始思考&#34;好吧,也许如果我去星巴克,我会得到一小杯咖啡而不是一大杯咖啡,我会节省50美分!&#34;,但那你怎么样?有3000美元的抵押贷款,可以节省60%的实得工资吗?

如果您不知道应用程序的速度有多慢,那么您可能会在错误的事情上浪费时间。