私は次のスキーマを持っています:
CREATE TABLE author (
id integer
, name varchar(255)
);
CREATE TABLE book (
id integer
, author_id integer
, title varchar(255)
, rating integer
);
そして、私は各著者に最後の本が欲しいです:
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
どうやらそれをmysqlで行うことができます: MySQLの2つのテーブルに参加し、2番目のテーブルから1行だけを返します 。
しかし、postgresはこのエラーを出します:
エラー:列 "book.id"はGROUP BY句に表示するか、集計関数で使用する必要があります: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
理由 :
GROUP BYが存在する場合、グループ化されていない列に対して複数の値が返される可能性があるため、集計関数内を除き、SELECTリスト式がグループ化されていない列を参照することは無効です。
Postgresに指定するにはどうすればよいですか:「joined_table.id
、結合されたテーブル内? "
編集:このデータで:
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);
表示されるはずです:
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
チェック distinct on
SELECT DISTINCT ON(expression [、...])は、指定された式が等しいと評価される各行セットの最初の行のみを保持します。 DISTINCT ON式は、ORDER BYと同じルールを使用して解釈されます(上記を参照)。 ORDER BYを使用して目的の行が最初に表示されるようにしない限り、各セットの「最初の行」は予測できないことに注意してください。
Distinctをオンにすると、order by
に「distinct」列を含める必要があります。それが望む順序でない場合は、クエリをラップして並べ替える必要があります
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
別の解決策は、レナートの答えのようにウィンドウ関数を使用することです。そしてもう一つの非常に一般的なものはこれです
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
これは古風で非常に単純に見えるかもしれませんが、ウィンドウ関数、CTE、および集約サブクエリに依存しません。ほとんどの場合、最速でもあります。
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
;
1つの方法を次に示します。
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;
@wildplasserの提案のわずかなバリエーションとして、実装全体で機能するため、存在しないよりもmaxを使用できます。これは、長いwhere句よりも短い結合を好む場合に読みやすくなります。
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;
または、サブクエリに名前を付けて物事を明確にするには、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;
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