同じSQLパターンを何度も使用していますが、もっと良い方法があるはずですが、それらをつなぎ合わせるのに問題があります。次に、パターンの単純なバージョンを示します。ここでは、学生の情報と、彼らがチェックアウトした最後の本(ある場合)を引き戻します。
SELECT TStudents.*,
BookName = (SELECT TOP 1 BookName
FROM TBookCheckouts
WHERE StudentID = TStudents.ID
ORDER BY DateCheckedOut DESC),
BookAuthor = (SELECT TOP 1 BookAuthor
FROM TBookCheckouts
WHERE StudentID = TStudents.ID
ORDER BY DateCheckedOut DESC),
BookCheckout = (SELECT TOP 1 DateCheckedOut
FROM TBookCheckouts
WHERE StudentID = TStudents.ID
ORDER BY DateCheckedOut DESC)
FROM TStudents
(この例のために、TBookCheckoutsはおそらくTCheckoutsとTBooksに分割されるべきであるという事実を無視してください)
説明しようとしていること:同じテーブルの列には多くのサブクエリがある傾向があります。また、最新のレコードを取得するには、サブクエリされたテーブルを日付で並べ替える必要がある傾向があるため、(少なくとも私にとっては)LEFT JOINを実行するほど簡単ではありません。ただし、返されるフィールドを除いて、基本的に同じサブクエリを3回実行していることに注意してください。 SQL Serverはそれを最適化するのに十分賢いかもしれませんが、私はそうではないと考えています(実行計画を読むのがもっと上手になる必要がある...)。
このように構造化することには利点があるかもしれませんが(多くのサブクエリとサブテーブルがある場合は、これがより読みやすくなることがあります)、これは特に効率的ではないようです。
派生テーブルからLEFT JOINを実行することを検討しました。ROW_NUMBER()とPARTITION BYが組み込まれている可能性がありますが、すべてをまとめることはできません。
SQL Server 2005以降を使用している場合は、次のようなランキング関数を使用できます。
With LastCheckout As
(
Select StudentId, BookName, BookAuthor, DateCheckedOut
, Row_Number() Over ( Partition By StudentId Order By DateCheckedOut Desc) As CheckoutRank
From TBookCheckouts
)
Select ..., LastCheckout.BookName, LastCheckout.BookAuthor, LastCheckout.DateCheckedOut
From TStudents
Left Join LastCheckout
On LastCheckout.StudentId = TStudents.StudentId
And LastCheckout.CheckoutRank = 1
2005年以降、OUTER APPLYはあなたの友達です:
SELECT TStudents.*,
t.BookName ,
t.BookAuthor ,
t.BookCheckout
FROM TStudents
OUTER APPLY(SELECT TOP 1 s.*
FROM TBookCheckouts AS s
WHERE s.StudentID = TStudents.ID
ORDER BY s.DateCheckedOut DESC) AS t
使用する:
SELECT s.*,
x.bookname,
x.bookauthor,
x.datecheckedout
FROM TSTUDENTS s
LEFT JOIN (SELECT bc.studentid,
bc.bookname,
bc.bookauthor,
bc.datecheckedout,
ROW_NUMBER() OVER(PARTITION BY bc.studentid
ORDER BY bc.datecheckedout DESC) AS rank
FROM TSBOOKCHECKOUTS bc) x ON x.studentid = s.id
AND x.rank = 1
学生が本をチェックアウトしていない場合、bookname
、bookauthor
、およびdatecheckedout
はNULLになります。
create table BookCheckout(StudentID int, CheckoutDate date, BookName varchar(10))
insert into BookCheckout values (1, '1.1.2010', 'a');
insert into BookCheckout values (1, '2.1.2010', 'b');
insert into BookCheckout values (1, '3.1.2010', 'c');
insert into BookCheckout values (2, '1.1.2010', 'd');
insert into BookCheckout values (2, '2.1.2010', 'e');
select *
from BookCheckout bc1
where CheckoutDate = (
Select MAX(CheckoutDate)
from BookCheckout bc2
where bc2.StudentID= bc1.StudentID)
StudentID CheckoutDate BookName
2 2010-01-02 e
1 2010-01-03 c
TStudentに結合を追加するだけで完了です。残っている問題は1つです。同じ最大チェックアウト日を持つ学生のブックチェックアウトが2つ以上ある場合、学生ごとに複数のBookCheckoutを取得します。
select s.*, LastBookCheckout.*
from TStudent s,
(select *
from BookCheckout bc1
where CheckoutDate = (
Select MAX(CheckoutDate)
from BookCheckout bc2
where bc2.StudentID= bc1.StudentID)) LastBookCheckout
where s.ID = LastBookCheckout.StudentID
重複を避けるには:
select *
from (
select *, RANK() over (partition by StudentID order by CheckoutDate desc,BookName) rnk
from BookCheckout bc1) x
where rnk=1
2番目の順序付け基準として「BookName」を使用しました。 =>代わりに主キーを使用して、それを実際に一意の基準にします。
試す
;WITH LatestCheckouts
AS
(
SELECT DISTINCT
A.StudentID
, A.BookName
, A.BookAuthor
, A.DateCheckedOut
FROM TBookCheckouts A
INNER JOIN
(
SELECT StudentID
, DateCheckedOut = MAX(DateCheckedOut)
FROM TBookCheckouts
GROUP BY
StudentID
) B
ON A.StudentID = B.StudentID
AND A.DateCheckedOut = B.DateCheckedOut
)
SELECT students.*
, BookName = checkouts.BookName
, BookAuthor = checkouts.BookAuthor
, BookCheckout = checkouts.DateCheckedOut
FROM TStudents students
LEFT JOIN
LatestCheckouts checkouts
ON students.ID = checkouts.StudentID
OMGPoniesの答えは良いものです。読みやすくするために、共通テーブル式で記述します。
_WITH CheckoutsPerStudentRankedByDate AS (
SELECT bookname, bookauthor, datecheckedout, studentid,
ROW_NUMBER(PARTITION BY studentid ORDER BY datecheckedout DESC) AS rank
FROM TSBOOKCHECKOUTS
)
SELECT
s.*, c.bookname, c.bookauthor, c.datecheckedout
FROM TSTUDENTS AS s
LEFT JOIN CheckoutsPerStudentRankedByDate AS c
ON s.studentid = c.studentid
AND c.rank = 1
_
_c.rank = 1
_は、最後の2回のチェックアウトの場合はc.rank IN(1, 2)
に、最後の3回のチェックアウトの場合は_BETWEEN 1 AND 3
_に置き換えることができます...
これがあなたが探しているものであることを願っています、これらのケースで私が知っている簡単な方法
SELECT (SELECT TOP 1 BookName
FROM TBookCheckouts
WHERE StudentID = TStudents.ID
ORDER BY DateCheckedOut DESC)[BOOK_NAME],
(SELECT TOP 1 BookAuthor
FROM TBookCheckouts
WHERE StudentID = TStudents.ID
ORDER BY DateCheckedOut DESC)[BOOK_AUTHOR],
(SELECT TOP 1 DateCheckedOut
FROM TBookCheckouts
WHERE StudentID = TStudents.ID
ORDER BY DateCheckedOut DESC)[DATE_CHECKEDOUT]
これは私がこのような問題に直面したときに私が解決した方法です、私はこれがあなたのケースの解決策になると思います。
共通テーブル式を使いたい場合は、次のクエリを実行できます。この場合、何も得られませんが、将来のために:
;with LatestBookOut as
(
SELECT C.StudentID, BookID, Title, Author, DateCheckedOut AS BookCheckout
FROM CheckedOut AS C
INNER JOIN ( SELECT StudentID,
MAX(DateCheckedOut) AS DD
FROM Checkedout
GROUP BY StudentID) StuMAX
ON StuMAX.StudentID = C.StudentID
AND StuMAX.DD = C.DateCheckedOut
)
SELECT B.BookCheckout,
BookId,
Title,
Author,
S.*
FROM LatestBookOut AS B
INNER JOIN Student AS S ON S.ID = B.StudentID