【问题标题】:how to identify rows having duplicate values in fields/columns of the same row如何识别在同一行的字段/列中具有重复值的行
【发布时间】:2018-10-24 02:40:51
【问题描述】:

在表中,我想找到至少 2 个字段(列)具有重复的“非空”值的行。通用 SQL 解决方案将更可取,因为它可以在任何数据库中使用。如果不是这样,Oracle 和 SQL Server 就是我的目标数据库。举个例子

ID    COL1    COL2    COL3    COL4
1      11     11       11      44
2      11     22       33      44
3      11     null     33      33
4      11     null     null    44

应该返回以下行

ID    COL1    COL2    COL3    COL4
1      11     11       11      44
3      11     null     33      33

第一行有 3 个字段,重复值为 11,其他行 col3 和 col4 重复值为 33

【问题讨论】:

    标签: sql sql-server oracle11g


    【解决方案1】:

    蛮力方法是:

    select t.*
    from t
    where (col1 = col2 or col1 = col3 or col1 = col4 or
           col2 = col3 or col2 = col4 or col3 = col4
          ) or
          (col1 is null and (col2 is null or col3 is null or col4 is null) or
           col2 is null and (col3 is null or col4 is null) or
           col3 is null and col4 is null
          )
    

    这适用于任何数据库。

    【讨论】:

    • 蛮力将不是一个选择,因为我在表中有大约 15 个奇数列。这将是一个脆弱的解决方案
    【解决方案2】:

    您可以在 Oracle 中使用UNPIVOT

    SQL Fiddle

    Oracle 11g R2 架构设置

    CREATE TABLE table_name ( ID, COL1, COL2, COL3, COL4 ) As
    SELECT 1,      11,     11,       11,      44 FROM DUAL UNION ALL
    SELECT 2,      11,     22,       33,      44 FROM DUAL UNION ALL
    SELECT 3,      11,     null,     33,      33 FROM DUAL UNION ALL
    SELECT 4,      11,     null,     null,    44 FROM DUAL;
    

    查询 1

    SELECT *
    FROM   table_name
    WHERE  id IN (
      SELECT id
      FROM   table_name
      UNPIVOT ( value FOR key IN ( COL1, COL2, COL3, COL4 ) )
      GROUP BY id, value
      HAVING COUNT( DISTINCT key ) > 1
    )
    

    Results

    | ID | COL1 |   COL2 | COL3 | COL4 |
    |----|------|--------|------|------|
    |  1 |   11 |     11 |   11 |   44 |
    |  3 |   11 | (null) |   33 |   33 |
    

    如果您想匹配NULL,那么只需使用UNPIVOT INCLUDE NULLS

    和 SQL Server,代码几乎相同(只需要在 UNPIVOT 上加上别名):

    SQL Fiddle

    查询 1

    SELECT *
    FROM   table_name
    WHERE  id IN (
      SELECT id
      FROM   table_name
      UNPIVOT ( value FOR name IN ( COL1, COL2, COL3, COL4 ) ) AS u
      GROUP BY id, value
      HAVING COUNT( DISTINCT name ) > 1
    )
    

    Results

    | ID | COL1 |   COL2 | COL3 | COL4 |
    |----|------|--------|------|------|
    |  1 |   11 |     11 |   11 |   44 |
    |  3 |   11 | (null) |   33 |   33 |
    

    更新

    您还可以使用 Oracle 中的 *_TAB_COLUMN 字典表生成蛮力查询(SQL server 中可能有一个等效项):

    SELECT 'SELECT * FROM TABLE_NAME WHERE ('
           || LISTAGG(
                '"' || PRIOR COLUMN_NAME || '" = "' || COLUMN_NAME || '"',
                ' OR '
              ) WITHIN GROUP ( ORDER BY ROWNUM )
              || ')' AS query
    FROM   USER_TAB_COLUMNS
    WHERE  TABLE_NAME = 'TABLE_NAME'
    AND    COLUMN_NAME LIKE 'COL%'
    AND    LEVEL = 2
    START WITH COLUMN_NAME LIKE 'COL%'
    CONNECT BY PRIOR COLUMN_ID < COLUMN_ID;
    

    哪些输出:

    SELECT * FROM TABLE_NAME WHERE ("COL1" = "COL2" OR "COL1" = "COL3" OR "COL1" = "COL4" OR "COL2" = "COL3" OR "COL2" = "COL4" OR "COL3" = "COL4")
    

    【讨论】:

    • @MTO,这很有趣。任何有关性能的输入,因为我需要在具有 15+ 列的表上进行此查询,以检查超过百万行的重复项。解决方案似乎很自然,我将在非工作时间尝试并更新
    • @kishore 最高效的查询将只是枚举对的所有排列并比较它们。我添加了一个查询来自动生成一个蛮力查询,枚举所有对的排列(这样您就不必手动编码和出错)。 UNPIVOT 查询很容易编写和维护,但需要做更多的工作来取消透视、聚合然后获取完整的行,因此性能会低得多(尽管它是否仍然足够性能是您需要测试的东西与您的硬件、表、索引等)。
    【解决方案3】:

    没有硬编码列名的解决方案(Sql Server)。 可以说,我们的表是 [#test]。那么我们的查询是:

    ;with [temp] as (
        select
             [id]           =   id
            ,[col_name1]    =   [c1].[value]('local-name(.)',   'nvarchar(256)')
            ,[col_value1]   =   [c1].[value]('.',               'nvarchar(256)')
            ,[col_name2]    =   [c2].[value]('local-name(.)',   'nvarchar(256)')
            ,[col_value2]   =   [c2].[value]('.',               'nvarchar(256)')    
        from 
            [#test] as  [t]
        cross apply
            (
                select [data] = convert(xml, (select [t].* for xml path('row') ))
            )       as  [x]
        cross apply
            [x].[data].[nodes]('row/*') as [t1]([c1])
        cross apply
            [x].[data].[nodes]('row/*') as [t2]([c2])
    )
    ,[ids] as (
        select 
            [id]
        from 
            [temp]
        where
                ([col_name1]    <>  [col_name2] )
            and ([col_value1]   =   [col_value2])
        group by
             [id]
    )
    select 
        *
    from
        [#test] as  [t]
    inner join
        [ids]   as  [i]
    on
            [t].[id] = [i].[id];
    

    完整查询可见:https://pastebin.com/jUG5r41c

    【讨论】:

    • 如果要检查一个包含 15 个奇数列和数百万行以上的表,我可能会期望什么样的性能影响。为什么我们这里需要 xml 路径构造?你能解释一下吗
    • XML 是为了避免列名硬编码。 Unpivot 不能解决这个问题——因为我们需要对列名进行硬编码。我的方法是将整行脚本编写为 XML,因为 XML 允许我们选择表列名称而无需硬编码(本地名称)。然后使用 cross apply 将这个 XML 加入到查找列中,它们是相同的。大型表的性能将成为问题(XML 节点功能资源成本高)。但是,unpivot 也很重(也许更重)。这是非硬编码查询的成本。
    【解决方案4】:

    对于 SQL Server,我会使用 APPLY 运算符来执行此操作:

    select * 
    from (select *, (select COUNT(*) from (values (Col1), (Col2),.. (ColN))t(ids)) TotalCols,
                    (select COUNT(distinct ids) from (values (Col1), (Col2),.. (ColN))t(ids)) DistinctCols
          from table t
        ) t
    where TotalIds <> DistinctCols;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-11
      • 1970-01-01
      • 2018-03-16
      • 2017-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多