Выбор строк, упорядоченных по одному столбцу и отличных по другому - PullRequest
18 голосов
/ 21 марта 2012

Относится к - PostgreSQL DISTINCT ON с другим ORDER BY

У меня есть таблица покупок (product_id, купил_at, address_id)

Пример данных:

| id | product_id |   purchased_at    | address_id |
| 1  |     2      | 20 Mar 2012 21:01 |     1      |
| 2  |     2      | 20 Mar 2012 21:33 |     1      |
| 3  |     2      | 20 Mar 2012 21:39 |     2      |
| 4  |     2      | 20 Mar 2012 21:48 |     2      |

Ожидаемый результат - это самый последний купленный продукт (полная строка) для каждого address_id, и этот результат должен быть отсортирован в порядке убывания по полю приобрела_at:

| id | product_id |   purchased_at    | address_id |
| 4  |     2      | 20 Mar 2012 21:48 |     2      |
| 2  |     2      | 20 Mar 2012 21:33 |     1      |

Использование запроса:

SELECT DISTINCT ON (address_id) purchases.address_id, purchases.*
FROM "purchases"
WHERE "purchases"."product_id" = 2
ORDER BY purchases.address_id ASC, purchases.purchased_at DESC

Я получаю:

| id | product_id |   purchased_at    | address_id |
| 2  |     2      | 20 Mar 2012 21:33 |     1      |
| 4  |     2      | 20 Mar 2012 21:48 |     2      |

Так что строки одинаковые, но порядок неправильный. Есть ли способ это исправить?

Ответы [ 3 ]

16 голосов
/ 21 марта 2012

Довольно четкий вопрос:)

SELECT t1.* FROM purchases t1
LEFT JOIN purchases t2
ON t1.address_id = t2.address_id AND t1.purchased_at < t2.purchased_at
WHERE t2.purchased_at IS NULL
ORDER BY t1.purchased_at DESC

И, скорее всего, более быстрый подход:

SELECT t1.* FROM purchases t1
JOIN (
    SELECT address_id, max(purchased_at) max_purchased_at
    FROM purchases
    GROUP BY address_id
) t2
ON t1.address_id = t2.address_id AND t1.purchased_at = t2.max_purchased_at
ORDER BY t1.purchased_at DESC
8 голосов
/ 21 марта 2012

Ваш ORDER BY используется DISTINCT ON для выбора строки для каждого отдельного address_id для создания.Если затем вы хотите упорядочить результирующие записи, сделайте DISTINCT ON подвыбором и упорядочите его результаты:

SELECT * FROM
(
  SELECT DISTINCT ON (address_id) purchases.address_id, purchases.*
  FROM "purchases"
  WHERE "purchases"."product_id" = 2
  ORDER BY purchases.address_id ASC, purchases.purchased_at DESC
) distinct_addrs
order by distinct_addrs.purchased_at DESC
1 голос
/ 12 июля 2017

Этот запрос сложнее перефразировать правильно, чем выглядит.

В настоящее время принятый ответ на основе соединения неправильно обрабатывает случай, когда две строки-кандидаты имеют одинаковое заданное значение purchased_at: он вернет обе строки.

Вы можете получить правильное поведение следующим образом:

SELECT * FROM purchases AS given
WHERE product_id = 2
AND NOT EXISTS (
    SELECT NULL FROM purchases AS other
    WHERE given.address_id = other.address_id
    AND (given.purchased_at < other.purchased_at OR given.id < other.id)
)
ORDER BY purchased_at DESC

Обратите внимание, что у него есть запасной вариант сравнения значений id для устранения неоднозначности в случае совпадения значений purchased_at. Это гарантирует, что условие может быть истинным только для одной строки из тех, которые имеют одинаковое значение address_id.

Исходный запрос с использованием DISTINCT ON обрабатывает этот случай автоматически!

Также обратите внимание на способ, которым вы вынуждены кодировать тот факт, что вы хотите «последний раз для каждого address_id» дважды, как в условии given.purchased_at < other.purchased_at, так и в предложении ORDER BY purchased_at DESC, и вы должны убедиться, что они матч. Мне пришлось потратить несколько лишних минут, чтобы убедить себя, что этот запрос действительно верен.

Намного проще написать этот запрос правильно и понятно, если использовать DISTINCT ON вместе с внешним подзапросом, как предлагает dbenhur .

...