web-dev-qa-db-fra.com

Comment joindre une seule ligne dans la table jointe avec postgres?

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

C'est parce que :

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"
19
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
30
Clodoaldo Neto

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
    ;
5
wildplasser

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;
3
Lennart

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;
1
jobermark