Как ограничить возврат объединения только одной строкой на таблицу? - PullRequest
2 голосов
/ 26 февраля 2010

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

Например, предположим, что таблица пользователей:

tblUsers
ID      = Autoint
Name    = String

и таблица входа в систему:

tblLogin
ID         = AUtoint
UserID    = Int
IP        = String
Browser   = String
OS        = String
timestamp = DateTime

Какой самый эффективный способ составить список всех пользователей и времени их последнего входа в систему (если когда-либо), и предоставить вывод, такой как:

user       | ip     | timestamp | browser | os   |
-------------------------------------------------
Some User  |1.1.1.1 | 12/12/12  | userBA  | win  |
Other User |1.1.1.1 | 12/12/12  | userBA  | win  |
And Other  |null    | null      | null    | null |
Other Yet  |1.1.1.1 | 12/12/12  | userBA  | win  |

Имейте в виду, что здесь мы хотим, чтобы все пользователи показывались ровно один раз, даже если он никогда не входил в систему, и только самые последние входы в систему (т.е. max (timestamp)).

Есть ли способ сделать это одним оператором SQL?

Мы используем MSSQL 2005.

Заранее спасибо, ребята, Джим

Ответы [ 4 ]

2 голосов
/ 02 марта 2010

По опыту следующий запрос обычно в несколько раз быстрее

select 
    u.name, 
    l1.ip, 
    l1.timestamp, 
    l1.browser, 
    l1.os
from 
    tblUsers u
inner join 
    tblLogin l1 
on 
    u.id = l1.userid
    and l1.Id = ISNULL(
        (select 
            top 1 l2.id 
        from 
            tblLogin l2 
        where 
            u.id = l2.userid 
        order by 
            timestamp desc), 0)

чем этот запрос:

select *
from (
    select u.name, l.ip, l.timestamp, l.browser, l.os,
      row_number() over (partition by u.id order by timestamp desc) rn 
    from tblUsers u
    inner join tblLogin l on u.id = l.userid
) sub
where rn = 1

Одно время я особенно интересовался этой темой, поскольку у меня есть огромные (несколько миллионов строк) таблиц, которые мне нужно было обрабатывать аналогичным образом. Поэтому я настроил тест, выполняя это обоими способами, и более быстрый запрос выполнялся около 20 секунд, а более медленный - около 3 минут 15 секунд. (Это было на SQL 2005). Конечно, ваши настройки могут отличаться, и это также зависит от показателей, но если для вас критична производительность, я бы протестировал ее в обоих направлениях и выбрал бы более эффективный.

Обычный отказ от ответственности: я на самом деле не выполнял запрос выше, он есть, чтобы проиллюстрировать идею, возможно несколько синтаксических ошибок.

2 голосов
/ 26 февраля 2010
SELECT tblUsers.Name, MAX(tblLogin.timestamp)
FROM 
tblUsers LEFT JOIN tblLogin ON tblUsers.ID = tblLogin.UserID
GROUP BY tblUsers.ID
1 голос
/ 26 февраля 2010

По моему мнению, наиболее читаемый способ использует row_number(). Вы можете использовать его для нумерации строк, начиная с 1 для каждого пользователя, например:

select *
from (
    select u.name, l.ip, l.timestamp, l.browser, l.os,
      row_number() over (partition by u.id order by timestamp desc) rn 
    from tblUsers u
    inner join tblLogin l on u.id = l.userid
) sub
where rn = 1

Фильтр на rn = 1 дает самую последнюю строку для пользователя. Подзапрос необходим, поскольку SQL Server 2005 не позволяет ссылаться на row_number() в предложении where.

Наиболее эффективный способ сделать это зависит от количества входов в систему на пользователя. Вы можете найти хорошее объяснение некоторых более продвинутых методов в этом блоге .

1 голос
/ 26 февраля 2010
;WITH cLogins AS
(
  SELECT
     L.ip, M.LastSeen, L.browser, L.os
  FROM
      (SELECT UserID, MAX(timestamp) AS LastSeen FROM tblLogin GROUP BY UserID) M
      LEFT JOIN
      tblLogin L ON M.UserID = L.UserID AND M.LastSeen = L.JOIN 
)
SELECT
  I.Name, L.ip, L.LastSeen, L.browser, L.os
FROM 
  tblUsers U
  LEFT JOIN
  cLogins L ON U.UserID = L.UserID
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...