【问题标题】:Is there a postgres CLOSEST operator?是否有 postgres CLOSEST 运算符?
【发布时间】:2011-08-31 11:06:50
【问题描述】:

我正在寻找类似的表格:

| id | number |
|  1 |     .7 |
|  2 |   1.25 |
|  3 |   1.01 |
|  4 |    3.0 |

查询 SELECT * FROM my_table WHEREnumberCLOSEST(1) 将返回第 3 行。我只关心数字。现在我有一个程序,它只遍历每一行并进行比较,但我认为信息应该可以从 b-tree 索引中获得,所以这可能是内置的,但我找不到任何文件表明它确实如此。

【问题讨论】:

    标签: sql postgresql


    【解决方案1】:

    我在语法上可能有点偏离,但这个参数化查询(所有的 ? 取原始问题的“1”)应该运行得很快,基本上是 2 个 B-Tree 查找 [假设数字已编入索引]。

    SELECT * FROM
    (
      (SELECT id, number FROM t WHERE number >= ? ORDER BY number LIMIT 1) AS above
      UNION ALL
      (SELECT id, number FROM t WHERE number < ? ORDER BY number DESC LIMIT 1) as below
    ) 
    ORDER BY abs(?-number) LIMIT 1;
    

    这个带有约 5e5 行表(在number 上具有索引)的查询计划如下所示:

    psql => explain select * from (
            (SELECT id, number FROM t WHERE number >= 1 order by number limit 1) 
            union all
            (select id, number from t where number < 1 order by number desc limit 1)
    ) as make_postgresql_happy 
    order by abs (1 - number) 
    limit 1;
                                                      QUERY PLAN
    --------------------------------------------------------------------------------------------------------------
     Limit  (cost=0.24..0.24 rows=1 width=12)
       ->  Sort  (cost=0.24..0.24 rows=2 width=12)
             Sort Key: (abs((1::double precision - public.t.number)))
             ->  Result  (cost=0.00..0.23 rows=2 width=12)
                   ->  Append  (cost=0.00..0.22 rows=2 width=12)
                         ->  Limit  (cost=0.00..0.06 rows=1 width=12)
                               ->  Index Scan using idx_t on t  (cost=0.00..15046.74 rows=255683 width=12)
                                     Index Cond: (number >= 1::double precision)
                         ->  Limit  (cost=0.00..0.14 rows=1 width=12)
                               ->  Index Scan Backward using idx_t on t  (cost=0.00..9053.67 rows=66136 width=12)
                                     Index Cond: (number < 1::double precision)
    (11 rows)
    

    【讨论】:

    • 这看起来很有希望。我将建立我的索引,看看这是否有帮助。谢谢!
    • 这是在做两次“Seq Scan on t”而不是一次(至少对我来说)。
    • @mu 太短:给数字添加索引后你ANALYZE 了吗? PG 应该足够聪明,可以对ORDER BY/LIMIT 进行索引扫描,但我猜你永远不知道。
    • 我刚刚进行了分析以确保我仍然进行了两次扫描。 OTOH,我的测试表中没有一百万行,因此优化器可能正在使用扫描,因为扫描小表很便宜。
    • 我认为是小桌子在愚弄我。我填充了 1e5 行,它现在正在执行索引扫描。不错的作品。如果您想查看它,我可以将解释输出添加到您的答案中(避免您构建自己的测试数据堆)。
    【解决方案2】:

    你可以试试这样的:

    select *
    from my_table
    where abs(1 - number) = (select min(abs(1 - number)) from t)
    

    这与手动循环遍历表没有太大不同,但至少它允许数据库在“数据库空间”内进行循环,而不必在函数和数据库内部之间来回跳转。此外,将所有内容推送到单个查询中可以让查询引擎知道您要执行的操作,然后它可以尝试以合理的方式执行此操作。

    【讨论】:

    • 这是一个正确的查询,类似于我目前使用的查询,但我认为索引不会有帮助。我的问题是我正在查询的表有数亿行,并且需要很长时间。
    • @quodlibetor:您是否尝试在 abs(1-number) 上创建索引以加快查询速度?
    • 例如在此查询上运行 EXPLAIN 会返回 postgres 必须对 281,610,907,7​​22 行进行顺序扫描……这很“烦人”。)
    • @a_horse_with_no_name:谢谢!这将有助于我的常见情况,但通常搜索的数字可以是任意的。
    • 如果最接近的参数始终为1,则索引会有所帮助。如果是任意的,则问题更难。
    【解决方案3】:

    第二个答案是正确的,但我在“UNION ALL”上遇到了错误:

    DBD::Pg::st execute failed: ERROR: syntax error at or near "UNION"

    我用这段代码修复了它:

    SELECT * FROM
      (
        (SELECT * FROM table WHERE num >= ? ORDER BY num LIMIT 1)
            UNION ALL
        (SELECT * FROM table WHERE num < ?  ORDER BY num DESC LIMIT 1)
      ) as foo
    ORDER BY abs(?-num) LIMIT 1;
    

    诀窍是从内部表中删除 AS 并仅在 UNION 上使用它。

    【讨论】:

      【解决方案4】:

      如果您希望在组中找到最接近的值,此代码会很有帮助。在这里,我根据我的列 val 与我的目标值 0.5 的接近程度,将我的表 column_you_wish_to_group_by 拆分为 column_you_wish_to_group_by

      SELECT *
      FROM (
        SELECT
          ROW_NUMBER() OVER (PARTITION BY t.column_you_wish_to_group_by ORDER BY abs(t.val - 0.5) ASC) AS r,
          t.*
        FROM
          tb t) x 
      WHERE x.r = 1;

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-05-04
        • 2014-12-29
        • 1970-01-01
        • 2012-02-08
        • 2017-01-09
        • 2012-09-03
        • 2023-03-20
        • 1970-01-01
        相关资源
        最近更新 更多