【问题标题】:How to select only the first rows for each unique value of a column?如何只为列的每个唯一值选择第一行?
【发布时间】:2011-06-07 10:36:14
【问题描述】:

假设我有一张客户地址表:

+-----------------------+------------------------+
|         CName         |      AddressLine       |
+-----------------------+------------------------+
|  John Smith           |  123 Nowheresville     |
|  Jane Doe             |  456 Evergreen Terrace |
|  John Smith           |  999 Somewhereelse     |
|  Joe Bloggs           |  1 Second Ave          |
+-----------------------+------------------------+

在表格中,像 John Smith 这样的一位客户可以有多个地址。 我需要该表的SELECT 查询以仅返回在“CName”中有重复项的第一行。对于这个表,它应该返回除第 3 行以外的所有行(或第 1 - 这两个地址中的任何一个都可以,但只能返回一个)。

是否有一个关键字可以添加到SELECT 查询中,以根据服务器之前是否已经看到该列值进行过滤?

【问题讨论】:

    标签: sql sql-server tsql select unique


    【解决方案1】:

    如果您说您不在乎使用哪个地址,这是一个非常简单的答案。

    SELECT
        CName, MIN(AddressLine)
    FROM
        MyTable
    GROUP BY
        CName
    

    如果您想要第一个根据“插入”列,那么它是一个不同的查询

    SELECT
        M.CName, M.AddressLine,
    FROM
        (
        SELECT
            CName, MIN(Inserted) AS First
        FROM
            MyTable
        GROUP BY
            CName
        ) foo
        JOIN
        MyTable M ON foo.CName = M.CName AND foo.First = M.Inserted
    

    【讨论】:

    • 虽然在选择 10 列时可能不打算以这种方式使用。也似乎它不能接受位类型的列。
    • @nuit9:当然它不适用于位和 10 列。这些事实都不是你的问题。您将使用第二种技术或 Ben Thul 的技术。我回答了您具体提出的问题,并提供了有关如何更一般地解决问题的指示。
    • 第一部分可以处理多列,但不能处理位类型的列。不过,我在 MS SQL Server 2016 中对此进行了测试。
    • 这个答案适用于许多数据库平台。
    【解决方案2】:

    在 SQL 2k5+ 中,您可以执行以下操作:

    ;with cte as (
      select CName, AddressLine,
      rank() over (partition by CName order by AddressLine) as [r]
      from MyTable
    )
    select CName, AddressLine
    from cte
    where [r] = 1
    

    【讨论】:

    • 请解释rank、partition和[r]的作用
    【解决方案3】:

    您可以像这样使用row_number() over(partition by ...) 语法:

    select * from
    (
    select *
    , ROW_NUMBER() OVER(PARTITION BY CName ORDER BY AddressLine) AS row
    from myTable
    ) as a
    where row = 1
    

    它的作用是创建一个名为row 的列,这是一个计数器,每次看到相同的CName 时都会递增,并按AddressLine 对这些事件进行索引。通过强加where row = 1,可以选择AddressLine 按字母顺序排在第一位的CName。如果order bydesc,那么它将选择CName,其AddressLine 按字母顺序排在最后。

    【讨论】:

    • 这有一个额外的好处,那就是不会将您限制在第一行。在我的情况下,我实际上是在寻找前 3 次出现作为感觉检查的手段。最后一行就是where row < 4
    【解决方案4】:

    您可以使用row_number() 获取行的行号。它使用over 命令——partition by 子句指定何时重新开始编号,order by 选择对行号进行排序。即使您在查询末尾添加了order by,它也会在编号时保留over 命令中的顺序。

    select *
    from mytable
    where row_number() over(partition by Name order by AddressLine) = 1
    

    【讨论】:

    • 在postgresql中,WHERE子句中不允许使用窗口函数
    • MS-SQL 不允许这样做。
    • ROW_NUMBER() 在 Teradata 的 Where 子句中也不起作用
    【解决方案5】:

    这将为您提供每个重复行的一行。它还将为您提供位类型的列,并且它至少在 MS Sql Server 中有效。

    (select cname, address 
    from (
      select cname,address, rn=row_number() over (partition by cname order by cname) 
      from customeraddresses  
    ) x 
    where rn = 1) order by cname
    

    如果您想查找所有重复项,只需将 rn= 1 更改为 rn > 1。 希望这会有所帮助

    【讨论】:

    • 我得到 SQL 编译错误:错误行 3 在位置 25 无效标识符 'RN' 使用此解决方案
    【解决方案6】:

    要从客户表中获取每个唯一值,请使用

    SELECT DISTINCT CName FROM customertable;
    

    更深入的 w3schools:https://www.w3schools.com/sql/sql_distinct.asp

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-08-17
      • 1970-01-01
      • 2015-01-18
      • 2017-07-24
      • 1970-01-01
      • 2020-10-24
      • 2016-11-01
      相关资源
      最近更新 更多