J'utilise le même modèle SQL encore et encore, et je sais qu'il doit y avoir un meilleur moyen, mais j'ai du mal à le reconstituer. Voici une version simple du modèle, où je retire les informations sur les étudiants et le dernier livre extrait, le cas échéant:
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
(Pour les besoins de cet exemple, veuillez ignorer le fait que TBookCheckouts devrait probablement être scindé en TCheckouts et TBooks)
Ce que j'essaie d'illustrer: j'ai tendance à avoir beaucoup de sous-requêtes pour les colonnes de la même table. J'ai aussi tendance à avoir besoin de trier ces tables sous-classées par une date pour obtenir le dernier enregistrement. Ce n'est donc pas aussi simple (du moins pour moi) que de faire un LEFT JOIN. Notez cependant que, sauf pour le champ renvoyé, je fais essentiellement la même sous-requête 3 fois. SQL Server est peut-être assez intelligent pour optimiser cela, mais je ne le pense pas (il faut absolument que je lise mieux les plans d'exécution ...).
Bien qu'il puisse y avoir des avantages à la structurer de cette façon (parfois cela finit par être plus lisible, si j'ai des tonnes de sous-requêtes et de sous-tables), il ne semble pas que cela soit particulièrement efficace.
J'ai envisagé de créer une jointure LEFT à partir d'une table dérivée, en incorporant éventuellement un ROW_NUMBER () et un PARTITION BY, mais je n'arrive pas à rassembler tous les éléments.
Si vous utilisez SQL Server 2005 et versions ultérieures, vous pouvez utiliser une fonction de classement comme suit:
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
À partir de 2005, OUTER APPLY est votre ami:
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
Utilisation:
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
Si l'étudiant n'a pas acheté de livres, les variables bookname
, bookauthor
et datecheckedout
seront 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
Ajoutez simplement la jointure à TStudent et vous avez terminé. Il reste un problème: vous obtenez plusieurs BookCheckouts par étudiant s’il existe au moins deux Bookcheckouts pour un étudiant avec la même date de sortie maximale.
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
Pour éviter les doublons:
select *
from (
select *, RANK() over (partition by StudentID order by CheckoutDate desc,BookName) rnk
from BookCheckout bc1) x
where rnk=1
J'ai utilisé "BookName" comme critère de second ordre. => Utilisez plutôt la clé primaire pour en faire un critère vraiment unique.
Essayer
;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
La réponse d'OMGPonies est bonne. Je l'écrirais avec des expressions de table communes pour plus de lisibilité:
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
Le c.rank = 1
peut être remplacé par c.rank IN(1, 2)
pour les 2 derniers paiements, BETWEEN 1 AND 3
pour les 3 derniers, etc ...
J'espère que c'est ce que vous recherchez, un moyen simple que je connais pour ces cas
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]
C’est ce que j’ai résolu lorsque j’ai fait face à un problème comme celui-ci. Je pense que ce serait la solution à votre cas.
Si vous souhaitez utiliser une expression de table commune, vous pouvez utiliser la requête suivante. Cela ne vous rapporte rien, dans ce cas, mais pour l'avenir:
;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