介绍
我有198305个的地理编码的葡萄牙邮政代码如下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以下的用户定义函数返回两个坐标之间的距离:
$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;
它返回1035条记录由有序distance
在〜0.05秒, 但是最后的记录具有的“距离” 1.353
公里(这是比我过去定义为最大的1公里更大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()
调用甚至没有进行评估......但它应该是吧?