从大约5个单词中按任意顺序匹配至少3个单词



我有一组单词:

"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

我想将它与Oracleregexp_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 dogs"。

编辑:为了解决标点符号方面的问题,可以进行一些小的修改。它并不完美,但应该匹配99%涉及标点符号的情况(但不匹配,例如!dog):

^((.*? )?(dog|car|house|work|cat)([ ,.!?]|$)){3}.*$

在这里试试

这是一个不使用正则表达式的解决方案,它将排除重复的单词,并且要匹配的单词可以作为集合中的绑定参数传入:

SQL Fiddle

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 |

忽略我在原始帖子下的评论中提出的问题,这里有一种简单的方法来解决问题,使用联接和聚合(使用HAVING条件)。请注意,输入中类似doghouse的单词将与doghouse等匹配。(请阅读我在原始帖子下的评论!)

在下面的查询中,输入短语和要匹配的单词都被硬编码在带因子的子查询中(WITH子句)。在一个严肃的环境中,两者都应该在基表中,或者作为输入变量提供,等等。

我展示了如何使用标准字符串比较运算符LIKE。这可以更改为REGEXP_LIKE,但这通常是不必要的(实际上是个坏主意)。但是,如果您需要区分"dog"one_answers"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;

关键是将要匹配的单词放入表或通用表表达式/子查询中。如果你愿意,你可以用REGEXP_LIKE()代替LIKE,尽管我认为这会更贵。如果您没有使用Oracle 11g或更高版本,或者您实际上不需要知道匹配了哪些单词,请跳过LISTAGG();如果您想要区分大小写的匹配,请跳过LOWER()

如果不需要匹配不同的单词。

(?:b(?:dog|car|house|work|cat)b.*?){3}

我不知道这在你的环境中是否有效。

编辑:我没想到还有另一个答案和这个差不多。

最新更新