SQL LIKE语句多条件贪婪匹配算法

在CMS开发中,经常会有类似这样的需求:

提问——回答模式,最经典的例子就是百度提问。

提问者提出问题,由其他人回答,其他人可以是用户,也可以是服务商。

在这个模式中,如何充分利用历史数据是最关键的技术。很多时候,由于客户不擅长使用搜索功能,一上来就提问,而这些问题往往早已经有近乎完美的答案,但没有充分利用。这样一来,不仅加大了劳动量,又增加了数据冗余。

如果在提问的时候能充分调动历史数据,提交问题之前先看看历史问题能不能解决客户疑问,解决了,最好不过,解决不了,再提交。百度提问就是采用的这种方案:



模式固然好,可怎么实现就有些困难了,毕竟这是百度作为搜索引擎的看家本领。

从上图可以看出“优快云网站如何注册用户”这句话被拆成了N个词,然后分开去数据库中匹配,为什么?因为直接去匹配“优快云网站如何注册用户”这句话,汉语博大精深,稍微变动一下:“如何在优快云网站注册用户”,意思完全一样,但直接匹配绝对匹配不到!

因此,我们需要把一句话拆分成词组,这个在网上有现成的组件,比如“庖丁解牛”等,它们大多数是免费开源的。拆成词组之后,应该还要有一个关键词筛选词库,用这个词库确定出有效的词组,比如上图中,“优快云”、“注册”、“用户”是有效的,而“网站”显然没有匹配,因为它在这句话中没有实际意义。

有点跑题了,拆词、选词不是本文的重点,但却是本文的前提。拿到关键词之后,怎么去数据库中匹配呢?

大家都知道T-SQL中的LIKE语句,通过类似LIKE “%abc%”这样的语法,可以进行模糊匹配,但是它仅仅能进行一次模糊匹配。举个例子:

假如我们确定了a,b,c三个关键词,要查找的记录当然是匹配的越多越好,于是可以这样写:LIKE “%a%b%c%”,这样匹配出的就是包含a,b,c三个关键词的记录,但是如果根本没有包含这三个关键词的记录,最多只有包含两个的,甚至是只包含一个,那么如何写LIKE语句呢?这样LIKE “%a%b%”?这样LIKE “%a%c%”?这样LIKE “%b%c%”?这样LIKE “%a%”?这样LIKE “%b%”?这样LIKE “%c%”?

显然,需要判断的情况太多,简单的LIKE语句已经无法满足需求。需要注意的是,千万不要试图选出范范的记录,返回到程序中去处理,在程序中处理虽然简单,但是范范的记录,在一个中型系统中,往往能达到千万级别,这么大的数据量,从数据库返回到程序,无疑会给服务器造成相当大的压力。

经过探索,本小菜总结了一个比较简单的方法,暂且称为“LIKE语句多条件贪婪匹配算法”。

算法思想:先用LIKE选出每一组符合一个条件的记录,只选择表的主键。然后把这些记录合并在一起,通过主键分组、统计数量,数量最多的,也就是匹配最多的,最后根据数量降序排序,越靠上的记录,匹配的越多。选出匹配的多的记录主键字段,再根据主键去表中选出内容即可。

为了方便大家使用,已经把算法封装成存储过程(直接把下边代码在查询分析器中执行即可)。

存储过程(函数)如下:

GO
CREATE function Get_StrArrayLength
(
 @str varchar(1024),  --要分割的字符串
 @split varchar(10)  --分隔符号
)
returns int
as
 begin
  declare @location int
  declare @start int
  declare @length int
  set @str=ltrim(rtrim(@str))
  set @location=charindex(@split,@str)
  set @length=1
   while @location<>0
     begin
      set @start=@location+1
      set @location=charindex(@split,@str,@start)
      set @length=@length+1
     end
   return @length
 end
 GO
 CREATE function Get_StrArrayStrOfIndex
(
 @str varchar(1024),  --要分割的字符串
 @split varchar(10),  --分隔符号
 @index int --取第几个元素
)
returns varchar(1024)
as
begin
 declare @location int
 declare @start int
 declare @next int
 declare @seed int
 set @str=ltrim(rtrim(@str))
 set @start=1
 set @next=1
 set @seed=len(@split)
 set @location=charindex(@split,@str)
 while @location<>0 and @index>@next
   begin
    set @start=@location+@seed
    set @location=charindex(@split,@str,@start)
    set @next=@next+1
   end
 if @location =0 select @location =len(@str)+1
 
--这儿存在两种情况:1、字符串不存在分隔符号 2、字符串中存在分隔符号,跳出while循环后,@location为0,那默认为字符串后边有一个分隔符号。
 return substring(@str,@start,@location-@start)
end
GO
CREATE PROCEDURE proc_Common_SuperLike
	--要查询的表的主键字段名称
	@primaryKeyName varchar(999),
	--要查询的表名
	@talbeName varchar(999),
	--要查询的表的字段名称,即内容所在的字段
	@contentFieldName varchar(999),
	--查询记录的个数(TOP *),匹配的个数越多,排名越靠前
	@selectNumber varchar(999),
	--匹配字符分隔标记
	@splitString varchar(999),
	--匹配字符组合字符串
	@words varchar(999)
	
AS
	declare @sqlFirst varchar(999)
	declare @sqlCenter varchar(999)
	declare @sqlLast varchar(999)
BEGIN
	set @sqlCenter=''
	declare @next int  
	set @next=1
	while @next<=dbo.Get_StrArrayLength(@words,@splitString)
	begin
		--构造sql查询条件(中间部分)
		set @sqlCenter = @sqlCenter+'SELECT '+@primaryKeyName+' FROM '+@talbeName+' WHERE '+@contentFieldName+' like ''%'+dbo.Get_StrArrayStrOfIndex(@words,@splitString,@next)+'%'' UNION ALL '
		set @next=@next+1
	end
	--处理sql语句中间部分,去除最后无用语句
	set @sqlCenter=left(@sqlCenter,(len(@sqlCenter)-10))
	--构造sql语句开头部分
	set @sqlFirst='SELECT TOP '+@selectNumber+' '+@primaryKeyName+',COUNT(*) AS showCout FROM ('
	--构造sql语句结尾部分
	set @sqlLast=') AS t_Temp GROUP BY '+@primaryKeyName+' ORDER BY showCout DESC'
	--拼接出完整sql语句,并执行
	execute(@sqlFirst+@sqlCenter+@sqlLast)
END

调用示例:

executeproc_Common_SuperLike 'id','t_test','content','20','|','i|o|c'

id表的主键字段名称。

t_test表名。

content匹配内容字段名称。

20选出20个记录(从顶至下匹配度越来越低)。

|关键字的分隔符号。

i|o|c一共有i,o,c三个关键字,通过|分隔。

注意这个存储过程选出来的是匹配度高的记录的主键,还需要根据主键去表中查一下,查询出需要的内容(问题名称)。

有问题欢迎和我交流,原创算法,可用于任何场合,注明出处即可。
<think>我们正在讨论FlinkSQL中实现多个字符串的模糊匹配。在FlinkSQL中,模糊匹配通常使用`LIKE`操作符或正则表达式函数(如`REGEXP`)来实现。但是,当需要同时匹配多个字符串模式时,我们需要一些额外的技巧。###方法1:使用多个`LIKE`条件结合`OR`最简单的方法是使用多个`LIKE`条件并用`OR`连接。例如,假设我们有一个表`myTable`,其中有一个字段`myField`,我们想匹配包含"abc"或"def"或"ghi"的行,我们可以这样写:```sqlSELECT*FROMmyTableWHEREmyFieldLIKE'%abc%'ORmyFieldLIKE'%def%'ORmyFieldLIKE'%ghi%';```这种方法简单直观,但是当模式很多时,SQL语句会变得冗长,且可能影响性能(因为每个`LIKE`都需要扫描整个字符串)。###方法2:使用正则表达式FlinkSQL支持使用`REGEXP`函数进行正则表达式匹配。我们可以将多个模式组合成一个正则表达式,使用`|`表示或。例如,上面的多个`LIKE`条件可以改写为:```sqlSELECT*FROMmyTableWHEREREGEXP(myField,'.*(abc|def|ghi).*');```这里,`.*`表示任意字符(包括空字符)任意次数,`(abc|def|ghi)`表示匹配"abc"或"def"或"ghi"。使用正则表达式可以使SQL语句更简洁,但要注意正则表达式的性能问题,尤其是当模式很多或者模式很复杂时。###方法3:使用`SIMILARTO`(如果支持)FlinkSQL的`SIMILARTO`操作符也可以用于模式匹配,它使用类似正则表达式的语法。但是,请注意`SIMILARTO`的语法与标准正则表达式略有不同,且不是所有的Flink版本都支持(在Flink1.13及以后版本中,更推荐使用`REGEXP`函数)。###方法4:使用用户自定义函数(UDF)如果上述方法不能满足需求(例如,需要更复杂的匹配逻辑,或者匹配的模式非常多),我们可以考虑使用用户自定义函数(UDF)。在Java或Scala中编写一个UDF,然后在FlinkSQL中调用它。例如,我们可以编写一个UDF,它接受一个字符串和一个模式列表,然后检查字符串中是否包含列表中的任意一个模式。步骤:1.编写UDF(以Java为例):```javaimportorg.apache.flink.table.functions.ScalarFunction;publicclassMultiPatternMatchextendsScalarFunction{publicbooleaneval(Stringstr,String...patterns){if(str==null){returnfalse;}for(Stringpattern:patterns){if(str.contains(pattern)){returntrue;}}returnfalse;}}```2.在FlinkSQL中注册并使用:```sqlCREATEFUNCTIONmultiPatternMatchAS'com.example.MultiPatternMatch';SELECT*FROMmyTableWHEREmultiPatternMatch(myField,'abc','def','ghi');```注意:上面的UDF使用了可变参数,但是FlinkSQL在调用UDF时,可变参数可能需要以数组形式传递。在FlinkSQL中,我们可以这样调用:```sqlSELECT*FROMmyTableWHEREmultiPatternMatch(myField,ARRAY['abc','def','ghi']);```因此,我们需要调整UDF以接受一个数组参数(或者使用Flink的`Array`类型)。另外,也可以重载`eval`方法接受一个字符串数组。###性能考虑-当使用多个`LIKE`条件时,如果模式很多,SQL语句会很长,且每个条件都会独立计算,性能可能较差。-正则表达式在模式数量不多时性能较好,但如果模式很多,正则表达式可能会变得复杂且匹配效率下降。-UDF方法更灵活,我们可以优化匹配算法(例如,将模式构建成一个Trie树,然后进行一次扫描匹配多个模式),特别是当模式数量很大时,这种方法可能更高效。###结论根据具体场景选择合适的方法:-如果只有少量模式,使用多个`LIKE`或一个正则表达式即可。-如果有大量模式,建议使用UDF,并在UDF内部实现高效的匹配算法(例如,使用Aho-Corasick算法等)。§§相关问题§§1.FlinkSQL中如何实现正则表达式匹配多个模式?2.在FlinkSQL中,使用UDF进行字符串匹配有哪些优势?3.如何优化FlinkSQL多个LIKE条件的性能?4.在FlinkSQL中,除了LIKE和REGEXP,还有哪些字符串匹配函数?</think>在 Flink SQL 中实现多个字符串的模糊匹配,可以通过以下方法实现: ### 方法 1:使用 `LIKE` 和 `OR` 组合 ```sql SELECT * FROM my_table WHERE field LIKE '%pattern1%' OR field LIKE '%pattern2%' OR field LIKE '%pattern3%'; ``` **说明**: - 每个 `LIKE` 子句匹配一个模糊模式(`%` 表示任意字符) - 用 `OR` 连接多个条件实现"或"逻辑 - **优点**:语法简单直观 - **缺点**:模式较多时代码冗长 ### 方法 2:使用正则表达式 `REGEXP` ```sql SELECT * FROM my_table WHERE field REGEXP '.*(pattern1|pattern2|pattern3).*'; ``` **说明**: - `REGEXP` 支持 POSIX 正则表达式 - `|` 表示逻辑"或",匹配任意一个模式 - `.*` 匹配任意前缀/后缀字符 - **优点**:模式多时更简洁 - **注意**:需要确保字段类型是 `STRING` ### 方法 3:使用用户自定义函数(UDF) 对于复杂需求(如动态模式列表),可创建 Java/Scala UDF: ```java public class MultiPatternMatch extends ScalarFunction { public boolean eval(String s, String... patterns) { for (String p : patterns) { if (s.contains(p)) return true; // 或使用正则引擎 } return false; } } ``` **SQL 调用**: ```sql -- 注册 UDF CREATE FUNCTION multiMatch AS 'com.example.MultiPatternMatch'; -- 使用 UDF 查询 SELECT * FROM my_table WHERE multiMatch(field, 'pat1', 'pat2', 'pat3'); ``` ### 性能优化建议 1. **预过滤**:先使用简单条件缩小数据集 ```sql WHERE LENGTH(field) > 5 AND multiMatch(...) ``` 2. **模式排序**:将高概率匹配的模式放前面 3. **缓存模式**:在 UDF 中预编译正则表达式 4. **避免全表扫描**:结合窗口函数或时间范围过滤 ### 实际案例 匹配日志中的错误关键词: ```sql SELECT log_time, message FROM server_logs WHERE message REGEXP '.*(timeout|connection refused|deadlock).*' AND log_time > CURRENT_TIMESTAMP - INTERVAL '5' MINUTE; ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值