【问题标题】:regex to replace all occurrences after a match正则表达式替换匹配后的所有匹配项
【发布时间】:2017-01-23 20:48:15
【问题描述】:

我希望删除以下语句中 ORDER BY 之后的 upper 关键字:

select upper(a.col1), b.col1
from a join
     b
     on a.q = b.q
order by upper(a.col1), upper(b.col1)

想要的输出:

select upper(a.col1), b.col1
from a join
     b
     on a.q = b.q
order by (a.col1), (b.col1)

我尝试过lookbehind 或简单的https://regex101.com/r/sfhDpt/1,但它只匹配一个实例。

上下文是 SQL 翻译 - 原始 SQL 在 MSSQL 或 Oracle 中有效,但在 DB2 或 H2 中无效,因此我需要一种简单的方法(读取、正则表达式)在需要时进行转换。

这甚至可以用一个正则表达式来完成吗?

【问题讨论】:

  • 只是为了我的理解,你为什么要只在 order-by 子句中删除上函数的使用?
  • 您的两个查询都应该在任何数据库中工作。您确定查询中没有group by 吗?或者,样本数据和期望的结果可能会更好地解释您真正想要做什么。
  • order by 中使用upper() 的最大出现次数是否合理?如果有,这个限制是多少?
  • @RavindraHV - 正如我所提到的,如果不是选择的一部分,DB2 和 H2 不允许使用 upper(或其他标量函数)。在我的示例中, b.col1 是问题所在。我知道 SQL 格式不正确,但这就是应用程序的编写方式。
  • @Bohemian - 我在遗留应用程序传递的 SQL 中最多追溯了 3 次出现

标签: java regex


【解决方案1】:

这样的正则表达式应该这样做(perl 语法):

s/(order by)((.*?)upper)*/$1$3/s

它利用了第三组的重复匹配。 test

编辑:不幸的是,以上不适用于超过 2 个upper 字符串,因为重复的匹配组仅捕获最后一次出现。要在perl 中捕获所有这些,可以写:

s/(order by)(?{$x=""})((.*?)(?{$x.=$3})upper)*/$1$x/s

捕获 $x 变量中的所有 $3 出现。这在正则表达式中使用perl 代码执行,并且在pcre 中不起作用(尽管callouts 可能在那里用于调用外部函数来执行类似的技巧)。 test

当然,重复也可以像这样手动展开:

s/(order by)((.*?)upper)?((.*?)upper)?((.*?)upper)?((.*?)upper)?/$1$3$5$7$9/s

但在这种情况下,((.*?)upper)? 必须重复(连同替换变量)多次,因为upper 可以在源字符串中出现。 test

【讨论】:

  • 不错,但它不适用于 order by 子句中超过 2 列。
  • @mik - 我无法让它工作。您可以将您的正则表达式保存在 regex101.com 或其他在线正则表达式测试器上吗?谢谢
  • @Yan 我刚刚添加了测试链接
  • 非常聪明的@mik!但我使用的是 Java,所以嵌入 perl 的正则表达式不起作用。
  • @Yan Java 也不支持标注,所以你需要坚持重复模式
【解决方案2】:

这将适用于 order by 中最多 3 个 upper(...) 调用:

(order by (?:(?!upper).)*)(?:upper(\(.*?\)))?((?:(?!upper).)*)(?:upper(\(.*?\)))?((?:(?!upper).)*)(?:upper(\(.*?\)))?((?:(?!upper).)*)

将匹配替换为:

$1$2$3$4$5$6$7

它也适用于不出现 upper() 的情况。

如果您发现需要更多捕获,请重复最后一对捕获组,并为替换添加另外两个反向引用。

live demo

【讨论】:

    【解决方案3】:

    只有一个正则表达式似乎很难。


    这里是php的解决方案:

    $sql = <<<EOD
    select upper(a.col1), b.col1
    from a join
         b
         on a.q = b.q
    order by upper(a.col1), upper(b.col1)
    EOD;
    
    $sql = preg_replace_callback('/(?=order by )(.*$)/', 
            function ($m) {
                return preg_replace('/\bupper\b/', ' ', $m[1]);
            },
            $sql
           ); 
    echo $sql,"\n";
    

    输出:

    select upper(a.col1), b.col1
    from a join
         b
         on a.q = b.q
    order by  (a.col1),  (b.col1)
    

    【讨论】:

      【解决方案4】:

      好的。根据您对我上面查询的回复,这就是我对问题的解释:

      有一个具有相当标准语法的 SQL 查询,因此它应该理想地在任何数据库中运行 - 特别是 mssql、oracle、db2 和 h2。

      但是在这种特殊情况下,查询具有某些在 mssql 和 oracle 中运行正常但在 db2 和 h2 中运行正常的语法,

      因此,您需要一个正则表达式来删除查询的违规部分 - 它位于两个目标数据库的 order-by 子句中,但它不起作用。

      方法一:

      第一点是,我认为解决此问题的更好方法是让应用程序了解数据库集并让它发送查询标识符,然后为特定数据库定制查询。

      这种方法还将确保由此导致的功能损失——按大写排序的结果可以在应用程序层中处理,因为它使用的数据库不支持它。

      最好使用准备好的语句(如果应用程序使用的语言支持它,或者如果您可以构建一个专门用于数据库访问的层,这显然是您正在做的 - 因为您似乎有一个要拦截的点传入的查询)。

      使用准备好的语句有助于 sql 注入和查询优化。

      方法二:

      如果上述方法完全不可行,并且您仍想使用 reg-exp,那么您可以将查询分成两部分: a) 在订购之前并包括订购 b) 订购后 并在 order-by 之后的部分中用空字符串替换所有出现的 'upper'。

      然后,您可以连接 order-by 之前(包括)之前的第一部分和替换 'upper' 关键字的最后部分,以获取所需的查询。

      您也可以尝试反转查询,然后搜索第一次出现的yb[\s]+redro,获取索引,提取子字符串,并将所有出现的)[\s]+reppu 替换为),然后将字符串反转回来并再次连接。

      【讨论】:

        猜你喜欢
        • 2016-11-04
        • 1970-01-01
        • 2016-05-04
        • 1970-01-01
        • 2012-05-16
        • 2021-10-12
        • 1970-01-01
        • 2022-11-29
        • 1970-01-01
        相关资源
        最近更新 更多