【问题标题】:linq sql find duplicates but take null and empty string as the samelinq sql查找重复项但将空字符串和空字符串视为相同
【发布时间】:2013-04-03 23:24:05
【问题描述】:

我有一个半百万的记录表,我需要找到重复项。所以我使用我创建的这段代码:

var dups2 = from m in mg_B
    group m by new { m.Addr1, m.Addr2, m.City, m.State }
    into g
    where g.Count() > 1
    select g;

此代码的问题在于,它不会将 addr1 为空字符串“”和分别为 NULL 的 2 条记录作为重复记录。

基本上在比较字段的空值和空值时,它会将它们视为不同,但我需要被视为相同。

我知道我可以检查每条记录并将空值替换为“”,但我花了 1 分钟时间检查了 4 000 条记录。当有人单击按钮时,这将重复执行。

我发现了这个空字符串问题,因为我最初创建了一个只包含一些字段的类(该表有 40 多个字段)。

List<CombineClass> mg = (from m in db.MG_Backup
   where m.IsArchived == false
   select new CombineClass { id = m.ID, name = m.Name, addr1 = string.IsNullOrEmpty(m.Addr1) ? "" : m.Addr1, addr2 = string.IsNullOrEmpty(m.Addr2) ? "" : m.Addr2, city = m.City, state = m.State }).ToList(); 

有什么想法吗?

【问题讨论】:

  • 第二个例子发生了什么?你是说一个工作但不受欢迎,因为你必须检查行动?一个空字符串和 null 是非常不同的东西,如果这就是你要问的,你不能告诉语言以其他方式对待它们。
  • 在第一个示例中找到 6000 条记录,在第二个示例中找到超过 7000 条记录
  • 你可以通过实现IEqualityComparer来实现你自己的comparer。然后,您的比较器将按照 的要求执行比较,方法是将 null 和空字符串视为相同。我怀疑它会很快 - 它仍然必须从数据库中提取所有记录并在 C# 端处理它们。您最好的选择是将数据库修复为只有 NULL 用于您没有 AddrLine1 的记录(因为这在语义上是正确的)。
  • @Nathan 第一个示例和第二个示例不会返回相同的记录数,因为第一个示例中的 where g.Count &gt; 1 条件。如果删除该过滤器,请查看是否得到相同的计数。

标签: c# linq null duplicates


【解决方案1】:

此版本兼容 Linq-to-Sql / Linq-to-Entities

var dups2 = from m in mg_B
    group m by new 
    { 
        Addr1 = m.Addr1 ?? string.Empty, 
        Addr2 = m.Addr2 ?? string.Empty, 
        City  = m.City ?? string.Empty, 
        State = m.State ?? string.Empty,
    }
    into g
    where g.Count() > 1
    select g;

生成的sql有点像这样:

-- Parameters
DECLARE @p0 NVarChar(1000) = ''
DECLARE @p1 NVarChar(1000) = ''
DECLARE @p2 NVarChar(1000) = ''
DECLARE @p3 NVarChar(1000) = ''
DECLARE @p4 Int = 1

SELECT [t2].[value2] AS [Addr1], [t2].[value22] AS [Addr2], [t2].[value3] AS [City], [t2].[value3] AS [State]
FROM (
    SELECT COUNT(*) AS [value], [t1].[value] AS [value2], [t1].[value2] AS [value22], [t1].[value3], [t1].[value4]
    FROM (
        SELECT COALESCE([t0].[Addr1],@p0) AS [value], COALESCE([t0].[Addr2],@p1) AS [value2], COALESCE([t0].[City],@p2) AS [value3], COALESCE([t0].[State],@p3) AS [value4]
        FROM [SettingSystemNodes] AS [t0]
        ) AS [t1]
    GROUP BY [t1].[value], [t1].[value2], [t1].[value3], [t1].[value4]
    ) AS [t2]
WHERE [t2].[value] > @p4

请注意,如果您在查询之前将string.Empty 设置为局部变量,甚至在查询中设置let 变量,则空字符串只会使用一个参数。

【讨论】:

    【解决方案2】:

    这是蛮力方法:

    var dups2 = from m in mg_B
        group m by new { 
            Addr1 = (string.IsNullOrEmpty(m.Addr1) ? "" : m.Addr1), 
            Addr2 = (string.IsNullOrEmpty(m.Addr2) ? "" : m.Addr2), 
            City  = (string.IsNullOrEmpty(m.City)  ? "" : m.City ), 
            State = (string.IsNullOrEmpty(m.State) ? "" : m.State),
            ...
            }
        into g
        where g.Count() > 1
        select g;
    

    如果您希望代码看起来更干净,您可以在string 上使用扩展方法:

    public static string EmptyForNull(this string s)
    {
        return string.IsNullOrEmpty(s) ? "" : s;
    }
    

    然后您的查询将是:

    var dups2 = from m in mg_B
        group m by new { 
            Addr1 = EmptyForNull(m.Addr1), 
            Addr2 = EmptyForNull(m.Addr2), 
            City  = EmptyForNull(m.City), 
            State = EmptyForNull(m.State),
            ...
            }
        into g
        where g.Count() > 1
        select g;
    

    但是,如果使用 SQL 而不是 Linq,这可能会快很多。

    【讨论】:

    • 这是我在写这篇文章之前尝试的第一件事,但它不起作用......它只是使标准“”或 addr1......
    • 不!我的眼睛在流血!使用空值合并,它正确地转换为 ISNULL。 Addr1 = m.Addr1 ?? string.Empty
    猜你喜欢
    • 1970-01-01
    • 2015-01-23
    • 2018-11-28
    • 1970-01-01
    • 2017-06-12
    • 2014-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多