如果字符串包含列值,则选择

时间:2019-04-10 22:18:19

标签: sql regex postgresql pattern-matching

Manufacturer
==========================
id            name      
--------------------------
1             Company Inc.
2             Google Test.
3             3M (UNITY) USA. INC.
4             CE EE

说,我有一个字符串'Google测试。 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(绿色)” 。值中包含特殊字符,例如,.()&/

我并不是真正在寻找效率,只要它不花费50ms就能完成一次查询。

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

3 个答案:

答案 0 :(得分:2)

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

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

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

答案 1 :(得分:2)

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

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

答案 2 :(得分: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 基础架构>功能: