J'ai le schéma suivant:
CREATE TABLE author (
id integer
, name varchar(255)
);
CREATE TABLE book (
id integer
, author_id integer
, title varchar(255)
, rating integer
);
Et je veux que chaque auteur avec son dernier livre:
SELECT book.id, author.id, author.name, book.title as last_book
FROM author
JOIN book book ON book.author_id = author.id
GROUP BY author.id
ORDER BY book.id ASC
Apparemment, vous pouvez le faire dans mysql: Joindre deux tables dans MySQL, en ne renvoyant qu'une ligne de la deuxième table .
Mais postgres donne cette erreur:
ERREUR: la colonne "book.id" doit apparaître dans la clause GROUP BY ou être utilisée dans une fonction d'agrégation: SELECT book.id, author.id, author.name, book.title as last_book DE l'auteur REJOIGNEZ le livre book ON book.author_id = author.id GROUP BY author.id ORDER BY book.id ASC
Lorsque GROUP BY est présent, il n'est pas valide pour la liste SELECT expressions pour faire référence à des colonnes non groupées sauf dans l'agrégat. fonctions, car il y aurait plus d'une valeur possible à renvoyer pour une colonne non groupée.
Comment puis-je spécifier à postgres: "Ne me donnez que la dernière ligne commandée par joined_table.id
dans la table jointe?"
Edit: Avec ces données:
INSERT INTO author (id, name) VALUES
(1, 'Bob')
, (2, 'David')
, (3, 'John');
INSERT INTO book (id, author_id, title, rating) VALUES
(1, 1, '1st book from bob', 5)
, (2, 1, '2nd book from bob', 6)
, (3, 1, '3rd book from bob', 7)
, (4, 2, '1st book from David', 6)
, (5, 2, '2nd book from David', 6);
Je devrais voir:
book_id author_id name last_book
3 1 "Bob" "3rd book from bob"
5 2 "David" "2nd book from David"
select distinct on (author.id)
book.id, author.id, author.name, book.title as last_book
from
author
inner join
book on book.author_id = author.id
order by author.id, book.id desc
Vérifier distinct on
SELECT DISTINCT ON (expression [ ...]) conserve uniquement la première ligne de chaque ensemble de lignes où les expressions données sont considérées comme égales. Les expressions DISTINCT ON sont interprétées selon les mêmes règles que pour ORDER BY (voir ci-dessus). Notez que la "première ligne" de chaque ensemble est imprévisible, sauf si ORDER BY est utilisé pour garantir que la ligne souhaitée apparaît en premier.
Avec distinct sur il est nécessaire d'inclure les colonnes "distinctes" dans le order by
. Si ce n'est pas l'ordre que vous voulez, vous devez alors envelopper la requête et réorganiser
select
*
from (
select distinct on (author.id)
book.id, author.id, author.name, book.title as last_book
from
author
inner join
book on book.author_id = author.id
order by author.id, book.id desc
) authors_with_first_book
order by authors_with_first_book.name
Une autre solution consiste à utiliser une fonction de fenêtre comme dans la réponse de Lennart. Et un autre très générique est-ce
select
book.id, author.id, author.name, book.title as last_book
from
book
inner join
(
select author.id as author_id, max(book.id) as book_id
from
author
inner join
book on author.id = book.author_id
group by author.id
) s
on s.book_id = book.id
inner join
author on book.author_id = author.id
Cela peut sembler archaïque et trop simple, mais cela ne dépend pas des fonctions de fenêtre, des CTE et de l'agrégation de sous-requêtes. Dans la plupart des cas, c'est aussi le plus rapide.
SELECT bk.id, au.id, au.name, bk.title as last_book
FROM author au
JOIN book bk ON bk.author_id = au.id
WHERE NOT EXISTS (
SELECT *
FROM book nx
WHERE nx.author_id = bk.author_id
AND nx.book_id > bk.book_id
)
ORDER BY book.id ASC
;
Voici un moyen:
SELECT book_id, author_id, author_name, last_book
FROM (
SELECT b.id as book_id
, a.id as author_id
, a.name as author_name
, b.title as last_book
, row_number() over (partition by a.id
order by b.id desc) as rn
FROM author a
JOIN book b
ON b.author_id = a.id
) last_books
WHERE rn = 1;
En guise de légère variation de la suggestion de @ wildplasser, qui fonctionne toujours pour toutes les implémentations, vous pouvez utiliser max plutôt que non existant Cela se lit mieux si vous aimez les jointures courtes mieux que les clauses où long
select *
from author au
join (
select max(id) as max_id, author_id
from book bk
group by author_id) as lb
on lb.author_id = au.id
join bk
on bk.id = lb.max_id;
ou, pour donner un nom à la sous-requête, qui clarifie les choses, allez avec WITH
with last_book as
(select max(id) as max_id, author_id
from book bk
group by author_id)
select *
from author au
join last_book lb
on au.id = lb.author_id
join bk
on bk.id = lb.max_id;