我有一个房地产列表数据库,需要返回一个社区列表。现在我正在使用mysql DISTINCT,它返回所有不同的值。我的问题是,有很多社区有相似的名字:例如:
Park View Sub 1
Park View
Park View Sub 2
Park View Sub 3
Great Lake Sub 1
Great Lake Sub 2
Great Lake
Great Lake Sub 3
我正在寻找一个简单的php或mysql解决方案,可以识别" Park View"和#34; Great Lake"已经存在并且只返回" Park View"和#34; Great Lake"。
我最初想的是如何按长度获取排序顺序,以便短值位于顶部,然后使用strstr循环。这听起来像一个大任务,我想知道是否有一个功能,无论是在mysql还是php,都可以很容易地做到这一点。
答案 0 :(得分:2)
以下是您可以尝试的一些事情;大概你正在寻找完全匹配和近距离比赛。
首先寻找完全匹配。 然后在REVERSED名称上查找LIKE匹配。 然后用最少的额外字符查找匹配。
这是一个可以完成所有这些工作的查询。请注意,如果您希望这样做有效,则需要将反转的地名存储在索引列中。
select name
from (
select name, 0 ordinal
from place
where name = 'Park View'
union
select name, 1 ordinal
from place
where Reverse(Name) like concat(Reverse('Park View'),'%')
union
select name, 2+length(name)
from place
where name like concat('Park View','%')
) a
order by ordinal
limit 1
注意此UNION查询如何使用ordinal
来确定最佳匹配。
答案 1 :(得分:0)
如果你总是有一个没有'Sub#'部分的条目,你可以这样做:
SELECT DISTINCT neighborhood FROM table WHERE neighborhood NOT LIKE '% Sub %';
按字符串长度排序:
SELECT DISTINCT neighborhood FROM table ORDER BY LENGTH(neighborhood);
答案 2 :(得分:0)
您可以使用PHP的similar_text
来实现简单的解决方案。如果您对数据进行预排序,以便首先使用较短的,所需的地址,那么它应该可以正常工作。此外,如果“不同”地址不太相似,它将更好地工作(但您可以始终提高阈值):
// if an address is 70% (or more) similar to another, it is not unique
$threshold = 70;
// list of addresses (and sorting them); this is done through the DB in your code
$addresses = array('Park View Sub 1', 'Park View', 'Park View Sub 2', 'Park View Sub 3', 'Great Lake Sub 1', 'Great Lake Sub 2', 'Great Lake', 'Great Lake Sub 3');
sort($addresses);
$unique = array();
foreach ($addresses as $address) {
$isUnique = true;
foreach ($unique as $u) {
// get the similarity between the current address and each unique address
similar_text($address, $u, $percent);
if ($percent > $threshold) {
// not unique; drop it
$isUnique = false;
break;
}
}
if ($isUnique) $unique[] = $address;
}
对于其他替代方案,您还可以查看PHP的levenshtein
和soundex
以及MySQL的SOUNDEX()
。
另一种伪模糊方法是按字母顺序(通过MySQL或PHP)对地址进行排序,并逐个循环遍历;如果当前地址开始 - 已找到唯一地址的文本,则删除它。这与使用实际的模糊方法非常相似,但它更直接:
// list of addresses (and sorting them); this is done through the DB in your code
$addresses = array('Park View Sub 1', 'Park View', 'Park View Sub 2', 'Park View Sub 3', 'Great Lake Sub 1', 'Great Lake Sub 2', 'Great Lake', 'Great Lake Sub 3');
sort($addresses);
$unique = array();
foreach ($addresses as $address) {
$isUnique = true;
foreach ($unique as $u) {
if (substr($address, 0, strlen($u)) == $u) {
$isUnique = false;
break;
}
}
if ($isUnique) $unique[] = $address;
}
此方法仅在排序后才有效,因为需要在Park View
之前找到较短地址Park View Sub 1
。如果你的地址太相似而且上面的similar_text
方法丢失了太多,你可以尝试后一种功能,因为它更严格。
答案 3 :(得分:0)
下面的示例查询将使用MySQL获取指定的结果集,但它并不真正进行“模糊匹配”,至少,这不是我将如何描述算法。 (这实现了您描述的算法 - 按值排序,然后检查每个值以查看前导部分是否“匹配”先前检索的值。)
这会找到邻域值的前导部分与先前检索到的行的值的“完全匹配”,对匹配没有任何“模糊”。
当查询遇到“不匹配”的值时,它会标记该值为“不匹配”。对于检索的下一个值,它检查该值是否以先前“不匹配”值开头;如果字符串的前导部分是完全匹配,则丢弃该值。否则,该值将标记为“不匹配”值,并保留。
此方法使用内联视图(或MySQL引用的“派生表”)。最里面的内联视图(别名为s)为我们提供了邻域的不同值的排序列表。 “技巧”(如果你想称之为)是在下一个内联视图中(别名为“t”),我们使用MySQL用户变量来引用先前检索的值。
为避免“特殊字符”出现任何问题,我们会对主要字符进行相等比较。
以下是整个查询:
SELECT t.neighborhood
FROM (
SELECT IF(IFNULL(LEFT(s.neighborhood,CHAR_LENGTH(@match)) <> @match,1),@match := s.neighborhood,NULL) AS neighborhood
FROM (SELECT RTRIM(neighborhood) AS neighborhood
FROM mytable
JOIN (SELECT @match := NULL) r
GROUP BY neighborhood
ORDER BY neighborhood
) s
) t
WHERE t.neighborhood IS NOT NULL
除了@match变量的初始化以及执行当前值与先前值的比较的表达式之外,这一切都非常简单。
如果我们不关心值中特殊字符引入的极端情况,我们可以使用更简单的LIKE或REGEXP进行比较:
s.neighborhood NOT LIKE CONCAT(@match,'%')
s.neighborhood NOT REGEXP CONCAT('^',@match)
LIKE运算符以下划线和百分号为准,REGEXP受正则表达式中使用的特殊字符的约束。为了避免这些问题,上面的查询使用了一个比较笨拙的比较:
LEFT(s.neighborhood,CHAR_LENGTH(@match)) <> @match
正在做的是取前一个值(例如@match:='Park View')并将其与下一个值的前导部分(直到'Park View'的长度)进行比较,确定它是否为a匹配。
使用此查询的方法的一个好处是,在后续查询的谓词中保证返回的值为“匹配”。假设您正在使用此查询来获取邻域列表,并且用户已选择一个。这将返回一组将与每一行“匹配”的值。
后续查询可以使用简单谓词(WHERE子句)中的任何返回值来返回匹配的行。例如,如果用户选择了“Great Lake”值:
SELECT t.*
FROM mytable t
WHERE LEFT(t.neighborhood,CHAR_LENGTH('Great Lake') = 'Great Lake'
在我们使用LIKE或REGEXP谓词进行匹配的情况下,我们希望在后续查询的谓词中使用相应的匹配:
SELECT t.*
FROM mytable t
WHERE t.neighborhood LIKE CONCAT('Great Lake','%')
SELECT t.*
FROM mytable t
WHERE t.neighborhood REGEXP CONCAT('^','Great Lake')