【问题标题】:SQL query to return one single record for each unique value in a column为列中的每个唯一值返回一条记录的 SQL 查询
【发布时间】:2010-11-02 05:56:01
【问题描述】:

我在 SQL Server 2000 中有一个表,我试图以特定方式查询它。展示这一点的最佳方式是使用示例数据。

看,[Addresses]

Name         Street                 City          State
--------------------------------------------------------
Bob          123 Fake Street        Peoria        IL
Bob          234 Other Street       Fargo         ND
Jim          345 Main Street        St Louis      MO

这实际上是实际表结构的简化示例。表的结构完全超出了我的控制。我需要一个查询,每个名称都会返回一个地址。哪个地址无关紧要,只有一个。结果可能是这样的:

Name         Street                 City          State
--------------------------------------------------------
Bob          123 Fake Street        Peoria        IL
Jim          345 Main Street        St Louis      MO

我发现了一个类似的问题here,但在我的情况下,给出的解决方案都不起作用,因为我无权访问CROSS APPLY,并且在每一列上调用MIN() 会将不同的地址混合在一起,尽管我不关心返回哪条记录,它必须是一个完整的行,而不是不同行的混合。

更改表结构的建议对我没有帮助。我同意这张表很糟糕,(它比这里显示的更糟糕)但这是我无法更改的主要 ERP 数据库的一部分。

此表中大约有 3000 条记录。没有主键。

有什么想法吗?

【问题讨论】:

  • 你能说出你的表中有多少条记录吗?我对此有一些想法,但如果有数千/数百万条记录,可能不会很快。
  • 这张表有主键吗?
  • ~3000 条记录,而且没有 PK,令人惊讶。我将此信息添加到问题中。
  • 好的,检查我的答案,我添加了一个独特的

标签: sql tsql sql-server-2000


【解决方案1】:

嗯,这会给你带来非常糟糕的表现,但我认为它会起作用

SELECT t.Name, t.Street, t.City, t.State
FROM table t 
INNER JOIN (
     SELECT m.Name, MIN(m.Street + ';' + m.City  + ';' + m.State) AS comb
     FROM table m
     GROUP BY m.Name
) x
   ON  x.Name = t.Name
   AND x.comb = t.Street + ';' + t.City  + ';' + t.State

【讨论】:

  • 很遗憾,此表没有唯一的 id 字段。是的,我知道。这很糟糕。
  • 我认为这些是街道名称的一部分。它似乎没有任何键。
  • 123、234 等不是 ID,它们是街道地址的一部分。给出的问题不包括表中的 ID 字段,这很遗憾,因为这个解决方案在其他方面很好。
  • 您可以使用临时表/表变量创建一个唯一的 id。例如: DECLARE @data TABLE (id int IDENTITY(1,1), ...) 然后,将表中的所有数据插入到临时表/表变量中,然后使用此脚本。
  • 在下面查看我的版本,了解如何将其更改为无需密钥即可使用的东西。
【解决方案2】:
select distinct Name , street,city,state
from table t1 where street =  
(select min(street) from table t2 where t2.name = t1.name)

【讨论】:

  • 那行不通:您可以在多个城市拥有相同的街道地址。
  • 实际上有多个行包含相同地址的示例,所以在这些情况下,我仍然会得到重复。
  • 同名同地址?如果名称+地址是唯一的,我认为这会起作用
  • 是的,有一些相同地址出现两次同名的例子。
  • 是的,如果它们是唯一的,它会起作用,但这不是一个有效的假设。姓名和街道地址冲突的几率不高,但仍然不为零。
【解决方案3】:

对上述内容稍作修改即可。

SELECT Name, Street, City, State
FROM table t 
INNER JOIN (
     SELECT Name, MIN(Street) AS Street
     FROM table m
     GROUP BY Name
) x
   ON x.Name = t.Name AND x.Street = t.Street

现在,如果您拥有相同的街道但其他信息不同(例如有错别字),这将不起作用。

或者更完整的哈希将包括所有字段(但您可能有太多的性能):

SELECT Name, Street, City, State
FROM table t 
INNER JOIN (
     SELECT Name, MIN(Street + '|' + City  + '|' + State) AS key
     FROM table m
     GROUP BY Name
) x
   ON  x.Name = t.Name
   AND x.key = Street + '|' + City  + '|' + State

【讨论】:

  • 我检查了这个。不幸的是,有些记录具有相同的街道。
  • 必须是名称和街道才会有问题。您可以添加更多列以减少碰撞的可能性,但总的来说这不是解决问题的好方法。如果可能的话,在记录上获得一个主键会更好。
  • 关闭,除了 "table" x 没有名为 Street 的字段。您需要类似“SELECT Name, MIN(Street) a”和“ON x.Name = t.Name AND x.a = t.Street”
【解决方案4】:

鉴于您的限制,我认为您无法做到这一点。您可以提取这些字段的不同组合。但是,如果有人用相同的地址拼写 Bob 和 Bobb,您最终会得到两条记录。 [GIGO]您是正确的,任何分组(没有对所有字段进行分组 - 相当于 DISTINCT)都会混合行。没有每个客户的唯一标识符,这太糟糕了。

您可以将查询嵌套在一起,例如为每个名称选择前 1 个并将所有这些连接在一起。

【讨论】:

    【解决方案5】:

    选择名称,街道,城市,州从( 选择名称,街道,城市,州, ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Name) AS rn 从表)作为吨 WHERE rn=1

    【讨论】:

      【解决方案6】:

      使用临时表或表变量,并在其中选择不同的名称列表。然后使用该结构为每个不同的名称选择原始表中每个记录的前 1 个。

      【讨论】:

      • 这样做的性能会很差,因为你必须为此使用游标,除非你有代码。
      • 查看源表我认为性能不是问题。
      • 我应该先阅读所有答案,因为这与我的解决方案相同。该表没有索引,只有 3000 条记录,所以我认为游标不会比严格的 SQL 解决方案慢得多。这也不会让我觉得这是一个经常运行的查询。
      【解决方案7】:
      SELECT name,
             ( SELECT TOP 1 street, city, state
                 FROM addresses b
                WHERE a.name = b.name )
        FROM addresses a
       GROUP BY name
      

      【讨论】:

        【解决方案8】:
        SELECT name, street, address, state
        FROM
         (SELECT name, street, address, state,
          DENSE_RANK() OVER (PARTITION BY name ORDER BY street DESC) AS r 
         FROM tbl) AS t
        WHERE r = 1; 
        

        【讨论】:

        • SQL 2000 不支持 DENSE_RANK
        【解决方案9】:

        临时表解决方案如下

        CREATE Table #Addresses
        (
            MyId int IDENTITY(1,1),
            [Name] NVARCHAR(50),
            Street NVARCHAR(50),
            City NVARCHAR(50),
            State NVARCHAR(50)
        )
        
        INSERT INTO #Addresses ([Name], Street, City, State) SELECT [Name], Street, City, State FROM Addresses
        
        SELECT
            Addresses1.[Name],
            Addresses1.Street,
            Addresses1.City,
            Addresses1.State
        FROM
            #Addresses Addresses1
        WHERE
            Addresses1.MyId =
        (
            SELECT
                MIN(MyId)
            FROM
                #Addresses Addresses2
            WHERE
                Addresses2.[Name] = Addresses1.[Name]
        )
        
        DROP TABLE #Addresses
        

        【讨论】:

        • 这不起作用,因为地址组件并不总是一起增加或减少。例如,123 法戈。
        • 好的,用你的数据集试了一下,确实,看来我的逻辑是非常错误的。只留下了临时表解决方案,但它对我来说很好。
        【解决方案10】:

        这太丑了,但听起来你的困境也很丑……所以就这样吧……

        select  name,
            (select top 1 street from [Addresses] a1 where a1.name = a0.name) as street,
            (select top 1 city from [Addresses] a2 where a2.name = a0.name) as city,
            (select top 1 state from [Addresses] a3 where a3.name = a0.name) as state
        from    (select distinct name from [Addresses]) as a0
        

        【讨论】:

        • 在阅读之前的答案时,我正在脑海中写下这个解决方案。这不是一个圆滑或漂亮的答案,但它应该可以工作。这里的另一个选择是将 FROM 子查询更改为 GROUP BY,以稍微提高 DISTINCT 的性能。
        • 这样能保证不混地址吗?
        • 需要并订购我认为是安全的
        • 这是错误的 - 您最终可能会得到来自不同地址的城市和街道
        • 是的,Alex 和递归的,它可能混淆的机会就在那里。但是,通过在使用相同索引的同一张表上选择 TOP 1 应该几乎没有失败地返回同一行。根据表的大小,只有高频率的更新和删除才会导致混淆。因此,如果这不是正在运行查询的大容量 OLTP 表,您可以应用锁并几乎保证自己每次都在同一行。
        【解决方案11】:

        如果你可以使用临时表:

        select * -- Create and populate temp table 
        into #Addresses
        from Addresses 
        
        alter table #Addresses add PK int identity(1, 1) primary key
        
        select Name, Street, City, State 
        -- Explicitly name columns here to not return the PK
        from #Addresses A
        where not exists 
            (select *
            from #Addresses B
            where B.Name = A.Name
            and A.PK > B.PK)
        

        此解决方案不适用于更大的表。

        【讨论】:

        • ++1。这是一个很好的答案。短而甜。它不需要您单独列出所有字段名称,并且完全避免了可空字段比较的问题。一个相关子查询,没有聚合,没有连接。对于 3000 行,这是 a) 最少的编码,b) 良好的性能,以及 c) 万无一失的结果。漂亮!
        【解决方案12】:

        我认为这是基于光标的解决方案的不错选择。我已经很久没有使用游标了,所以我不会尝试编写 T-SQL,但这是我的想法:

        1. 创建与地址具有相同架构的临时表
        2. 在光标中选择不同的名称
        3. 遍历光标,从地址中选择前 1 个到每个不同名称的临时表中
        4. 从临时表中返回选择

        【讨论】:

          【解决方案13】:

          还有另一种方式:

          -- build a sample table  
          DECLARE @T TABLE (Name VARCHAR(50),Street VARCHAR(50),City VARCHAR(50),State VARCHAR(50))  
          INSERT INTO @T   
          SELECT 'Bob','123 Fake Street','Peoria','IL' UNION  
          SELECT 'Bob','234 Other Street','Fargo','ND' UNION  
          SELECT 'Jim','345 Main Street','St Louis','MO' UNION  
          SELECT 'Fred','234 Other Street','Fargo','ND'  
          
          -- here is all you do to get the unique record  
          SELECT * FROM @T a WHERE (SELECT COUNT(*) FROM @T b WHERE a.Name = b.name and a.street <= b.street) = 1
          

          【讨论】:

            【解决方案14】:
            select c.*, b.* from companies c left outer join 
            (SELECT *,
                ROW_NUMBER()
                    OVER(PARTITION BY FKID ORDER BY PKId) AS Seq
             FROM Contacts) b on b.FKID = c.PKID and b.Seq = 1
            

            【讨论】:

              猜你喜欢
              • 2012-05-05
              • 1970-01-01
              • 1970-01-01
              • 2014-06-01
              • 1970-01-01
              • 1970-01-01
              • 2018-05-08
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多