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

「Park View」と「Great Lake」がすでに存在することを認識し、「Park View」と「Great Lake」のみを返す簡単なphpまたはmysqlソリューションを探しています。

私の最初の考えは、短い値が一番上になるように長さで並べ替え順序を取得し、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

以下のクエリ例では、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'の長さまで)と比較して、一致するかどうかを判断します。


このクエリを使用したアプローチの利点の1つは、返される値が後続のクエリの述語で「一致」することが保証されることです。このクエリを使用して近隣のリストを取得していて、ユーザーがそのリストを選択したとします。これにより、すべての行に「一致」する値のセットが返されます。

後続のクエリでは、単純な述語(WHERE句)の任意の戻り値を使用して、一致する行を返すことができます。たとえば、ユーザーが値「五大湖」を選択した場合:

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

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;
}

levenshtein他の代替手段については、PHP のとsoundex、および MySQL のも調べることができますSOUNDEX()

もう 1 つの疑似ファジー方法は、アドレスを (MySQL または PHP を使用して) アルファベット順に並べ替え、1 つずつループ処理することです。現在のアドレスが、既に見つかった一意のアドレスのテキストで始まる場合は、それを削除します。これは、実際のファジー法を使用する場合と非常によく似ていますが、より単純です。

// 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 に答える
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 に答える