1

はじめに

198,305 ジオコーディングされたポルトガルの郵便番号を含む次の SQLite テーブルがあります。

CREATE TABLE "pt_postal" (
  "code" text NOT NULL,
  "geo_latitude" real(9,6) NULL,
  "geo_longitude" real(9,6) NULL
);

CREATE UNIQUE INDEX "pt_postal_code" ON "pt_postal" ("code");
CREATE INDEX "coordinates" ON "pt_postal" ("geo_latitude", "geo_longitude");

PHP には、2 つの座標間の距離を返す次のユーザー定義関数もあります。

$db->sqliteCreateFunction('geo', function ()
{
    if (count($data = func_get_args()) < 4)
    {
        $data = explode(',', implode(',', $data));
    }

    if (count($data = array_map('deg2rad', array_filter($data, 'is_numeric'))) == 4)
    {
        return round(6378.14 * acos(sin($data[0]) * sin($data[2]) + cos($data[0]) * cos($data[2]) * cos($data[1] - $data[3])), 3);
    }

    return null;
});

からの距離が 1 km 以下のレコードは874件のみです。38.73311, -9.138707


問題

UDF は SQL クエリで問題なく動作していますが、何らかの理由でその戻り値をWHERE句で使用できません。たとえば、次のクエリを実行した場合です。

SELECT
    "code",
    geo(38.73311, -9.138707, "geo_latitude", "geo_longitude") AS "distance"
    FROM "pt_postal" WHERE 1 = 1
        AND "geo_latitude" BETWEEN 38.7241268076 AND 38.7420931924
        AND "geo_longitude" BETWEEN -9.15022289523 AND -9.12719110477
        AND "distance" <= 1
    ORDER BY "distance" ASC
LIMIT 2048;

〜 0.05 秒で並べdistance替えられた1035 レコードを返します、最後のレコードには km の「距離」が1.353あります (これは、最後の で最大として定義した 1 km よりも大きいですWHERE)。

次の句を削除すると:

AND "geo_latitude" BETWEEN 38.7241268076 AND 38.7420931924
AND "geo_longitude" BETWEEN -9.15022289523 AND -9.12719110477

現在、クエリには約 6 秒かかり、 でLIMIT並べ替えられた 2048 レコード ( my )が返されdistanceます。これだけ時間がかかると思われますが"distance" <= 1 .

元のEXPLAIN QUERY PLANクエリの は次を返します。

SEARCH TABLE pt_postal USING INDEX coordinates (geo_latitude>? AND geo_latitude<?)
#(~7500 rows)
USE TEMP B-TREE FOR ORDER BY

座標境界なし:

SCAN TABLE pt_postal
#(~500000 rows)
USE TEMP B-TREE FOR ORDER BY

やりたいこと

なぜこれが起こっているのか知っていると思います.SQLiteは次のことをしています:

  1. インデックスを使用して、句coordinatesの境界外のレコードを除外しますWHERE
  2. これらのレコードを"distance" <= 1 WHERE句でフィルタリングしますが、distanceそれでもNULL => 0!
  3. 「コード」と「距離」を入力します (UDF を初めて呼び出すことによって)
  4. 「距離」による順序(現在は設定済み)
  5. 記録を制限する

SQLite にやりたいこと:

  1. インデックスを使用して、句coordinatesの境界外のレコードを除外しますWHERE
  2. これらのレコードに対して、データを入力codedistance、UDF を呼び出すことによって
  3. "distance" <= 1 WHERE句でレコードをフィルタリングする
  4. 「距離」による順序付け (UDF を再度呼び出さずに)
  5. 記録を制限する

SQLite を (可能であれば) 希望どおりに動作させる方法を説明できる人はいますか?


あとがき

好奇心から、UDF を 2 回呼び出すとどれだけ遅くなるかをベンチマークしてみました。

SELECT
    "code",
    geo(38.73311, -9.138707, "geo_latitude", "geo_longitude") AS "distance"
    FROM "pt_postal" WHERE 1 = 1
        AND "geo_latitude" BETWEEN 38.7241268076 AND 38.7420931924
        AND "geo_longitude" BETWEEN -9.15022289523 AND -9.12719110477
        AND geo(38.73311, -9.138707, "geo_latitude", "geo_longitude") <= 1
    ORDER BY "distance" ASC
LIMIT 2048;

驚いたことに、同じ ~0.06 秒で実行され、(間違って!) 1035 レコードが返されます。

2番目のgeo()呼び出しは評価されていないようです...しかし、そうすべきですよね?

4

4 に答える 4

0

このクエリ ( @OMGPonies提供):

SELECT *
    FROM (
        SELECT
            "code",
            geo(38.73311, -9.138707, "geo_latitude", "geo_longitude") AS "distance"
            FROM "pt_postal" WHERE 1 = 1
                AND "geo_latitude" BETWEEN 38.7241268076 AND 38.7420931924
                AND "geo_longitude" BETWEEN -9.15022289523 AND -9.12719110477
    )
        WHERE "distance" <= 1
    ORDER BY "distance" ASC
LIMIT 2048;

distance~0.07 秒で並べ替えられた 873 レコードを正しく返します。

しかし、 MySQL のように、なぜ SQLite が節で評価geo()しないのか、まだ疑問に思っています...WHERE

于 2013-05-12T14:12:33.147 に答える
0

distanceこれも0.04 秒以内に並べ替えられた 873 レコードを返します。

SELECT
    "code",
    geo(38.73311, -9.138707, "geo_latitude", "geo_longitude") AS "distance"
    FROM "pt_postal" WHERE 1 = 1
        AND "geo_latitude" BETWEEN 38.7241268076 AND 38.7420931924
        AND "geo_longitude" BETWEEN -9.15022289523 AND -9.12719110477
    GROUP BY "code"
        HAVING "distance" <= 1
    ORDER BY "distance" ASC
LIMIT 2048;

このページGROUP BYに句がない理由は、 MySQL 固有のものです。

HAVING 句は、SELECT リストまたは外部サブクエリの select_expr で指定された任意の列またはエイリアス、および集計関数を参照できます。ただし、SQL 標準では、HAVING は GROUP BY 句の列または集計関数で使用される列のみを参照する必要があります。標準 SQL と、SELECT リスト内の列を参照できる MySQL 固有の動作の両方に対応するために、MySQL 5.0.2 以降では、HAVING が SELECT リスト内の列、GROUP BY 句内の列、外部サブクエリ内の列を参照することを許可します。 、および関数を集約します。


プライマリ/一意のキーが利用できない場合、次のハックも機能します (少し遅くなりますが - ~0.16 秒):

SELECT
    "code",
    geo(38.73311, -9.138707, "geo_latitude", "geo_longitude") AS "distance"
    FROM "pt_postal" WHERE 1 = 1
        AND "geo_latitude" BETWEEN 38.7241268076 AND 38.7420931924
        AND "geo_longitude" BETWEEN -9.15022289523 AND -9.12719110477
    GROUP BY _ROWID_
        HAVING "distance" <= 1
    ORDER BY "distance" ASC
LIMIT 2048;
于 2013-05-12T15:48:23.190 に答える