介绍
我有以下 SQLite 表,其中包含 198,305 个地理编码的葡萄牙邮政编码:
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 中还有以下用户定义的函数,它返回两个坐标之间的距离:
$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;
});
只有874条记录的距离38.73311, -9.138707
小于或等于 1 公里。
问题
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 秒内返回 1035 条记录,distance
但是最后一条记录的“距离”为1.353
km(大于我在 last 中定义为最大值的 1 km WHERE
)。
如果我删除以下条款:
AND "geo_latitude" BETWEEN 38.7241268076 AND 38.7420931924
AND "geo_longitude" BETWEEN -9.15022289523 AND -9.12719110477
现在查询需要将近 6 秒,并返回 2048 条记录(我的LIMIT
),按distance
. 它应该花费这么长时间,但它应该只返回874 条记录"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 两次的速度进行基准测试:
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 条记录。
似乎第二个geo()
电话甚至没有被评估......但它应该,对吧?