ВЫБЕРИТЕ с несколькими подзапросами к одной таблице - PullRequest
13 голосов
/ 11 октября 2010

Я использую один и тот же шаблон 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, но я просто не могу собрать все это вместе.

Ответы [ 8 ]

12 голосов
/ 11 октября 2010

Если вы используете 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
9 голосов
/ 11 октября 2010

На 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
3 голосов
/ 11 октября 2010

Использование:

   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.

0 голосов
/ 23 августа 2018

Надеюсь, это то, что вы ищете, простой способ, который я знаю для этого случая

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 голосов
/ 11 октября 2010

Ответ 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 можно заменить на c.rank IN(1, 2) для последних 2 проверок, BETWEEN 1 AND 3 для последних 3 и т. Д ...

0 голосов
/ 11 октября 2010
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 проблема: вы получаете несколько BookCheckouts на одного учащегося, если есть 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

Я использовал «BookName» в качестве второго критерия заказа.=> Вместо этого используйте первичный ключ, чтобы сделать его действительно уникальным критерием.

0 голосов
/ 11 октября 2010

Если вы хотите использовать общее табличное выражение, вы можете выполнить следующий запрос. Это ничего вам не даст, в данном случае, но на будущее:

;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 голосов
/ 11 октября 2010

Попробуйте

    ;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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...