【问题标题】:Select values between multiple parentheses in a string via SQL query通过 SQL 查询在字符串中的多个括号之间选择值
【发布时间】:2016-01-26 23:54:18
【问题描述】:

我正在使用 Oracle 数据库,并且正在尝试选择括号之间的值。这是我的表格,其中有 ID 和 Roads 作为列。我有这个数据库的只读权限,所以我只能使用SELECT:

ID   Roads
--   -----
1    #Chaussée de Waterloo (Ixelles)#
2    #Rue Reper-Vreven (Bruxelles)#
3    #Rue des Fraises (Anderlecht)#
4    #Chaussée de Roodebeek (Woluwe-Saint-Lambert)#
5    #Square Jean Absil (Etterbeek)#Avenue Hansen-Soulie (Etterbeek)#Avenue Le Marinel (Etterbeek)#

基本上,从Roads 列中,我只想保留括号之间的值。由于最终查询中包含其他表,因此我想要一个不同的选择。所需的输出是:

 ID    Roads
------------------
  1      Ixelles  
  2      Bruxelles 
  3      Anderlecht
  4      Woluwe-Saint-Lambert
  5      Etterbeek, Etterbeek, Etterbeek

我尝试了以下查询,当只有一组括号时它可以正常工作,但是当有多个括号时它不起作用(例如 ID 5),因为它只返回第一组中的值括号:

select distinct substr(roads, instr(roads,'(') + 1, instr(roads,')') - instr(roads,'(') - 1) as roads 
from table

有人知道我哪里出错了吗?

【问题讨论】:

  • 错误 #1:没有规范化您的数据集。
  • @Marc B 具体是什么意思?
  • 数据规范化是关系数据库的基础
  • @lad2025 感谢您的建议,但它只返回第一组括号之间的值...

标签: sql oracle substring


【解决方案1】:

一种解决方案 - 部分基于 przemo_pl 引用的解决方案:

SELECT SUBSTR( with_parentheses,2,length(with_parentheses)-2) between_parenthesis
FROM 
  (select REGEXP_SUBSTR(dat, '\([^()]*\)+',1,level) AS with_parentheses 
     from (select '#Square Jean Absil (Etterbeek)#Avenue Hansen-Soulie (Anderlecht)#Avenue Le Marinel (Ixelles)#' as dat from dual )
   connect by LEVEL <= ( LENGTH(dat) - LENGTH(REPLACE(dat, '(', '')))    
  )

返回:

between_parenthesis
---------------------
"Etterbeek"
"Anderlecht"
"Ixelles"

如果你想把它重新组装成一行,那就会增加另一个皱纹。

【讨论】:

    【解决方案2】:

    请参阅我的另一篇文章以获得更简单的答案。我把它留在这里,因为它仍然是一种有趣的方法,也是关于过度思考解决方案如何变得过于复杂的教训,有时人们必须退后一步,以不同的方式解决问题。 :-)

    好的,你需要遍历行和行中的括号:

    with tbl(ID, Roads) as (
      select 1, '#Chaussée de Waterloo (Ixelles)#' from dual
      union
      select 2, '#Rue Reper-Vreven (Bruxelles)#' from dual
      union
      select 3, '#Rue des Fraises (Anderlecht)#' from dual
      union
      select 4, '#Chaussée de Roodebeek (Woluwe-Saint-Lambert)#' from dual
      union
      select 5, '#Square Jean Absil (Etterbeek)#Avenue Hansen-Soulie (Etterbeek)#Avenue Le Marinel (Etterbeek)#' from dual
        )
        SELECT ID, Roads,
               COLUMN_VALUE AS match_nbr,
              REGEXP_SUBSTR( Roads ,'\(([^\)]*)\)', 1, COLUMN_VALUE, NULL, 1 ) AS match_value
       FROM   tbl,
              TABLE(
                CAST(
                  MULTISET(
                    SELECT LEVEL
                    FROM   DUAL
                    CONNECT BY LEVEL <= REGEXP_COUNT( Roads ,'\(' )
                  ) AS SYS.ODCINUMBERLIST
                )
              );
    

    结果:

    请参阅here 以获取类似的帖子,该帖子链接到另一个提供更多信息的帖子。我并不声称完全理解它。 :-)

    编辑:更新为使用 listagg() 获取单行道路列表:

    SQL> with tbl(ID, Roads) as (
         select 1, '#Chaussée de Waterloo (Ixelles)#' from dual
         union
         select 2, '#Rue Reper-Vreven (Bruxelles)#' from dual
         union
         select 3, '#Rue des Fraises (Anderlecht)#' from dual
         union
         select 4, '#Chaussée de Roodebeek (Woluwe-Saint-Lambert)#' from dual
         union
         select 5, '#Square Jean Absil (Etterbeek)#Avenue Hansen-Soulie (Etterbeek)#Avenue Le Marinel (Etterbeek)#' from dual
           )
           select id,
           listagg(match_value, ', ') within group (order by id) road_list
           from (
           SELECT ID, Roads, COLUMN_VALUE AS match_nbr,
                 REGEXP_SUBSTR( Roads ,'\(([^\)]*)\)', 1, COLUMN_VALUE, NULL, 1 ) AS match_value
          FROM   tbl,
                 TABLE(
                   CAST(
                     MULTISET(
                       SELECT LEVEL
                       FROM   DUAL
                       CONNECT BY LEVEL <= REGEXP_COUNT( Roads ,'\(' )
                     ) AS SYS.ODCINUMBERLIST
                   )
                 )
           )
           group by id
           order by id
           ;
    
            ID ROAD_LIST
    ---------- --------------------------------------------------
             1 Ixelles
             2 Bruxelles
             3 Anderlecht
             4 Woluwe-Saint-Lambert
             5 Etterbeek, Etterbeek, Etterbeek
    
    SQL>
    

    【讨论】:

    • 谢谢,这确实有效,但在我的具体情况下,我还需要使用 select distinct,因为我表中的另一列需要唯一值...
    • 原始规范中没有提及。
    • 你的意思是像Etterbeek这样的街道不应该重复吗?
    • 很抱歉,这个查询实际上只是更大查询的一小部分。事实上,我只想要不同的价值观。 Etterbeek 应该重复,但如果有意义的话,应该在同一行上
    • @philippe 好的,我使用 listagg() 来完成。查看更新后的帖子。
    【解决方案3】:

    我将其添加为一个新答案,因为它与我之前的答案大不相同,这是一个经典的例子,过度复杂的思维随着每次迭代而变得更糟,因此仍然是一个很好的例子!哈哈,有时候你只需要感受一下,当你变得太复杂时,不要害怕重新开始!

    好的,看看这个。我回到第一格,研究了字符串的模式。无论是一条道路还是多条道路(实际上,这种在一列中包含多个值的设计违反了基本的数据建模原则,应该重新设计,但谁没有处理过我们无法控制的糟糕设计?),每条道路都被英镑的迹象。我的想法是使用正则表达式遍历字符串,用括号内的内容替换右括号中的井号模式。当然,这会在最后留下一个英镑符号,但我们稍后会清理它。请注意,如果发现 REGEXP_REPLACE 将替换所有出现的模式,因此默认情况下会遍历所有道路,并且比大量嵌套的 INSTR()、SUBSTR() 更容易维护:

    SQL> with tbl(ID, Roads) as (
         select 1, '#Chaussée de Waterloo (Ixelles)#' from dual
         union
         select 2, '#Rue Reper-Vreven (Bruxelles)#' from dual
         union
         select 3, '#Rue des Fraises (Anderlecht)#' from dual
         union
         select 4, '#Chaussée de Roodebeek (Woluwe-Saint-Lambert)#' from dual
         union
         select 5, '#Square Jean Absil (Etterbeek)#Avenue Hansen-Soulie (Etterbeek)#Avenue Le Marinel (Etterbeek)#' from dual
       )
       select ID, rtrim(regexp_replace(Roads, '#.+?\((.+)\)', '\1, '), ', #') Roads
       from tbl;
    
            ID ROADS
    ---------- ----------------------------------------
             1 Ixelles
             2 Bruxelles
             3 Anderlecht
             4 Woluwe-Saint-Lambert
             5 Etterbeek, Etterbeek, Etterbeek
    
    SQL>
    

    正则表达式模式解释:

    #    Look for a literal pound sign
    .    followed by any character
    +    followed by one or more of the previous character (any character)
    ?    make the previous character optional (one or more any characters)
    \(   a literal left paren
    (    start remembered group 1
    .    any character
    +    one or more "any" characters
    )    end remembered group 1
    \)   followed by a literal closing right paren
    

    如果找到上面的字符串,用“replace-with”字符串替换:

    \1       The first remembered group which is what is inside the parentheses.
    ,<space> followed by a comma and a space
    

    然后它有点快速和肮脏,但只需使用RTRIM 删除尾随的逗号-空格-磅符号。哇啦!呼。

    【讨论】:

    • 是的,这确实有效,非常感谢,确实有时候回到更简单的事情上会更好。
    • 如果这对您来说是解决方案,请标记它以向未来的搜索者展示什么是有效的。谢谢,这很有趣。
    • 我做过,但由于我是这个网站的新手并且没有足够的声誉,它不会公开出现。不过再次感谢您的帮助。
    • 再次感谢@gary_w 的回答,但我真的不明白正则表达式的语法
    • 正则表达式本身就是一种语言。所有风格都有一个基本核心,其中一些功能取决于您使用的版本或操作系统/工具。由于这是与 Oracle 相关的开始,但一旦你学会了它,它就非常强大。也做一些搜索,那里有很多学习正则表达式的资源。祝你好运! docs.oracle.com/cd/B28359_01/appdev.111/b28424/adfns_regexp.htm
    猜你喜欢
    • 1970-01-01
    • 2022-08-03
    • 2017-02-26
    • 2013-08-24
    • 1970-01-01
    • 1970-01-01
    • 2017-12-27
    • 1970-01-01
    • 2015-07-22
    相关资源
    最近更新 更多