【问题标题】:SQLite - WHERE Clause & UDFsSQLite - WHERE 子句和 UDF
【发布时间】:2013-05-12 12:26:42
【问题描述】:

简介

我有以下 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;

它返回 1035 条记录distance 在约 0.05 秒内排序,然而最后一条记录的“距离”为 @987654328 @km(比我在上一个WHERE中定义的最大值1km还大)。

如果我删除以下子句:

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

现在查询需要将近 6 秒,并返回由 distance 排序的 2048 条记录(我的 LIMIT)。它应该需要这么长时间,但它应该只返回具有"distance" &lt;= 1874 条记录。

原始查询返回的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" &lt;= 1 WHERE 子句过滤这些记录,distance 仍然是NULL =&gt; 0
  3. 填充“代码”和“距离”(通过首次调用 UDF)
  4. 按“距离”排序(现在已填充)
  5. 限制记录

我想让 SQLite 做什么:

  1. 使用索引coordinates过滤掉WHERE子句中边界外的记录
  2. 对于这些记录,通过调用 UDF 填充 codedistance
  3. "distance" &lt;= 1 WHERE 子句过滤记录
  4. 按“距离”排序(无需再次调用 UDF)
  5. 限制记录

谁能解释我如何让 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,对吧?

【问题讨论】:

  • 请选择一个错误的记录,看看直接使用它的值是否仍然得到相同的结果:SELECT geo(1.2, 3.4, 5.6, 7.8);
  • @CL。 [geo(1.2, 3.4, 5.6, 7.8)] =&gt; 691.995。当我更改代码时,我注意到我正在通过sprintf() 输出一个带有参数的查询,并且我正在使用 PDO 执行另一个准备好的查询。问题是,我没有将绑定参数传递给准备好的参数! :S 我现在很尴尬,我已经搞砸了好几个小时,之前我都没有发现。很抱歉浪费了您的时间,至少您将我引向了问题的根源。

标签: sql sqlite user-defined-functions


【解决方案1】:

基本上,我使用sprintf() 来查看正在计算的边界坐标类型,并且由于我无法在 PHP 以外的任何地方运行查询(由于 UDF),我正在生成另一个准备好的查询陈述。问题是,我没有生成最后一个绑定参数(distance &lt;= ? 子句中的公里),我被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;

【讨论】:

    【解决方案2】:

    这也返回 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_expr 在 SELECT 列表 或外部子查询中,并 聚合函数。但是,SQL 标准要求 HAVING 必须仅引用 GROUP BY 子句中的列或 聚合函数。为了适应标准 SQL 和 能够引用 SELECT 中的列的 MySQL 特定行为 列表,MySQL 5.0.2 及更高版本允许 HAVING 引用 SELECT 列表,GROUP BY 子句中的列,outer 中的列 子查询和聚合函数。


    如果没有可用的主键/唯一键,则以下 hack 也可以使用(虽然有点慢 - ~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;
    

    【讨论】:

      【解决方案3】:

      这个查询(@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 子句中评估 geo()like MySQL...

      【讨论】:

      • 我刚刚删除了我的答案,因为我看到它与这个相同。为什么这个答案是由您而不是 OMGPonies 发布的?
      • @MikeSherrill'Catcall':这是我很久以前问过的另一个问题 (stackoverflow.com/a/2099140/89771),但这个问题更糟糕,我当时对 HAVING 子句和那个制造了很多噪音。认为发布另一个问题比重新提出一个令人困惑的问题更合适。
      【解决方案4】:

      我无法判断 from the documentation 是否定义了 sqliteCreateFunction 是否定义了聚合(如 SUM)或标量(如 sqrt)。 WHERE 子句中不能引用聚合函数; HAVING 是必需的。

      根据 SQLite UDF documentation,您需要知道是否仅填充了 xFunc,或者是否填充了 xStepxFinal。这些是 SQLite 用来了解您正在定义的函数类型的指针,因此是否在 WHERE 子句中尊重它。

      【讨论】:

      • 聚合 UDF:php.net/manual/en/pdo.sqlitecreateaggregate.php 在这里。我正在创建一个常规的 UDF - 例如 LENGTHMD5
      • 好的,好的。所以你发现了一个错误,无论是在 POD 的实现中还是在 SQLite 中。为了我的钱,我会押注 PHP。我已经用 C 编写了 SQLite UDF,但没有看到您报告的问题。
      猜你喜欢
      • 1970-01-01
      • 2018-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-18
      • 1970-01-01
      • 2016-09-18
      • 1970-01-01
      相关资源
      最近更新 更多