【问题标题】:How to join tables on regex如何在正则表达式上加入表格
【发布时间】:2011-12-30 11:40:12
【问题描述】:

假设我有两个表 msg 用于消息,mnc 用于移动网络代码。 他们没有任何关系。但我想加入他们

SELECT msg.message,
    msg.src_addr,
    msg.dst_addr,
    mnc.name,
FROM "msg"
JOIN "mnc"
ON array_to_string(regexp_matches(msg.src_addr || '+' || msg.dst_addr, '38(...)'), '') = mnc.code

但查询失败并出现错误:

psql:marketing.sql:28: ERROR:  argument of JOIN/ON must not return a set
LINE 12: ON array_to_string(regexp_matches(msg.src_addr || '+' || msg...

有没有办法进行这种加入?还是我走错路了?

【问题讨论】:

  • 这可能效率极低(不使用索引,必须比较每一行)。
  • @TheifMaster,不一定,它可以使用基于函数的索引,例如如何提到upper here
  • @z4y4ts,正则表达式38(...) 是否有重要意义,或者它只是作为正则表达式的一个例子?据我所见,它只查找任何出现的 38 后跟任何 3 个其他字符。

标签: sql regex postgresql join pattern-matching


【解决方案1】:

一种非常奇怪的加入方式。一侧的每个匹配项都与另一张表中的每一行相结合...

regexp_matches() 对于您的目的来说可能是错误的函数。你想要一个简单的regular expression match (~)。实际上,LIKE operator更快

LIKE 大概是最快的

SELECT msg.message
     , msg.src_addr
     , msg.dst_addr
     , mnc.name
FROM   mnc
JOIN   msg ON msg.src_addr LIKE ('%38' || mnc.code || '%')
           OR msg.dst_addr LIKE ('%38' || mnc.code || '%')
WHERE  length(mnc.code) = 3;

此外,您只需要恰好 3 个字符的 mnc.code

与正则表达式匹配

可以用正则表达式编写相同的内容,但肯定会更慢。这是一个与您的原始版本接近的工作示例:

SELECT msg.message
     , msg.src_addr
     , msg.dst_addr
     , mnc.name
FROM   mnc
JOIN   msg ON (msg.src_addr || '+' || msg.dst_addr) ~ (38 || mnc.code)
           AND length(mnc.code) = 3;

这也要求msg.src_addrmsg.dst_addrNOT NULL

第二个查询演示了附加检查length(mnc.code) = 3 如何进入JOIN 条件或WHERE 子句。这里的效果一样。

regexp_matches()

可以使用regexp_matches()

SELECT msg.message
     , msg.src_addr
     , msg.dst_addr
     , mnc.name
FROM   mnc
JOIN   msg ON EXISTS (
    SELECT * 
    FROM   regexp_matches(msg.src_addr ||'+'|| msg.dst_addr, '38(...)', 'g') x(y)
    WHERE  y[1] = mnc.code
    );

但相比之下会慢。

说明:
您的 regexp_matches() 表达式只返回一个包含 first 匹配的所有捕获子字符串的数组。由于您只捕获一个子字符串(模式中的一对括号),您将专门获得 具有一个元素的数组

您可以通过附加的“全局”开关 'g' 获得所有匹配项 - 但在多行中。所以你需要一个子选择来测试它们全部(或聚合)。把它放在EXISTS - semi-join 中,你就会得到你想要的。

也许您可以通过对所有三个方面的性能测试进行报告? 为此使用EXPLAIN ANALYZE

【讨论】:

  • 嗨@erwin,谢谢你的可靠回答。以下是一些性能数据gist.github.com/1691021 正如您所说,使用 LIKE 查询是最快的,其次是 regexp 和 regexp_matches()。虽然没有什么意外,但我认为真实的数字可能很有趣。
  • @z4y4ts:感谢您的反馈。完全符合预期,但验证总是好的。 :)
【解决方案2】:

您的直接问题是regexp_matches 可能返回一行或多行。

【讨论】:

  • 实际上,如果没有'g' 开关,regexp_matches() 会准确返回 1 行,其中包含所有捕获的 first 匹配子字符串的数组。但是,OP 需要 'g' 开关才能获得 all 匹配的结果。
  • 可以返回多行,这对解析器来说很重要,因此会出现错误消息。
【解决方案3】:

尝试改用“substring”,它会提取给定正则表达式模式的子字符串。

SELECT msg.message,
    msg.src_addr,
    msg.dst_addr,
    mnc.name
FROM "msg"
JOIN "mnc"
ON substring(msg.src_addr || '+' || msg.dst_addr from '38(...)') = mnc.code

【讨论】:

  • 这将失败,因为substring() 只返回第一个匹配项,但其中一个附加匹配项可能是mnc.code。考虑:SELECT substring('38foo+38bar', '38(...)') = 'bar'。这可能是 OP 尝试regexp_matches() 的原因。
猜你喜欢
  • 2016-05-17
  • 1970-01-01
  • 2020-04-13
  • 1970-01-01
  • 2016-04-05
  • 2018-03-21
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
相关资源
最近更新 更多