Есть ли оператор postgres CLOSEST?

Я ищу что-то, что, учитывая таблицу вроде:

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

запрос SELECT * FROM my_table WHERE number CLOSEST(1) вернет строку 3. Мне все равно, о числах. Сейчас у меня есть процедура, которая просто перебирает каждую строку и делает сравнение, но я полагаю, что информация должна быть доступна из индекса b-дерева, поэтому это может быть возможно как встроенное, но я не могу найти документация, предполагающая, что он это делает.

Ответы

Ответ 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)

Ответ 2

Вы можете попробовать что-то вроде этого:

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

Это не так сильно отличается от рутинного цикла в таблице, но, по крайней мере, он позволяет базе данных выполнять цикл внутри "пространства базы данных", а не переходить между вашей функцией и внутренними базами данных. Кроме того, нажатие всего на один запрос позволяет механизму запросов знать, что вы пытаетесь сделать, и затем он может попытаться сделать это разумным способом.

Ответ 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

Этот код полезен, если вы хотите найти самое близкое значение внутри групп. Здесь я разделил таблицу tb на column_you_wish_to_group_by на основании того, насколько близок мой столбец val, близкий к моему целевому значению 0,5.

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;