【问题标题】:Distinct of two columns grouping on another column不同的两列分组在另一列上
【发布时间】:2016-09-08 14:32:00
【问题描述】:

我正在尝试计算 SQL Server 中另一列分组的两列重复值的数量。

以下是我正在处理的示例场景。

    DECLARE @mytable TABLE (CampName varchar(10),ID VARCHAR(10),ListName varchar(10))
    INSERT INTO @mytable
            ( CampName, ID, ListName )
    VALUES  ( 'A',   'X',   'Y' ), ( 'A',   'X',   'Y' ), 
            ( 'A',   'Y',   'Z' ), ( 'A',   'Y',   'Z' ),
            ( 'A',   'Y',   'Z' ), ( 'A',   'P',   'Q' ),
            ( 'B',   'X',   'Y' ), ( 'B',   'X',   'Y' ), 
            ( 'B',   'Y',   'Z' ), ( 'B',   'Y',   'Z' ),
            ( 'B',   'Y',   'Z' ), ( 'B',   'P',   'Q' ),
            ( 'B',   'R',   'S' ), ( 'B',   'R',   'S' )

这将产生下表。

 CampName   ID  ListName
-------------------------------------
      A     X     Y
      A     X     Y -- Duplicate Record
      A     Y     Z
      A     Y     Z -- Duplicate Record
      A     Y     Z -- Duplicate Record
      A     P     Q
      B     X     Y 
      B     X     Y -- Duplicate Record
      B     Y     Z
      B     Y     Z -- Duplicate Record
      B     Y     Z -- Duplicate Record
      B     P     Q
      B     R     S
      B     R     S -- Duplicate Record

我需要如下输出:

CampName   dupcount
-------------------
A            3
B            4

基本上,无论重复值是什么,我都需要计算出每个 CampName 的重复 (ID、ListName) 数量。

如果我能澄清这方面的其他内容,请告诉我。 任何帮助将不胜感激。

【问题讨论】:

    标签: sql sql-server tsql


    【解决方案1】:

    您可以使用以下查询:

    SELECT CampName, SUM(cnt) AS dupcount
    FROM (
      SELECT CampName, COUNT(*) - 1 AS cnt
      FROM @mytable
      GROUP BY CampName, ID, ListName
      HAVING COUNT(*) > 1) AS t
    GROUP BY CampName
    

    内部查询使用HAVING 子句过滤掉非重复条目。它还计算每个ID, ListName 的重复记录数。外部查询只是对重复项的数量求和。

    【讨论】:

    • 最健壮(没有边缘情况失败)和高性能(不连接字段允许使用索引扫描而不是表扫描,如果存在适当的索引)都在这里回答...
    【解决方案2】:

    我相信ID 和 ListName 组合的不同数量需要从每个CampName 组的总数中减去才能得到正确的结果。

    SELECT t.CampName,
           COUNT(*) - COUNT(DISTINCT 'ColOne' + ID + 'ColTwo' + ListName) AS dupcount
    FROM yourTable t
    GROUP BY CampName
    

    这个查询使用了一个技巧,即连接ID 和ListName 列,它们都是文本,以有效地形成一个伪组。这样做的需要是 DISTINCT 仅适用于单列,但您有两列需要考虑。

    参考:Quora: In SQL, how to I count DISTINCT over multiple columns?

    【讨论】:

    • 串联是危险的; 'X' + 'XY' == 'XX' + 'Y' 加个分隔符也是不够的; 'X' + ',' + ',Y' == 'X,' + ',' + 'Y'。为了变得健壮,需要搜索和替换以“转义”任何使用过的分隔符。
    • @MatBailie 我们还可以为我们期望唯一的每一列附加一个标识符(例如,ColOne 用于第一列,ColTwo 用于第二列)。那么你的例子会变成ColOneX + ColTwoXY != ColOneXX + ColTwoY
    • 确实这不太可能,但仍然不是 100% 稳健; 'ColOne' + '12' + 'ColTwo' + 'XXColTwoXX' == 'ColOne' + '12ColTwoXX' + 'ColTwo' + 'XX'
    • @MatBailie 不,期望您的最后一个示例是不合理的。所以你是说ListName 可以有一个名为XXColTwoXX 的条目?真的吗?
    • 是的,这就是为什么在我的解决方案中,我更喜欢连接之间的运算符:)
    【解决方案3】:

    这是获得所需结果的简单方法:

    select t.campname, count(*) - count(distinct t.listname) as num_duplicates
    from @mytable t
    group by t.campname;
    

    逻辑是count(*) 计算所有行。 count(distinct) 计算不同列表的数量。区别在于重复的数量。

    编辑:

    Giorgios 提出了一个很好的观点。但是,数据看起来像 id 和 name 包含相同的信息,因此似乎只需要一个。如果你必须同时使用这两种方法,许多数据库会让你这样做:

    select t.campname, count(*) - count(distinct t.id, t.listname) as num_duplicates
    from @mytable t
    group by t.campname;
    

    但不是 SQL Server。相反,将它们连接在一起:

    select t.campname,
           count(*) - count(distinct concat(t.id, ':', t.listname)) as num_duplicates
    from @mytable t
    group by t.campname;
    

    【讨论】:

    • 仅当id 字段完全冗余时才有效。在示例数据中似乎是这种情况,但在number of duplicate (ID,ListName) for each CampName 的措辞中,它似乎不是多余的......
    • 我认为distinct 应该考虑两个字段,而不仅仅是listname。
    【解决方案4】:

    这个问题有点含糊。

    如果您认为所有 ID 和 ListName 组合始终相等,则以下查询适合您:

    您可以通过在您的COUNT 中使用DISTINCT 来简单地做到这一点

    SELECT CampName, COUNT(DISTINCT ListName) UniqueCount
    FROM @mytable
    GROUP BY CampName
    

    如果您怀疑该组合可能并非始终相等,则需要考虑计算 ID 和 ListName 列的组合。

    这假定连接运算符 | 不会出现在 两列中的任何一列。

    SELECT CampName, COUNT(DISTINCT ID+'|'+ListName) UniqueCount
    FROM @mytable
    GROUP BY CampName
    

    如果您担心计算重复的行数

    SELECT CampName, COUNT(*) - COUNT(DISTINCT ID+'|'+ListName) dupCount
    FROM @mytable
    GROUP BY CampName
    

    我认为是另一种选择

    ;WITH Temp AS
    (
        SELECT CampName, ID, ListName, COUNT(*) UniqueCount 
        FROM @mytable
        GROUP BY CampName, ID, ListName
    )
    SELECT CampName, COUNT(UniqueCount) count 
    FROM Temp
    GROUP BY CampName
    

    【讨论】:

    • DISTINCT 不是函数。 IE。你可以做COUNT(DISTINCT ListName)。
    • 不,如果你愿意,你甚至可以使用COUNT(DISTINCT((((ListName)))))。
    • 我看不到这两个查询中的任何一个如何产生所需的结果。用户想要计算 duplicate ID, ListName 对。这些查询似乎产生了某种相反的结果。
    • 第一个答案假设 id 和 name 组合总是相等的,所以 DISTINCT 中的任何一个都应该工作;第二个假设相反,因此 id 和 name 的组合会生成唯一计数
    • @techspider - 如果连接的值可以包含 |...'X|' + '|' + 'Y' == 'X' + '|' + '|Y'
    【解决方案5】:

    您也可以使用 CONCAT 检索相同的结果,它更可靠

    SELECT CampName, 
        COUNT(ListName)-COUNT(DISTINCT CONCAT(id,ListName)) tot 
    FROM #tmp 
    GROUP BY CampName 
    

    【讨论】:

      【解决方案6】:

      试试这样,分析SELECT语句,WITH子句对逻辑不重要:

      WITH input_data AS (
        SELECT 'X' AS x, 'Y' AS y FROM DUAL
        UNION ALL
        SELECT 'X' AS x, 'Y' AS y FROM DUAL
        UNION ALL
        SELECT 'X' AS x, 'A' AS y FROM DUAL
      )
      SELECT input_data.*, COUNT(*) OVER (PARTITION BY x, y) - 1 AS numer_duplicates
      FROM input_data
      ;
      

      【讨论】:

      • 每个 CampName 需要一个独立的结果。当你处理这个问题时,你最终会得到 GiorgosBetsos 提供的嵌套聚合答案。
      • @MatBailie 是的,只是使用分析函数作为替代解决方案。
      • 我的意思是,这种替代解决方案并不能满足 OP 的要求。 OP 需要 Per CampName 的重复计数。
      • @MatBailie 我得到了那部分,只是不认为 OP 需要用勺子喂食并且可以自己弄清楚最后一小步,但也许我只是过于乐观了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-24
      • 2018-11-29
      • 2021-06-08
      • 1970-01-01
      • 2021-10-23
      • 1970-01-01
      相关资源
      最近更新 更多