在 WHERE 子句中使用 REPLACE 检查拼写排列 - MS SQL
Posted
技术标签:
【中文标题】在 WHERE 子句中使用 REPLACE 检查拼写排列 - MS SQL【英文标题】:using REPLACE in WHERE clause to check spelling permutations - MS SQL 【发布时间】:2011-09-09 07:41:46 【问题描述】:我有一张这样的桌子:
| id | lastname | firstname |
| 1 | doe | john |
| 2 | oman | donald |
| 3 | o'neill | james |
| 4 | onackers | sharon |
基本上,用户将按姓氏的第一个字母进行搜索。
我希望能够从数据库中返回包含和不包含标点符号的结果。例如,当用户搜索:on
我想同时返回: 奥尼尔,onackers
我希望有人能够搜索“o, on, oneill, o neill, etc”来得到 o'neill。
因此,执行此操作的最佳方法似乎是采用 lastname 列值并在 WHERE 子句中使用 OR 搜索它的两个排列。一种是用 SQL 中的 _ 替换任何特殊字符,另一种是所有非字母字符(包括空格)都消失了。
我想我可以在 SQL 替换中使用下划线来保持一个空格可用。
我在使用 WHERE 子句时遇到了一点问题。如果可能的话,我宁愿用一个简单的 REPLACE 来做这件事,而不是创建一个正则表达式函数。如果那是不行的,我明白:
@last_name (this is the nvarchar input)
SELECT id, lastname, firstname
FROM people
WHERE ((REPLACE(people.lastname, '[^A-Za-z]', '_') like @last_name + '%')
OR (REPLACE(people.lastnname,'[^A-Za-z ]', '') like @last_name + '%'))
ORDER BY lastname
我很确定替换部分必须在 LIKE 的另一侧。我搞砸了结构,但需要一些帮助。
我正在使用 MSSQL Server 2005。
非常感谢您。
更新
看来我有两个选择:
-
使用 CLR 创建正则表达式函数(如果我说错了,请原谅,我是新手)
在表上创建额外的列或使用清理后的姓氏创建一个新的“fuzzyTable”。
数据库每晚更新一次。实际上,我已经开始了新的表格方法,因为这是我最初要做的。但是,我开始认为将“模糊”列添加到主表中,然后在每晚更新时将调整后的姓氏添加到新/更新的行中会更聪明。
堆栈溢出:哪种方法更好?我可以在 SQL 中使用用户定义的 REGEX 函数,从而避免额外的列?或者将额外的一列或两列添加到表中?还是新表?
【问题讨论】:
替换有什么问题? 它似乎不起作用。查询'o'会返回所有以'o'开头的,但是当我查询'on'(或任何超过一个字符的东西......嗯......也许我需要正则表达式中的+)我做此时不会得到任何返回。 好的,'on' 现在可以工作了...但是在查询 'on' 时它不再返回 o'neill 行...我想要它。 @Stephanie:不是这样。正如@Philip Kelley 在他的回答中指出的那样,2008 年的REPLACE()
版本具有一些增强的搜索能力(正则表达式的一小部分)。但无论如何,使用任何建议的解决方案(额外的预计算字段、索引视图)都将比正则表达式更加高效和灵活。
在此处查看 Mastros 的答案,了解从字符串中去除非字母字符的用户定义函数:***.com/questions/1007697/…
【参考方案1】:
嗯...使用经典的 asp 示例。我猜这是来自表格。对于此示例,我将您的文本框字段称为“名称搜索”。所以你request.form("namesearch")所在的页面,只需赋值strSearch = request.form("namesearch")。然后在将其运行到 SQL 查询之前,请执行以下操作:
strSearch = request.form("namesearch") 'to get textbox info from form
strSearch = replace(strSearch," ", "") 'to remove spaces
strSearch = replace(strSearch,"'", "") 'to remove apostrophes
对于 SQL
SELECT id, lastname, firstname FROM people WHERE people.lastname like '%"& strSearch &"%' ORDER BY lastname
使用 VBScript 和 SQL 2005 Server 测试和工作
【讨论】:
因此,您实际上是在运行查询之前调整代码中的字符串。我正在使用存储过程。我宁愿在一个查询中运行它。这并不能解决您的变量 strSearch(例如,oneill)与数据库中的 o'neill 不匹配的想法。因此,为什么需要对查询中的列本身进行调整。 -1 :同意 - 似乎没有抓住重点;需要使用和/或不使用非字母字符搜索存储的姓氏...【参考方案2】:我相信您遇到的问题是 SQL-Server 的 repalce 函数不接受 [^A-Za-z]
表示“非 alpa 字符”。相反,它实际上是在寻找那个确切的字符串来替换它。
http://msdn.microsoft.com/en-us/library/ms186862%28v=sql.90%29.aspx
在使用正则表达式方面,我只是通过使用 CLR 来做到这一点,这似乎对这个特定问题涉及太多了。
我的建议是将可搜索字段以两种不同的格式保存在表本身中。然后使用简单的 LIKE 搜索。
WHERE last_name LIKE @last_name OR last_name_stripped LIKE @last_name
last_name_stripped 可以是计算列(可能使用函数去除所有非字母字符),或由您的客户在插入时处理。
【讨论】:
我正计划这样做——这正是我的初衷,因为我认为这是最好的方法——但管理数据库的人试图避免这种情况。说替换会处理它,但我确实注意到它看起来不像 REPLACE 接受的模式。让我再靠近那个人。我觉得我第一次走在正确的轨道上,这证明了我是正确的。 DBA 是一个独特的品种,即使你愿意,也不要给他下地狱。他会哭,然后永远妨碍他;)【参考方案3】:如果您需要对大表上的列进行相对复杂的查找,则创建第二列包含为高效搜索而格式化的数据可能会更有效(立即需要注意的是,“喜欢”搜索很少有效率)。因此,如果您有列 LastName
,请添加一个新列,例如 LastNameLookup
,并使用针对您的搜索条件格式化的数据填充该列。如果格式化规则比较简单,你可以将其实现为计算列列;如果性能很重要,请将其设为持久计算列。
另外提一下,SQL 不支持正则表达式(尽管 SQL 2008 中的 LIKE 子句绑定了一种有限的形式)。
【讨论】:
【参考方案4】:根据您的场景可能变得多么复杂,这将是很多工作,而且速度也很慢。但是有一种更灵活的方法。考虑这样的事情,称为initialTable
:
| id | lastname | firstname |
| 1 | o'malley | josé |
| 2 | omállèy | dònáld |
| 3 | o'neill | jámès |
| 4 | onackers | sharon |
可能有点多,但它说明了一般问题。我必须根据看起来非常相似的字符数据对我们的 Intranet 网站实施“模糊”搜索 - 例如,法语或西班牙语名称或街道地址中有许多口音。
我所做的是定义一个对给定字符串执行所有替换的函数,例如(伪代码):
function string replacestuff(string input)
input = replace(input, "è", "e");
input = replace(input, "é", "e");
input = replace(input, "ò", "o");
input = replace(input, "ó", "o");
input = replace(input, "'", "");
...
return input;
使用此转换函数,创建第二个表fuzzyTable
,其内容如下:
| id | lastname | firstname |
| 1 | omalley | jose |
| 2 | omalley | donald |
| 3 | oneill | james |
| 4 | onackers | sharon |
现在,假设您将获得一个用于搜索josè
的输入字符串。这在任一表中都找不到。你需要做的是:
declare @input varchar(50)
declare @input_mod varchar(50)
set @input = 'josè'
set @input_mod = replacestuff(@input)
SELECT id FROM initialTable WHERE firstname like @input OR firstname like @input_mod
UNION
SELECT id FROM fuzzyTable WHERE firstname like @input OR firstname like @input_mod
GROUP BY id
(当然,您必须添加%
才能使LIKE
工作。)这里的关键是使用替换功能修改您输入的搜索字符串;这样,如果针对 sé
的内容搜索 sè
,您将得到匹配,因为在替换函数处理时两者都归结为 se
。
您甚至可以进行两级搜索;首先只根据正确的表检查未修改的字符串,然后如果用户这么说,则使用上面显示的语句进行模糊搜索。
这是一种非常灵活的方法,可以处理各种事情,例如通过使用两个字母表达式ae、oe、ue、ss 来查找德语字母ä、ö、ü、ß。缺点是您必须保留某些数据的重复项,并随着初始表(或替换函数)的更改而在模糊表中更改这些重复项。在我们当前的用例中,内网数据库每晚更新一次,所以这不是问题。
编辑
您需要注意,在某些情况下,使用此功能会导致误报。例如,我们将其用于员工搜索,如果您有一个拼写为 Hoek
的荷兰语姓名,您还会发现此名称正在搜索 Hök
,因为在德语中,ö
的替换会是oe
。这可以使用可识别国家/地区的替换功能来解决,但我们从未将这个概念推到这么远。根据您的输入数据,这或多或少是学术性的,对于我们的用例,我不记得有人抱怨过。
我们首先提出这种方法的主要原因是我们必须处理的一些数据充满了拼写错误,即。在法语中,许多元音的重读方式是错误的,但我们仍然需要提供一个结果。
【讨论】:
听起来像我最初想做的事情,尽管有更多的灵活性。您会建议在第二个表中执行此操作,还是只在主表中的第二列(例如,lastname_mod)中执行此操作? 这完全取决于您的数据是什么样的。如果您只有名字和姓氏,我可能会在同一个表中使用额外的列 - 这可能会使主要内容更改时更容易更新修改后的字段。在我们的例子中,我们有 12 到 14 个字段需要像这样修改,所以我们选择了第二个表。那只有一个 id 列、一个语言 id、text 列和一个 texttype 列来区分 14 种不同的文本类型。这种方法后来也大大简化了对所有这些字段的模糊全文搜索。 如果只是名称,您也可以选择包含“姓氏,名字”或“名字姓氏”的单个字段。 主表有很多列(不是我的设计)。大概30-50。因此,带有 UNION 的第二张表可能是最好的。我也在考虑 firstname lastname 字段。您会在列中的 firstname lastname 之间保留一个空格吗? 那么主要的问题是您是否真的需要对所有列进行模糊搜索,或者是否可以将其缩减为一个子集。我们也有大约 30 列,但仅在其中 14 列上使用此方法。这绝对有助于提高性能,因为您确实需要索引所有字段以提高性能。附带说明一下,您甚至可以为不同的字段定义不同的替换函数; IE。从电话号码中删除所有空格和连字符,然后将1 2-3
与123
匹配。这取决于您的需求。【参考方案5】:
使用:
WHERE ( REPLACE(people.lastname, '[^A-Za-z]', '') LIKE @last_name + '%' )
或
WHERE ( ComplexFunction( field ) LIKE whatever )
很可能会导致您的查询不使用字段people.lastname
的索引(如果有的话),因此每次运行查询时都会扫描整个表。
我看到了两种避免这种情况的方法:
一,向表中添加另一个字段lastnameStripped
,其中存储了ComplexFunction(lastname)
,并为该字段添加了索引。然后你可以搜索:
WHERE ( lastnameStripped LIKE REPLACE(@last_name, '[^A-Za-z]', '') + '%' )
或
WHERE ( lastnameStripped LIKE @last_name + '%' )
两者都将使用lastnameStripped
的索引。
二,创建一个 indexed view,将ComplexFunction( lastname )
作为字段。
【讨论】:
【参考方案6】:在我的情况下,我有一个表,其中我有带有破折号的电话号码,我想用用户输入的电话号码搜索记录(但用户输入的电话号码没有破折号)
所以我做了这样的事情
select * from rpcusttest
WHERE ( REPLACE(RPCustTest.CustomerID, '-', '') LIKE '7183877333' + '%' )
现在虽然用户输入了一个没有破折号的数字,但它也会搜索所有带有破折号的记录
【讨论】:
以上是关于在 WHERE 子句中使用 REPLACE 检查拼写排列 - MS SQL的主要内容,如果未能解决你的问题,请参考以下文章