2

値の取得:

ここからlevenshtein_ratio関数を取得し、MySQL データベースのキューに入れました。次の方法で実行します。

    $stmt = $db->prepare("SELECT r_id, val FROM table WHERE levenshtein_ratio(:input, someval) > 70");
    $stmt->execute(array('input' => $input));
    $result = $stmt->fetchAll(); 

    if(count($result)) {
        foreach($result as $row) {
            $out .= $row['r_id'] . ', ' . $row['val'];
        }
    }

そして、それはまさに期待どおりに機能します。levenshtein_ratio()しかし、計算する値を取得する良い方法はありますか?

私はもう試した:

    $stmt = $db->prepare("SELECT levenshtein_ratio(:input, someval), r_id, val FROM table WHERE levenshtein_ratio(:input, someval) > 70");
    $stmt->execute(array('input' => $input));
    $result = $stmt->fetchAll(); 

    if(count($result)) {
        foreach($result as $row) {
            $out .= $row['r_id'] . ', ' . $row['val'] . ', ' . $row[0];
        }
    }

技術的に機能します ( からパーセンテージを取得し$row[0]ます) が、クエリは少し見苦しく、適切なキーを使用して値を取得することはできません。他の 2 つのアイテムの場合と同様です。

どうにかして素敵なリファレンスを取得する方法はありますか?

私は試した:

$stmt = $db->prepare("SELECT r_id, val SET output=levenshtein_ratio(:input, someval) FROM table WHERE levenshtein_ratio(:input, someval) > 70");

オンラインで見つけたものをモデル化しましたが、うまくいかず、クエリ全体が台無しになりました。

高速化:

値の配列に対して次のクエリを実行しています。

foreach($parent as $input){
    $stmt = ...
    $stmt->execute...
    $result = $stmt->fetchAll(); 

    ... etc
}

しかし、それは非常に遅くなります。わずか 14 の入力の配列と約 350 行の DB の場合、20 秒遅いように、すぐに 10,000 になると予想されます。クエリをループ内に配置するのは厄介な作業であることはわかっていますが、それを回避する方法が他にわかりません。

編集1

私が使うとき

$stmt = $db->prepare("SELECT r_id, val SET output=levenshtein_ratio(:input, someval) FROM table WHERE levenshtein_ratio(:input, someval) > 70");

一度だけ計算した場合と比べて、2 倍の時間がかかることは確かですか? $i < sizeof($arr);forループにいるのと似ていますか?

4

2 に答える 2

1

「someval」は、テーブル内の列への修飾されていない参照であると想定しています。テーブル定義を見なければ理解できるかもしれませんが、SQL ステートメントを読んでいる他の人にはわかりません。今後の読者への支援として、テーブルの名前または (できれば) ステートメントでテーブルに割り当てられた短いエイリアスで列参照を修飾することを検討してください。

 SELECT t.r_id
      , t.val
   FROM `table` t
  WHERE levenshtein_ratio(:input, t.someval) > 70

WHERE 句内のその関数は、テーブル内のすべての行に対して評価する必要があります。MySQL にインデックスを作成させる方法はありません。そのため、MySQL にインデックス レンジ スキャン操作を実行させる方法はありません。

たとえば、クエリにORDER BY t.val句があった場合や、利用可能な「カバリング インデックス」がある場合、MySQL にクエリのインデックスを使用させることができる場合があります。

しかし、すべての行に対して関数を評価する必要があるという問題を回避することはできません。(問合せに行を除外する他の述語がある場合、除外された行に対して関数を評価する必要は必ずしもありません。)

関数が DETERMINISTIC であると宣言されている場合、式を SELECT リストに追加してもそれほどコストがかかることはありません。同じ引数を指定した DETERMINISTIC 関数の 2 回目の呼び出しでは、前回の実行で返された値を再利用できます。(関数 DETERMINISTIC を宣言することは、基本的に、同じ引数値が与えられたときに関数が同じ結果を返すことが保証されることを意味します。繰り返し呼び出しても同じ値が返されます。つまり、戻り値は引数値のみに依存し、依存しません。他に何か。

 SELECT t.r_id
      , t.val
      , levenshtein_ratio(:input, t.someval) AS lev_ratio
   FROM `table` t
  WHERE levenshtein_ratio(:input2, t.someval) > 70

(注: 2 番目の参照には個別のバインド プレースホルダー名を使用しました。これは、PDO が期待どおりに「重複した」バインド プレースホルダー名を処理しないためです。(これは、PDO の最近のバージョンでは修正されている可能性があります。最初の "この問題の修正」は、バインド プレースホルダー名はステートメントで 1 回だけ使用する必要があることを示すドキュメントの更新であり、同じ値への 2 つの参照が必要な場合は、2 つの異なるプレースホルダー名を使用し、同じ値を両方にバインドします。)

式を繰り返したくない場合は、条件を WHERE 句から HAVING に移動し、列に割り当てられたエイリアスによって SELECT リスト内の式を参照できます。

 SELECT t.r_id
      , t.val
      , levenshtein_ratio(:input, t.someval) AS lev_ratio
   FROM `table` t
 HAVING lev_ratio > 70

WHERE と HAVING の大きな違いは、行がアクセスされるときに WHERE 句の述語が評価されることです。HAVING 句は、行がアクセスされた後で評価されます。(これは、HAVING 句がエイリアスによって SELECT リスト内の列を参照できる理由の簡単な説明ですが、WHERE 句はそれを行うことができません。)

それが大きなテーブルであり、多数の行が除外されている場合、HAVING 句を使用するとパフォーマンスに大きな違いが生じる可能性があります..はるかに大きな中間セットが作成される可能性があります。

クエリに「使用されるインデックス」を取得するには、カバリング インデックスが唯一の選択肢です。

 ON `table` (r_id, val, someval)

これにより、MySQL は、基礎となるテーブルでページをルックアップする必要なく、インデックスからのクエリを満たすことができます。クエリが必要とするすべての列の値は、インデックスから入手できます。


ファローアップ

インデックスを作成するには、列を作成する必要があります。

  lev_ratio_foo FLOAT

関数の結果を事前に入力します

UPDATE `table` t
   SET t.lev_ratio_foo = levenshtein_ratio('foo', t.someval) 
;

次に、インデックスを作成できます。

... ON `table` (lev_ratio_foo, val, r_id)   

そして、クエリを書き直します

SELECT t.r_id
     , t.val
     , t.lev_ratio_foo 
  FROM `table` t
 WHERE t.lev_ratio_foo > 70

そのクエリを使用すると、MySQL は、先頭の列として lev_ratio_foo を持つインデックスでインデックス レンジ スキャン操作を利用できます。

おそらく、新しい行がテーブルに追加されたとき、または someval 列の値が変更されたときに、値を維持するために BEFORE INSERT および BEFORE UPDATE トリガーを追加する必要があります。

このパターンは拡張でき、「foo」以外の値に追加の列を追加できます。例: 「バー」

UPDATE `table` t
   SET t.lev_ratio_bar = levenshtein_ratio('bar', t.someval)

明らかに、そのアプローチは、広範囲の入力値に対してスケーラブルではありません。

于 2016-04-17T18:15:40.057 に答える
1

列名をクリーンアップするには、「as」を使用して関数の列の名前を変更します。同時に、where 句でその列名を使用して処理を高速化できるため、関数は 1 回だけ実行されます。

$stmt = $db->prepare("SELECT r_id, levenshtein_ratio(:input, someval) AS val FROM table HAVING val > 70");

それでも遅すぎる場合は、https://github.com/juanmirocks/Levenshtein-MySQL-UDFのような ac ライブラリを検討してください。

doh - spencer7593 が指摘したように、「where」を「having」に切り替えるのを忘れていました。

于 2016-04-17T18:14:56.167 に答える