8

我有一个房地产列表数据库,需要返回一个社区列表。现在我正在使用 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”和“Great Lake”已经存在并且只返回“Park View”和“Great Lake”。

我最初的想法是如何按长度获取排序顺序,以便短值位于顶部,然后使用 strstr 循环。这听起来像是一项艰巨的任务,我想知道 mysql 或 php 中是否有一个函数可以轻松执行此操作。

4

4 回答 4

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用于找出最佳匹配。

在这里查看: http ://sqlfiddle.com/#!2/76a97/9/0

于 2012-08-28T18:54:00.533 回答
0

如果您总是有一个没有“Sub #”部分的条目,您可以执行以下操作:

SELECT DISTINCT neighborhood FROM table WHERE neighborhood NOT LIKE '% Sub %';

按字符串长度排序:

SELECT DISTINCT neighborhood FROM table ORDER BY LENGTH(neighborhood);
于 2012-08-28T18:41:53.513 回答
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' 的长度)进行比较,确定它是否匹配。


这种查询方法的一个好处是返回的值保证在后续查询中的谓词中“匹配”。假设您正在使用此查询来获取社区列表,并且用户选择了一个。这将返回一组将“匹配”到每一行的值。

后续查询可以使用简单谓词(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')
于 2012-08-28T22:08:30.043 回答
0

您可以使用 PHPsimilar_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 的levenshteinandsoundex以及 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方法下降太多,您可以尝试后一种功能,因为它更严格。

于 2012-08-28T18:51:08.577 回答