Есть ли оператор postgres CLOSEST? - PullRequest
14 голосов
/ 24 мая 2011

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

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

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

Ответы [ 4 ]

20 голосов
/ 24 мая 2011

Я, возможно, немного ошибаюсь в синтаксисе, но этот параметризованный запрос (все? Взять '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)
5 голосов
/ 24 мая 2011

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

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

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

3 голосов
/ 08 сентября 2015

2-й ответ правильный, но я обнаружил ошибку "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.

0 голосов
/ 12 сентября 2015

Этот код полезен, если вы хотите найти самое близкое значение в группах. Здесь я делю свою таблицу 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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...