如何仅选择每个列唯一值的第一行?

160
假设我有一个客户地址的表格:
+-----------------------+------------------------+
|         CName         |      AddressLine       |
+-----------------------+------------------------+
|  John Smith           |  123 Nowheresville     |
|  Jane Doe             |  456 Evergreen Terrace |
|  John Smith           |  999 Somewhereelse     |
|  Joe Bloggs           |  1 Second Ave          |
+-----------------------+------------------------+

在这个表中,一个名为John Smith的客户可以有多个地址。 我需要针对这个表的SELECT查询语句,只返回在“CName”列中存在重复值时找到的第一行。对于这个表,它应该返回除了第三行之外的所有行(或者是第一行 - 这两个地址都可以,但只能返回其中一个)。
是否有一个关键字可以添加到SELECT查询中,根据服务器之前是否已经看到过该列的值来进行过滤?
7个回答

181

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

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
3
@nuit9:当然,它不能在位和10列中工作。这些事实都没有在你的问题中提到。你可以使用第二种技术或Ben Thul的技术。我具体回答了你所问的问题,并指出了如何更普遍地解决问题。 - gbn
第一部分确实可以处理多列,但不能处理位类型的列。我在MS SQL Server 2016中进行了测试。 - netfed
1
这个答案适用于许多数据库平台。 - TheLegendaryCopyCoder

41
你可以像这样使用row_number() over(partition by ...)语法:
select * from
(
select *
, ROW_NUMBER() OVER(PARTITION BY CName ORDER BY AddressLine) AS rownum
from myTable
) as a
where rownum = 1

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

3
这样做的好处是不会限制您只查找第一行。在我的情况下,我实际上正在寻找前三次出现作为一种感觉检查的手段。最后一行只需写成where row < 4 - Morvael
'row'是mysql中的保留字。我建议在第4行和第7行使用不同的变量。 - deweydb

34

在SQL 2005及以上版本中,你可以进行如下操作:

;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

10
请解释一下rank、partition和[r]分别是什么意思:
  • rank:表示矩阵的秩,即矩阵中线性无关的行数或列数。
  • partition:表示将数据集合分为更小的部分。在计算机科学中,partition通常指按照某个条件将数据进行划分并分配到不同的区域中。
  • [r]:表示R语言中的索引操作符,用来提取矩阵或向量中的元素。其中r可以是一个数字、一个向量或一个逻辑向量,表示要提取的元素的位置。
- Roberto

8
你可以使用 row_number() 来获取行的行号。它使用 over 命令 - partition by 子句指定何时重新开始编号,order by 选择在哪个列上排序行号。即使你在查询的末尾添加了一个 order by,它也会保留在编号时使用的 over 命令的排序方式。
select *
from mytable
where row_number() over(partition by Name order by AddressLine) = 1

8
在PostgreSQL中,窗口函数不允许在WHERE子句中使用。 - ekanna
7
这在 MS-SQL 中都不被允许。 - Mixxiphoid
3
在 Teradata 中,ROW_NUMBER()WHERE 子句中无法使用。 - Pirate X

4

这将为您提供每个重复行的一行。它还将为您提供位类型列,并且至少在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”,以下是解决方案。 - Garglesoap

-2
select amount 
from (
  select distinct(amount) 
  from orders 
  order by amount desc 
  limit 3
) 
order by amount asc 
limit 1;

-3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接