2013-05-12 91 views
0

简介

我有以下的SQLite表198305层地理编码葡萄牙语邮政编码:的SQLite - WHERE子句&的UDF

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; 
}); 

只有记录具有到1 k从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; 

它返回1035条记录通过distance在〜0.05秒有序,然而最后一条记录的“距离”为1.353公里(大于我在最后WHERE中定义的最大值1公里)。

如果我把以下条款:

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

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

而且没有坐标界限:

的原始查询返回EXPLAIN QUERY PLAN

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

我想这样做

我想我知道为什么这发生了,SQLite正在这样做:

  1. 使用指数coordinates过滤掉的记录的边界之外的WHERE条款
  2. 过滤这些记录由"distance" <= 1WHERE条款,distance仍然NULL => 0
  3. 填入“代码”和“距离”(通过调用UDF的第一次)
  4. 为了用“距离”(这是由现在已填充)
  5. 极限记录

什么我想的SQLite做:

  1. 使用指数coordinates过滤掉的记录的边界之外的WHERE条款
  2. 这些记录,通过调用UDF
  3. 通过的“距离”过滤器由"distance" <= 1WHERE子句
  4. 顺序记录(不再次呼叫UDF)
  5. 限制记录
填充 codedistance

任何人都可以解释我如何使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()调用甚至没有被评估......但是it should,对吧?

+0

请选择一个错误的记录,并检查您是否仍得到相同的结果当你直接使用它的值时:'SELECT geo(1.2,3.4,5.6,7.8);' – 2013-05-13 07:46:19

+0

@CL。 '[地理(1.2,3.4,5.6,7.8)] => 691.995'。当我改变代码时,我注意到我通过'sprintf()'输出了一个带参数的查询,并且我正在执行另一个准备好的PDO查询。问题是,我没有将绑定参数传递给准备好的参数! :我现在很尴尬,我几个小时都在搞这个,以前我都看不清楚了。对于你浪费的时间感到抱歉,至少你会把我引向问题的根源。 – 2013-05-13 08:31:53

回答

0

基本上,我使用的是sprintf()来查看计算的是哪种边界坐标,并且由于我无法在除PHP以外的任何地方运行查询(由于UDF),我正在生成另一个带有预处理语句的查询。问题是,我没有生成最后一个参数(distance <= ?条款中的公里),我被我的sprintf()版本愚弄。

猜猜我睡觉时不应该尝试编码。我真的为你浪费时间而感到难过,谢谢大家!


只是为了保持完整性,下面的返回(!正确)873个记录的缘故,在〜0.04秒:

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

该查询(由@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; 

正确返回873分的记录,通过在distance〜0.07秒排序。

不过,我还是想知道为什么SQLite不中WHERE条款,like MySQL在评估geo() ...

+0

我刚刚删除了我的答案,因为我看到它和这个一样。为什么这个答案是由你发布的,而不是由OMGPonies发布的? – 2013-05-12 22:16:10

+0

@ MikeSherrill'Catcall':这是我在几年前问过的另一个问题(http://stackoverflow.com/a/2099140/89771),但这个问题更加糟糕,我正在使用HAVING'子句的巨大混淆。时间和那创造了很多噪音。认为发表另一个问题比复活一个令人困惑的问题更合适。 – 2013-05-12 22:33:00

0

这也返回873条记录,通过distance下令〜0.04秒:

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; 

原因this page没有一个GROUP BY子句是MySQL specific

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; 
0

我不能告诉from the documentation与否sqliteCreateFunction定义像SUM这样的聚合或者标量,如sqrt。在WHERE子句中不能引用聚合函数;需要HAVING

每SQLite的UDF documentation,你需要知道,如果只xFunc填充,或者如果特步xFinal是。这些是SQLite用来了解你正在定义的函数类型的指针,因此是否在WHERE子句中遵守它。

+0

Aggregate UDFs:http://www.php.net/manual/en/pdo.sqlitecreateaggregate.php在这里。我创建了一个常规的UDF,比如'LENGTH'或'MD5'。 – 2013-05-13 04:18:38

+1

好,好。所以你在POD的实现或SQLite中发现了一个bug。为了我的钱,我会在PHP上下注。我用C编写SQLite UDF,没有看到你正在报告的问题。 – 2013-05-13 04:24:26