はじめに
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は次のことをしています:
- インデックスを使用して、句
coordinates
の境界外のレコードを除外しますWHERE
- これらのレコードを
"distance" <= 1
WHERE
句でフィルタリングしますが、distance
それでもNULL => 0
! - 「コード」と「距離」を入力します (UDF を初めて呼び出すことによって)
- 「距離」による順序(現在は設定済み)
- 記録を制限する
SQLite にやりたいこと:
- インデックスを使用して、句
coordinates
の境界外のレコードを除外しますWHERE
- これらのレコードに対して、データを入力
code
しdistance
、UDF を呼び出すことによって "distance" <= 1
WHERE
句でレコードをフィルタリングする- 「距離」による順序付け (UDF を再度呼び出さずに)
- 記録を制限する
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()
呼び出しは評価されていないようです...しかし、そうすべきですよね?