Найти группы в данных по относительной разнице между записями - PullRequest
0 голосов
/ 03 февраля 2020

У меня есть несколько строк, отсортированных по цене:

| id | price |
|----|-------|
|  1 |  2.00 |
|  2 |  2.10 |
|  3 |  2.11 |
|  4 |  2.50 |
|  5 |  2.99 |
|  6 |  3.02 |
|  7 |  9.01 |
|  8 |  9.10 |
|  9 |  9.11 |
| 10 | 13.01 |
| 11 | 13.51 |
| 12 | 14.10 |

Мне нужно сгруппировать их в «ценовые группы». Элемент принадлежит к другой группе, когда разница в цене между ним и предыдущим элементом превышает некоторое фиксированное значение, скажем 1.50.

Таким образом, ожидаемый результат будет примерно таким:

| MIN(price) | MAX(price) | 
|------------|------------|
|       2.00 |       3.02 |
|       9.01 |       9.11 |
|      13.01 |      14.10 |

Я даже не уверен, как назвать этот тип группировки. Группировать по "скользящей разнице"? Не совсем ...

Можно ли это сделать в SQL (или в Postgres в частности)?

Ответы [ 2 ]

1 голос
/ 03 февраля 2020

Ваши результаты соответствуют взгляду на предыдущее значение и говорят, что группа начинается, когда разница больше 1,5. Вы можете сделать это с lag(), накопленной суммой и агрегацией:

select min(price), max(price)
from (select t.*,
             count(*) filter (where prev_price is null or prev_price < price - 1.5) over (order by price) as grp
      from (select t.*,
                   lag(price) over (order by price) as prev_price
            from t
           ) t
     ) t
group by grp
0 голосов
/ 03 февраля 2020

Спасибо Гордону Линоффу за его ответ , это именно то, что мне было нужно!

Я закончил использовать этот запрос просто потому, что лучше его понимаю. Я думаю, это больше noobi sh, но я тоже.

Оба запроса сортируют таблицу из 1M строк в 34 группы примерно за секунду. Этот запрос немного более производителен для 11M строк, сортируя их по 380 группам за 15 секунд, по сравнению с 23 секундами в ответе Гордона.

SELECT results.group_i, MIN(results.price), MAX(results.price), AVG(results.price)
FROM (
         SELECT *,
                SUM(new_group) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS group_i
         FROM (
                  SELECT annotated.*,
                         CASE
                             WHEN prev_price IS NULL OR price - prev_price > 1.5 THEN 1
                             ELSE 0
                         END AS new_group
                  FROM (
                           SELECT *,
                                  LAG(price) OVER (ORDER BY price) AS prev_price
                           FROM prices
                       ) AS annotated
              ) AS grouppable
) AS results
GROUP BY results.group_i
ORDER BY results.group_i;
...