【问题标题】:How to limit the return of a join to just one row per table?如何将连接的返回限制为每个表仅一行?
【发布时间】:2011-01-21 05:51:05
【问题描述】:

今天在工作中,我们讨论了哪种方式是进行这样的查询的最佳方式:

例如让我们假设一个用户表:

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。

在此先感谢大家,吉姆

【问题讨论】:

    标签: sql sql-server-2005 tsql join


    【解决方案1】:
    SELECT tblUsers.Name, MAX(tblLogin.timestamp)
    FROM 
    tblUsers LEFT JOIN tblLogin ON tblUsers.ID = tblLogin.UserID
    GROUP BY tblUsers.ID
    

    【讨论】:

    • 感谢您的输入 Alex,但是其他字段呢?IP、浏览器、操作系统?当您将它们添加到组中时,您将获得重复的用户,一个对应于tblLogin 准确地说,有没有办法为每个用户(tblUsers 中的所有用户)只返回一行?
    【解决方案2】:

    根据经验,以下查询通常快几倍

    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 上)。当然,您的设置可能会有所不同,这也取决于指标,但如果性能对您很重要,我会两种方式进行测试并选择性能更好的一种。

    通常的免责声明:我实际上并没有运行上面的查询,它是为了说明这个想法,可能存在一些语法错误。

    【讨论】:

      【解决方案3】:

      我认为,最易读的方式使用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 不允许您在 where 子句中引用 row_number()

      执行此操作的最有效方法取决于每个用户的登录次数。您可以在this blog post 中找到一些更高级方法的很好解释。

      【讨论】:

        【解决方案4】:
        ;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
        

        【讨论】:

          猜你喜欢
          • 2012-07-08
          • 2012-09-20
          • 1970-01-01
          • 1970-01-01
          • 2010-10-04
          • 2022-06-13
          • 2020-01-14
          • 2018-10-12
          • 1970-01-01
          相关资源
          最近更新 更多