【问题标题】:SELECT if string contains column valueSELECT 如果字符串包含列值
【发布时间】:2019-09-01 11:23:46
【问题描述】:
Manufacturer
==========================
id            name      
--------------------------
1             Company Inc.
2             Google Test.
3             3M (UNITY) USA. INC.
4             CE EE

说,我有一个字符串 'Google Test. 1257 SCS RANDOM 31233DD' 我想查找表manufacturer 中的所有行,其中ht name 是给定字符串的一部分:

SELECT * FROM manufacturer
WHERE 'Google Test. 1257 SCS RANDOM 31233DD' ILIKE '%' || name || '%' 

正确返回:

id            name      
--------------------------
2             Google Test.

但是当我这样做时:

SELECT * FROM manufacturer
WHERE '3dad QTICE EEN ' ILIKE  '%' || name || '%'

它返回:

id            name      
--------------------------
4             CE EE

我不想要这样的部分匹配。 name 不能在单词中间匹配。我试过substring():

SELECT * from manufacturer
WHERE  SUBSTRING('Google Test. 1257 SCS RANDOM 31233DD' from name) != '';

但我明白了:

ERROR: invalid regular expression: quantifier operand invalid

不幸的是,我没有确切的规范,因为我正在从外部数据库查询这个。但据我所见,专栏是varchar(256)。所有值都大写并使用纯空格。全部以字符或数字开头,以数字、字符或特殊字符结尾。例如:'CLEVLAND DRILL (GREEN)'。值中有特殊字符,如,.()&/

我并不是真的在寻找效率,只要执行一次查询不超过 50 毫秒。

截至目前,大约有 10000 多个条目,但它可能会随着时间的推移而增长。

【问题讨论】:

  • 要得到准确的答案,您必须给出“单词”的准确定义。单词总是用纯空格分隔吗?开始/结束呢?短跑?其他标点符号?区分大小写?数字?词干?然后:桌子很大,性能重要吗?然后提供您的 Postgres 版本、表定义、avg。字符串长度,基数。
  • @ErwinBrandstetter 嗨,对不起。根据我的观察,我尽可能地添加了规范。

标签: sql regex postgresql pattern-matching


【解决方案1】:

LIKE 的一种方法是在开头和结尾添加空格:

SELECT *
FROM db
WHERE ' ' || '3dad QTICE EEN ' || ' ' ILIKE  '% ' || manufacturer || ' %'

如果您需要更复杂的匹配,那么您可能需要使用带有单词边界的正则表达式。

【讨论】:

    【解决方案2】:

    所有值都以字符或数字开头,以数字、字符或特殊字符结尾。 ... 值中有特殊字符,如,.()&/。

    我建议使用正则表达式匹配运算符~。仔细定义name中的边界和转义特殊字符:

    创建一次:

    CREATE OR REPLACE FUNCTION f_regexp_escape(text)
      RETURNS text AS
    $func$
    SELECT regexp_replace($1, '([!$()*+.:<=>?[\\\]^{|}-])', '\\\1', 'g')
    $func$  LANGUAGE sql IMMUTABLE;
    

    然后:

    SELECT * FROM manufacturer
    WHERE  '3dad QTICE EEN ' ~ ('\m' || f_regexp_escape(name) || '( |$)')
    

    怎么样?为什么?

    \m .. beginning of a word. 有效,因为:值以字符或数字开头
    ( |$) .. 空格或字符串结尾。我们需要这个,因为值:以数字、字符或特殊字符结尾

    manufacturer.name的内容是模式的核心。您想要其所有字符的字面意义,因此通过正确转义去除任何特殊含义。对于LIKE(少数特殊字符)以及正则表达式匹配运算符~(更多特殊字符)都是如此。经常被忽视并且是一个陷阱。这得到了你(以及边界的棘手定义)。阅读本文!

    然后按照演示使用函数f_regexp_escape()。一个name 喜欢:

    3M (UNITY) USA. INC.
    

    变成:

    3M \(UNITY\) USA\. INC\.
    

    在表manufacturer 中存储容易转义的模式可能很方便,可能作为附加列。也许像这样添加填充:

    \m3M \(UNITY\) USA\. INC\.( |$)
    

    或者像演示的那样动态生成模式。

    这样name 可以是单个单词或整个短语,并且可以以任何字符结尾。但是在另一边的“单词”中间,开始和结束永远不会匹配。

    Postgres 中有许多其他模式匹配工具:

    如果您的表很大,请考虑具有优化索引和 短语搜索 功能的 full text search 基础架构:

    【讨论】:

    • 我最初尝试过这种方法(使用\m、\M 以及\y)但发现它在表格列以非单词字符结束(或开始)的地方不起作用因为没有匹配的单词中断。见db-fiddle.com/f/wJZnDdo2kNxWSg4Lgbnedq/1
    • @Nick:是的,一个有效的观点。 OP 确实需要澄清规格。
    • 我在帖子中添加了规范。谢谢
    • @Av0id:我相应地适应了这些规范。
    【解决方案3】:

    要解决这个问题,您确实需要使用正则表达式,因为在字符串的任一侧添加空格将不会在行首或行尾匹配。通过使用正则表达式,我们也可以检查这种情况。例如:

    SELECT *
    FROM manufacturer
    WHERE 'Google Test. 1257 36700 SCS RANDOM WORD 31233DD' ~ ('(^| )' || name || '( |$)');
    

    输出:

    id  name
    2   Google Test.
    

    查询:

    SELECT *
    FROM manufacturer
    WHERE '3dad QTICE EEN ' ~ ('(^| )' || name || '( |$)');
    

    输出:

    There are no results to be displayed.
    

    查询:

    SELECT *
    FROM manufacturer
    WHERE 'CE EE ' ~ ('(^| )' || name || '( |$)');
    

    输出:

    id  name
    4   CE EE
    

    Demo on dbfiddle

    更新

    由于表中的name 值可以包含在正则表达式中具有特殊含义的字符,因此需要在将名称包含到正则表达式之前对其进行转义。您可以使用 REGEXP_REPLACE 来执行此操作,例如

    REGEXP_REPLACE(name, '([\\.+*?[^\]$(){}=!<>|:\-#])', '\\\1', 'g')
    

    所以你的查询应该是:

    SELECT *
    FROM manufacturer
    WHERE 'Google Test. 1257 36700 SCS RANDOM WORD 31233DD' ~ ('(^| )' || REGEXP_REPLACE(name, '([\\.+*?[^\]$(){}=!<>|:\-#])', '\\\1', 'g') || '( |$)');
    

    Updated demo

    【讨论】:

    • 所以我尝试使用正则表达式,我得到一个错误“错误:无效的正则表达式:量词操作数无效”。这可能是由于值中的特殊字符造成的吗?我在我的帖子中添加了规范。谢谢
    • @Av0id 我已经更新了我的答案以应对这种情况
    猜你喜欢
    • 2014-06-12
    • 1970-01-01
    • 2015-08-19
    • 1970-01-01
    • 2017-02-15
    • 2017-03-21
    • 1970-01-01
    • 1970-01-01
    • 2018-10-27
    相关资源
    最近更新 更多