web-dev-qa-db-ja.com

同じテーブルへの複数のサブクエリを持つSELECT

同じ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が組み込まれている可能性がありますが、すべてをまとめることはできません。

14
Jon Smock

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
12
Thomas

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
9
A-K

使用する:

   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

学生が本をチェックアウトしていない場合、booknamebookauthor、およびdatecheckedoutはNULLになります。

3
OMG Ponies
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」を使用しました。 =>代わりに主キーを使用して、それを実際に一意の基準にします。

0
nang

試す

    ;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
0
Noel Abrahams

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_に置き換えることができます...

0
thomaspaulb

これがあなたが探しているものであることを願っています、これらのケースで私が知っている簡単な方法

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]

これは私がこのような問題に直面したときに私が解決した方法です、私はこれがあなたのケースの解決策になると思います。

0
Dennis

共通テーブル式を使いたい場合は、次のクエリを実行できます。この場合、何も得られませんが、将来のために:

;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 
0
p.campbell