从大约 5 个单词中以任意顺序匹配至少 3 个单词
Match at least 3 words in any order from some 5 words
我有一组词:
"dog", "car", "house", "work", "cat"
我需要能够在文本中匹配至少 3 个,例如:
"I always let my cat and dog at the animal nursery when I go to work by car"
这里我要匹配正则表达式,因为它至少匹配 3 个词(这里是 4 个词):
"cat", "dog", "car" and "work"
编辑 1
我想将它与 Oracle 的 regexp_like
函数一起使用
编辑 2
我也需要它来处理连续的单词
由于Oracle的regexp_like
不支持非捕获组和词边界,可以使用下面的表达式:
^((.*? )?(dog|car|house|work|cat)( |$)){3}.*$
或者,更大但可以说更清洁的解决方案是:
^(.*? )?(dog|car|house|work|cat) .*?(dog|car|house|work|cat) .*?(dog|car|house|work|cat)( .*)?$
注意:它们将匹配多次使用的同一个词,例如"dog dog dog".
编辑: 为了解决标点符号问题,可以进行一些小的修改。它并不完美,但应该匹配 99% 的标点符号情况(但不会匹配 !dog
):
^((.*? )?(dog|car|house|work|cat)([ ,.!?]|$)){3}.*$
如果不需要匹配不同的词
(?:\b(?:dog|car|house|work|cat)\b.*?){3}
我不知道这是否适用于您的环境。
编辑:我没看到有另一个答案几乎像这个。
这是一个不使用正则表达式的解决方案,将排除重复的词,并且可以将要匹配的词作为集合中的绑定参数传入:
Oracle 11g R2 架构设置:
创建一个集合类型来存储单词列表:
CREATE TYPE StringList IS TABLE OF VARCHAR2(50)
/
创建一个 PL/SQL 函数以将定界字符串拆分到集合中:
CREATE OR REPLACE FUNCTION split_String(
i_str IN VARCHAR2,
i_delim IN VARCHAR2 DEFAULT ','
) RETURN StringList DETERMINISTIC
AS
p_result StringList := StringList();
p_start NUMBER(5) := 1;
p_end NUMBER(5);
c_len CONSTANT NUMBER(5) := LENGTH( i_str );
c_ld CONSTANT NUMBER(5) := LENGTH( i_delim );
BEGIN
IF c_len > 0 THEN
p_end := INSTR( i_str, i_delim, p_start );
WHILE p_end > 0 LOOP
p_result.EXTEND;
p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, p_end - p_start );
p_start := p_end + c_ld;
p_end := INSTR( i_str, i_delim, p_start );
END LOOP;
IF p_start <= c_len + 1 THEN
p_result.EXTEND;
p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, c_len - p_start + 1 );
END IF;
END IF;
RETURN p_result;
END;
/
创建一些测试数据:
CREATE TABLE test_data ( value ) AS
SELECT 'I always let my cat and dog at the animal nursery when I go to work by car' FROM DUAL UNION ALL
SELECT 'dog dog foo bar dog' FROM DUAL
/
查询 1:
SELECT *
FROM test_data
WHERE CARDINALITY(
split_string( value, ' ' ) -- Split the string into a collection
MULTISET INTERSECT -- Intersect it with the input words
StringList( 'dog', 'car', 'house', 'work', 'cat' )
) >= 3 -- Check that the size of the intersection
-- is at least 3 items.
| VALUE |
|----------------------------------------------------------------------------|
| I always let my cat and dog at the animal nursery when I go to work by car |
忽略我在原始 post 下的评论中提出的问题,这是解决问题的一种简单方法,使用连接和聚合(使用 HAVING
条件)。请注意,输入中的 doghouse
之类的词将同时匹配 dog
和 house
等。(请阅读我在原文 post 下的评论!)
在下面的查询中,输入短语和要匹配的词都硬编码在因子子查询(WITH
子句)中。在严肃的环境中,两者都应该在基表中,或者作为输入变量等提供。
我展示了如何使用标准字符串比较运算符 LIKE
。这可以更改为 REGEXP_LIKE
,但这通常是不需要的(而且确实是个坏主意)。但是如果需要区分'dog'和'dogs'(以及'dogwood'),或者需要不区分大小写的比较等,可以使用REGEXP_LIKE
。这个解决方案的要点是你不需要担心匹配三个不同的词;如果您知道如何匹配一个(是否需要全字匹配、大小写是否重要等),那么您也可以轻松地在相同规则下匹配三个字。
with
inputs ( input_phrase ) as (
select
'I always let my cat and dog at the animal nursery when I go to work by car'
from dual
),
words ( word_to_match) as (
select 'dog' from dual union all
select 'car' from dual union all
select 'house' from dual union all
select 'work' from dual union all
select 'cat' from dual
)
select input_phrase
from inputs inner join words
on input_phrase like '%' || word_to_match || '%'
group by input_phrase
having count(*) >= 3
;
INPUT_PHRASE
--------------------------------------------------------------------------
I always let my cat and dog at the animal nursery when I go to work by car
以下解决方案将排除重复匹配项,不使用正则表达式(如果您愿意也可以),并且不使用 PL/SQL.
WITH match_list ( match_word ) AS (
SELECT 'dog' AS match_word FROM dual
UNION ALL
SELECT 'work' FROM dual
UNION ALL
SELECT 'car' FROM dual
UNION ALL
SELECT 'house' FROM dual
UNION ALL
SELECT 'cat' FROM dual
)
SELECT phrase, COUNT(*) AS unique_match_cnt, SUM(match_cnt) AS total_match_cnt
, LISTAGG(match_word, ',') WITHIN GROUP ( ORDER BY match_word ) AS unique_matches
FROM (
SELECT pt.phrase, ml.match_word, COUNT(*) AS match_cnt
FROM phrase_table pt INNER JOIN match_list ml
ON ' ' || LOWER(pt.phrase) || ' ' LIKE '%' || ml.match_word || '%'
GROUP BY pt.phrase, ml.match_word
) GROUP BY phrase
HAVING COUNT(*) >= 3;
关键是把你要匹配的词放到table或普通的tableexpression/subquery中。如果您愿意,可以使用 REGEXP_LIKE()
代替 LIKE
,但我认为那样会更昂贵。如果您使用的不是 Oracle 11g 或更高版本,或者您实际上不需要知道匹配了哪些单词,请跳过 LISTAGG()
,如果您需要区分大小写的匹配,请跳过 LOWER()
。
我有一组词:
"dog", "car", "house", "work", "cat"
我需要能够在文本中匹配至少 3 个,例如:
"I always let my cat and dog at the animal nursery when I go to work by car"
这里我要匹配正则表达式,因为它至少匹配 3 个词(这里是 4 个词):
"cat", "dog", "car" and "work"
编辑 1
我想将它与 Oracle 的 regexp_like
函数一起使用
编辑 2
我也需要它来处理连续的单词
由于Oracle的regexp_like
不支持非捕获组和词边界,可以使用下面的表达式:
^((.*? )?(dog|car|house|work|cat)( |$)){3}.*$
或者,更大但可以说更清洁的解决方案是:
^(.*? )?(dog|car|house|work|cat) .*?(dog|car|house|work|cat) .*?(dog|car|house|work|cat)( .*)?$
注意:它们将匹配多次使用的同一个词,例如"dog dog dog".
编辑: 为了解决标点符号问题,可以进行一些小的修改。它并不完美,但应该匹配 99% 的标点符号情况(但不会匹配 !dog
):
^((.*? )?(dog|car|house|work|cat)([ ,.!?]|$)){3}.*$
如果不需要匹配不同的词
(?:\b(?:dog|car|house|work|cat)\b.*?){3}
我不知道这是否适用于您的环境。
编辑:我没看到有另一个答案几乎像这个。
这是一个不使用正则表达式的解决方案,将排除重复的词,并且可以将要匹配的词作为集合中的绑定参数传入:
Oracle 11g R2 架构设置:
创建一个集合类型来存储单词列表:
CREATE TYPE StringList IS TABLE OF VARCHAR2(50)
/
创建一个 PL/SQL 函数以将定界字符串拆分到集合中:
CREATE OR REPLACE FUNCTION split_String(
i_str IN VARCHAR2,
i_delim IN VARCHAR2 DEFAULT ','
) RETURN StringList DETERMINISTIC
AS
p_result StringList := StringList();
p_start NUMBER(5) := 1;
p_end NUMBER(5);
c_len CONSTANT NUMBER(5) := LENGTH( i_str );
c_ld CONSTANT NUMBER(5) := LENGTH( i_delim );
BEGIN
IF c_len > 0 THEN
p_end := INSTR( i_str, i_delim, p_start );
WHILE p_end > 0 LOOP
p_result.EXTEND;
p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, p_end - p_start );
p_start := p_end + c_ld;
p_end := INSTR( i_str, i_delim, p_start );
END LOOP;
IF p_start <= c_len + 1 THEN
p_result.EXTEND;
p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, c_len - p_start + 1 );
END IF;
END IF;
RETURN p_result;
END;
/
创建一些测试数据:
CREATE TABLE test_data ( value ) AS
SELECT 'I always let my cat and dog at the animal nursery when I go to work by car' FROM DUAL UNION ALL
SELECT 'dog dog foo bar dog' FROM DUAL
/
查询 1:
SELECT *
FROM test_data
WHERE CARDINALITY(
split_string( value, ' ' ) -- Split the string into a collection
MULTISET INTERSECT -- Intersect it with the input words
StringList( 'dog', 'car', 'house', 'work', 'cat' )
) >= 3 -- Check that the size of the intersection
-- is at least 3 items.
| VALUE |
|----------------------------------------------------------------------------|
| I always let my cat and dog at the animal nursery when I go to work by car |
忽略我在原始 post 下的评论中提出的问题,这是解决问题的一种简单方法,使用连接和聚合(使用 HAVING
条件)。请注意,输入中的 doghouse
之类的词将同时匹配 dog
和 house
等。(请阅读我在原文 post 下的评论!)
在下面的查询中,输入短语和要匹配的词都硬编码在因子子查询(WITH
子句)中。在严肃的环境中,两者都应该在基表中,或者作为输入变量等提供。
我展示了如何使用标准字符串比较运算符 LIKE
。这可以更改为 REGEXP_LIKE
,但这通常是不需要的(而且确实是个坏主意)。但是如果需要区分'dog'和'dogs'(以及'dogwood'),或者需要不区分大小写的比较等,可以使用REGEXP_LIKE
。这个解决方案的要点是你不需要担心匹配三个不同的词;如果您知道如何匹配一个(是否需要全字匹配、大小写是否重要等),那么您也可以轻松地在相同规则下匹配三个字。
with
inputs ( input_phrase ) as (
select
'I always let my cat and dog at the animal nursery when I go to work by car'
from dual
),
words ( word_to_match) as (
select 'dog' from dual union all
select 'car' from dual union all
select 'house' from dual union all
select 'work' from dual union all
select 'cat' from dual
)
select input_phrase
from inputs inner join words
on input_phrase like '%' || word_to_match || '%'
group by input_phrase
having count(*) >= 3
;
INPUT_PHRASE
--------------------------------------------------------------------------
I always let my cat and dog at the animal nursery when I go to work by car
以下解决方案将排除重复匹配项,不使用正则表达式(如果您愿意也可以),并且不使用 PL/SQL.
WITH match_list ( match_word ) AS (
SELECT 'dog' AS match_word FROM dual
UNION ALL
SELECT 'work' FROM dual
UNION ALL
SELECT 'car' FROM dual
UNION ALL
SELECT 'house' FROM dual
UNION ALL
SELECT 'cat' FROM dual
)
SELECT phrase, COUNT(*) AS unique_match_cnt, SUM(match_cnt) AS total_match_cnt
, LISTAGG(match_word, ',') WITHIN GROUP ( ORDER BY match_word ) AS unique_matches
FROM (
SELECT pt.phrase, ml.match_word, COUNT(*) AS match_cnt
FROM phrase_table pt INNER JOIN match_list ml
ON ' ' || LOWER(pt.phrase) || ' ' LIKE '%' || ml.match_word || '%'
GROUP BY pt.phrase, ml.match_word
) GROUP BY phrase
HAVING COUNT(*) >= 3;
关键是把你要匹配的词放到table或普通的tableexpression/subquery中。如果您愿意,可以使用 REGEXP_LIKE()
代替 LIKE
,但我认为那样会更昂贵。如果您使用的不是 Oracle 11g 或更高版本,或者您实际上不需要知道匹配了哪些单词,请跳过 LISTAGG()
,如果您需要区分大小写的匹配,请跳过 LOWER()
。