it-swarm.com.de

Wie kann man nur eine Zeile in einer verbundenen Tabelle mit Postgres verbinden?

Ich habe das folgende Schema:

CREATE TABLE author (
    id   integer
  , name varchar(255)
);
CREATE TABLE book (
    id        integer
  , author_id integer
  , title     varchar(255)
  , rating    integer
);

Und ich möchte jeden Autor mit seinem letzten Buch:

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

Anscheinend können Sie dies in mysql tun: Verknüpfen Sie zwei Tabellen in MySQL und geben Sie nur eine Zeile aus der zweiten Tabelle zurück .

Aber postgres gibt diesen Fehler:

ERROR: Die Spalte "book.id" muss in der GROUP BY-Klausel erscheinen oder verwendet werden in einer Aggregatfunktion: SELECT book.id, author.id, author.name, book.title as last_book FROM Autor BEITRETEN Buch buchen ON book.author_id = author.id GROUP BY autor.id ORDER BY book.id ASC

Es ist, weil :

Wenn GROUP BY vorhanden ist, ist es nicht gültig für die SELECT-Liste Ausdrücke, die sich auf nicht gruppierte Spalten beziehen, außer im Aggregat Funktionen, da es mehr als einen möglichen Wert gibt für eine nicht gruppierte Spalte.

Wie kann ich postgres angeben: "Gib mir nur die letzte Zeile in der verknüpften Tabelle, wenn sie nach joined_table.id geordnet ist?"


Edit: Mit diesen Daten:

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);

Ich sollte sehen:

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

Prüfen distinct on

SELECT DISTINCT ON (Ausdruck [ ...]) behält nur die erste Zeile jeder Reihe von Zeilen bei, in denen die angegebenen Ausdrücke als gleich ausgewertet werden. Die DISTINCT ON-Ausdrücke werden nach den gleichen Regeln wie für ORDER BY interpretiert (siehe oben). Beachten Sie, dass die "erste Zeile" jedes Satzes nicht vorhersehbar ist, es sei denn, ORDER BY wird verwendet, um sicherzustellen, dass die gewünschte Zeile zuerst angezeigt wird.

Wenn distinct aktiviert ist, müssen die "distinct" -Spalten im order by. Wenn dies nicht die gewünschte Reihenfolge ist, müssen Sie die Abfrage umbrechen und neu anordnen

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

Eine andere Lösung besteht darin, eine Fensterfunktion wie in Lennarts Antwort zu verwenden. Und ein anderes sehr allgemeines ist dieses

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
43
Clodoaldo Neto

Dies mag archaisch und übermäßig einfach aussehen, hängt jedoch nicht von Fensterfunktionen, CTEs und aggregierenden Unterabfragen ab. In den meisten Fällen ist es auch das schnellste.

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

Hier ist ein Weg:

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

Als geringfügige Variation des @ wildplasser-Vorschlags, der immer noch für Implementierungen gilt, können Sie max verwenden und nicht existieren. Dies liest sich besser, wenn Sie kurze Verknüpfungen besser mögen als long-Where-Klauseln

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;

oder, um der Unterabfrage einen Namen zu geben, der die Dinge klärt, gehen Sie mit 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
create temp table book_1 as (
SELECT
id
,title
,author_id
,row_number() OVER (PARTITION BY id) as rownum 
FROM
book)  distributed by ( id );

select author.id,b.id, author.id, author.name, b.title as last_book
from
    author

    left  join
   (select * from  book_1 where rownum = 1 ) b on b.author_id = author.id
order by author.id, b.id desc
0
Bobburi Madhu