PostgreSQL ВІДКЛЮЧИТИ з різними ЗАМОВЛЕННЯМИ


216

Я хочу запустити цей запит:

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

Але я отримую цю помилку:

PG :: Помилка: ПОМИЛКА: ВИБІР DISTINCT ON вирази повинні відповідати початковим виразам ORDER BY

Додавання address_idяк перший ORDER BYвираз замовчує помилку, але я дійсно не хочу додавати сортування address_id. Чи можна обійтися без замовлення address_id?


Ваша пропозиція про замовлення придбала_нато не адресу_ід. Ви можете зрозуміти своє запитання.
Тея

у мого замовлення є покупка, тому що я хочу його, але postgres також просить адресу (див. повідомлення про помилку).
sl_bug

3
Повністю відповів тут - stackoverflow.com/questions/9796078 / ... Завдяки stackoverflow.com/users/268273/mosty-mostacho
sl_bug

Особисто я вважаю, що вимагати DISTINCT ON для відповідності ORDER BY дуже сумнівне, оскільки існує безліч законних випадків використання для їх відмінності. На postgresql.uservoice є публікація, яка намагається змінити це для тих, хто відчуває подібне. postgresql.uservoice.com/forums/21853-general/suggestions/…
крапка з комою

отримав абсолютно таку ж проблему і зіткнувся з тим же обмеженням. На даний момент я розбив його на підзапит, а потім замовляв, але він відчувається брудним.
Парк Гая

Відповіді:


208

Документація говорить:

DISTINCT ON (вираз [, ...]) зберігає лише перший рядок кожного набору рядків, де дані вирази оцінюються рівними. [...] Зауважте, що "перший рядок" кожного набору є непередбачуваним, якщо ORDER BY не використовується для того, щоб бажаний рядок відображався першим. [...] Вираз DISTINCT ON повинен відповідати самому крайньому лівому виразу (ORDER BY).

Офіційна документація

Тому вам доведеться додати address_idзамовлення до.

Крім того, якщо ви шукаєте повний рядок, який містить найновіший придбаний продукт для кожного, address_idі результат, відсортований за часом, purchased_atви намагаєтеся вирішити найбільшу проблему N на групу, яку можна вирішити наступними підходами:

Загальне рішення, яке має працювати в більшості СУБД:

SELECT t1.* FROM purchases t1
JOIN (
    SELECT address_id, max(purchased_at) max_purchased_at
    FROM purchases
    WHERE product_id = 1
    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

Більше PostgreSQL-орієнтоване рішення, засноване на відповіді @ hkf:

SELECT * FROM (
  SELECT DISTINCT ON (address_id) *
  FROM purchases 
  WHERE product_id = 1
  ORDER BY address_id, purchased_at DESC
) t
ORDER BY purchased_at DESC

Проблема, уточнена, розширена та вирішена тут: Вибір рядків, упорядкованих деяким стовпцем, та відмінності в іншому


40
Це працює, але дає неправильне впорядкування. Ось чому я хочу позбутися
адреси

1
Документація зрозуміла: Ви не можете, тому що вибраний рядок буде непередбачуваним
Mosty Mostacho

3
Але, можливо, є інший спосіб вибору останніх покупок за адресою, що діють?
sl_bug

1
Якщо вам необхідно замовити по purchases.purchased_at, ви можете додати purchased_at ваших DISTINCT умов: SELECT DISTINCT ON (purchases.purchased_at, address_id). Однак два записи з однаковим адресом_id, але різними значеннями купленого_at, призведе до дублікатів у поверненому наборі. Переконайтеся, що ви знаєте дані, які ви запитуєте.
Брендан Бенсон

23
Дух питання зрозумілий. Не потрібно вибирати семантику. Сумно, що прийнята і найбільш голосована відповідь не допомагає вирішити проблему.
nicooga

55

Ви можете замовити по адресу_id у підзапиті, а потім упорядкувати за тим, що вам потрібно у зовнішньому запиті.

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

3
Але це буде повільніше, ніж лише один запит, ні?
sl_bug

2
Дуже незначно так. Хоча, оскільки у вас є покупки. * У оригіналі select, я не думаю, що це виробничий код?
hkf

8
Додам, що для новіших версій постгресів вам потрібно псевдонім підзапросу. Наприклад: ВИБІР * ВІД (ВИБІР ВІДКРИТТЯ ВКЛЮЧЕНО (address_id) purchase.address_id, покупки. * ВІД "покупок" ДЕ "закуповує". "Product_id" = 1 ЗАМОВЛЕННЯ ДО адреси_id DESC) AS tmp ЗАМОВИТИ tmp.purchased_at DESC
aembke

Це повернеться address_idдвічі (без потреби). Багато клієнтів мають проблеми з дублюючими назвами стовпців. ORDER BY address_id DESCбезглуздо і вводити в оману. Це нічого корисного в цьому запиті. Результат - довільний вибір з кожного набору рядків з однаковим address_id, а не з останнім рядком purchased_at. Неоднозначне запитання не ставило цього прямо, але це майже напевно намір ОП. Якщо коротко: не використовуйте цей запит . Я розмістив альтернативи з поясненнями.
Ервін Брандстеттер

Працювали для мене. Чудова відповідь.
Метт Вест

46

Підзапит може вирішити:

SELECT *
FROM  (
    SELECT DISTINCT ON (address_id) *
    FROM   purchases
    WHERE  product_id = 1
    ) p
ORDER  BY purchased_at DESC;

Провідні вирази в ORDER BYповинні погоджуватися з стовпцями в DISTINCT ON, тому ви не можете замовляти різні стовпці в одній і тій же SELECT.

Використовуйте додатковий ORDER BYу підзапиті, лише якщо ви бажаєте вибрати певний рядок з кожного набору:

SELECT *
FROM  (
    SELECT DISTINCT ON (address_id) *
    FROM   purchases
    WHERE  product_id = 1
    ORDER  BY address_id, purchased_at DESC  -- get "latest" row per address_id
    ) p
ORDER  BY purchased_at DESC;

Якщо purchased_atможе бути NULL, врахуйте DESC NULLS LAST. Але не забудьте відповідати вашому індексу, якщо ви маєте намір використовувати його. Побачити:

Пов’язано, з додатковими поясненнями:


Ви не можете використовувати DISTINCT ONбез відповідності ORDER BY. Перший запит вимагає ORDER BY address_idвсередині підзапиту.
Арістотель Пагалціс

4
@AristotlePagaltzis: Але можна . Де б ви це не взяли, це неправильно. Ви можете використовувати DISTINCT ONбез ORDER BYтого ж запиту. Ви отримуєте довільний рядок від кожного набору однолітків, визначеного DISTINCT ONпунктом у цьому випадку. Спробуйте або перейдіть за наведеними вище посиланнями, щоб отримати детальну інформацію та посилання на посібник. ORDER BYв одному запиті (той самий SELECT) просто не можна погодитися DISTINCT ON. Я теж це пояснив.
Erwin Brandstetter

Ага, ти маєш рацію. Я був сліпий про ORDER BYте, що в документі йдеться про замітку "непередбачуваного, якщо не використовується", тому що для мене немає сенсу, що функція реалізована таким чином, щоб мати змогу мати справу з непослідовними наборами значень ... все ж не дозволить вам використовувати це з явним замовленням. Дратівливий.
Арістотель Пагалціс

@AristotlePagaltzis: Це тому, що внутрішньо Postgres використовує один із (принаймні) двох чітких алгоритмів: або перебирати відсортований список, або працювати з хеш-значеннями - те, що обіцяє бути швидшим. У подальшому випадку результат не сортується за DISTINCT ONвиразами (поки).
Ервін Брандстеттер

2
Дякую. Ваші відповіді завжди кристально зрозумілі та корисні!
Андрій Дейнеко

10

Функція Windows може вирішити це за один прохід:

SELECT DISTINCT ON (address_id) 
   LAST_VALUE(purchases.address_id) OVER wnd AS address_id
FROM "purchases"
WHERE "purchases"."product_id" = 1
WINDOW wnd AS (
   PARTITION BY address_id ORDER BY purchases.purchased_at DESC
   ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)

7
Було б добре, якби хтось пояснив запит.
Гаджус

@Gajus: Коротке пояснення: воно не працює, лише повертає чітко address_id. Однак принцип може працювати. Суміжні приклади: stackoverflow.com/a/22064571/939860 або stackoverflow.com/a/11533808/939860 . Але існують більш короткі та / або швидші запити щодо проблеми.
Ервін Брандстеттер

5

Для всіх, хто використовує Flask-SQLAlchemy, це працювало для мене

from app import db
from app.models import Purchases
from sqlalchemy.orm import aliased
from sqlalchemy import desc

stmt = Purchases.query.distinct(Purchases.address_id).subquery('purchases')
alias = aliased(Purchases, stmt)
distinct = db.session.query(alias)
distinct.order_by(desc(alias.purchased_at))

2
Так, або ще простіше, я зміг використати:query.distinct(foo).from_self().order(bar)
Лоран Мейєр

@LaurentMeyer ти маєш на увазі Purchases.query?
reubano

Так, я мав на увазі Purchases.query
Лоран Мейєр

-2

Ви також можете це зробити, використовуючи групу за пунктом

   SELECT purchases.address_id, purchases.* FROM "purchases"
    WHERE "purchases"."product_id" = 1 GROUP BY address_id,
purchases.purchased_at ORDER purchases.purchased_at DESC

Це неправильно (якщо purchasesтільки два колонки address_idта purchased_at). Через це GROUP BYвам потрібно буде використовувати сукупну функцію, щоб отримати значення кожного стовпця, не використовуваного для групування, тому всі значення будуть надходити з різних рядків групи, якщо ви не пройдете потворну та неефективну гімнастику. Це можна виправити лише за допомогою віконних функцій, а не GROUP BY.
Арістотель Пагалціс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.