【问题标题】:LINQ query to find similar addressesLINQ 查询以查找相似地址
【发布时间】:2017-02-03 00:11:55
【问题描述】:

我有一个存储地址的表。该表具有地址组件的多个字段,例如地址编号、街道名称、方向、后缀、前缀、城市、州和邮政编码。 (编辑:这个地址表有用户之前添加的地址。我希望他们来自同一个城镇、城市、州和国家。所以我确实保留了城市、州、国家和邮编来自他们,但不用于查询。)

我的应用程序是从数据库中用户输入的地址中找到完全匹配的地址。如果没有完全匹配,则返回相似的地址。

用户输入或存储在数据库中的所有地址均由 Google Map API 规范化以避免不匹配,例如 1234 N Johnson St、1234 North Johnson St 或 1234 North John Street。

这是我正在使用的完全匹配查询。由于存储和输入的地址均由 Google 地址 API 规范化,因此我得到了我想要的完全匹配结果。

var exactMatch = (from address in db.Addresses
                             where address.PrimaryAddressNumber == userInput.Number && address.Directional == userInput.Direction && address.Suffix == userInput.Suffix && address.StreetName  == userInput.StreetName
                             select new IncidentSite
                             {
                                 FullAddress = 'address components goes here'
                             });

但是,如果没有完全匹配,那么我想给用户一个选项。据我所知,构建多个查询然后组合在一起。它按我的预期工作,但时间太长了。

我正在做的事情

    private IQueryable<IncidentSite> GetSimilarAddress(UserInput userInput)
            {
            var numberDirectionStreetname = (from address in db.Addresses
                                     where address.PrimaryAddressNumber == userInput.Number && address.Directional == userInput.Direction && address.StreetName  == userInput.StreetName
                                     select new IncidentSite
                                     {
                                         FullAddress = 'address components goes here'
                                     });

        var numberStreetname = (from address in db.Addresses
                                     where address.PrimaryAddressNumber == userInput.Number && address.StreetName  == userInput.StreetName
                                     select new IncidentSite
                                     {
                                         FullAddress = 'address components goes here'
                                     });

        var streetname = (from address in db.Addresses
                                     where address.StreetName  == userInput.StreetName
                                     select new IncidentSite
                                     {
                                         FullAddress = 'address components goes here'
                                     });

        var similarAddress = numberDirectionStreetname.Union(numberStreetname).Union(streetname);

return similarAddress;
    }

正如您在similarAdddress 看到的,它将运行来自dbo.Addresses 表的三个查询,但使用不同的where 语句,然后union 将所有三个结果构建一个结果。

我相信我正在做的不是找到类似地址的更聪明的方法。有什么好的方法可以让我构建一个更简单、更高效的查询吗?

编辑: 我想我不够清楚为什么我必须有三个不同的查询,而不是一个。原因是向用户提供所有可能的结果。为了更详细的解释,请看下文。

如果用户搜索“1234 North Johnson St”并且没有返回完全匹配,则执行以下步骤。

首先,numberDirectionStreetname,选择所有匹配“1234 North Johnson”的地址。所以结果可能是 1234 North Johnson + Boulevard/Street/Court/Way/Parkway/etc。我希望它显示在列表的顶部,因为存在的匹配地址组件比以下地址组件多。

其次,numberStreetname,选择所有匹配“1234 Johnson”的地址。所以结果可以是 1234 + South/North/East/West/etc + Johnson + Boulevard/Street/Court/Way/Parkway/etc

第三,街道名称,选择所有匹配“约翰逊”的地址。所以结果可以是 9999 + South/North/East/West/etc + Johnson + Boulevard/Street/Court/Way/Parkway/etc

如果可能,我想在一个查询中完成。这也是我的问题的一部分,不仅让它执行得更快,而且让它变得简单。但是,它必须是三个单独的查询,您将如何订购它们?如果我的逻辑不理想,那你会怎么建议?

【问题讨论】:

  • 根据您的编辑 - 您已经足够清楚了。您想要与所提供地址的任何部分匹配的地址,并且地址与结果中排序较高的大多数部分匹配。为此,您不需要 3 个单独的查询。有关在一个查询中完成此操作的方法,请参见下面的答案。顺便说一句,虽然你没有要求,但如果你想让某些部分的匹配比其他部分更重要,你只需要加权该部分的排名即可。
  • 谢谢!我阅读了您的答案并致力于我的项目以使其按计划执行。我知道你理解我的问题,但是我不得不添加更多细节,因为对方问了。
  • 很高兴它有帮助。

标签: c# sql-server linq kendo-grid


【解决方案1】:

不必担心进行直接比较。由于您想要一个紧密匹配的列表,您只需要根据匹配的组件数对结果进行排名。

这是一个工作示例程序,如果地址的每个元素都匹配,则对其进行排名,根据排名计算总体排名和顺序(排名越高,匹配越好)。

public class Program
{
    private static readonly IEnumerable<Address> Addresses = new List<Address>
    {
        new Address{ Number = "1000", Direction = "North", Street = "Grand" },
        new Address{ Number = "2000", Direction = "North", Street = "Broadway" },
        new Address{ Number = "1000", Direction = "South", Street = "Main" },
        new Address{ Number = "3000", Direction = "South", Street = "Grand" },
        new Address{ Number = "2000", Direction = "East", Street = "Broadway" },
    };

    static void Main()
    {
        const string streetToMatch = "Broadway";
        const string numberToMatch = "2000";
        const string directionToMatch = "South";

        var rankedAddresses = from address in Addresses
                              let streetRank = address.Street == streetToMatch ? 1 : 0
                              let numberRank = address.Number == numberToMatch ? 1 : 0
                              let directionRank = address.Direction == directionToMatch ? 1 : 0
                              let rank = streetRank + numberRank + directionRank
                              orderby rank descending
                              select new
                              {
                                  Address = address,
                                  Rank = rank
                              };

        foreach (var rankedAddress in rankedAddresses)
        {
            var rank = rankedAddress.Rank;
            var address = rankedAddress.Address;
            Console.WriteLine($"Rank: {rank} | Address: {address.Number} {address.Direction} {address.Street}");
        }
    }
}

public class Address
{
    public string Street { get; set; }
    public string Number { get; set; }
    public string Direction { get; set; }
}

结果

排名:2 |地址:2000 北百老汇
排名:2 |地址:2000 东百老汇
排名:1 |地址:1000 South Main
排名:1 |地址:3000 South Grand
排名:0 |地址:1000 North Grand

【讨论】:

  • 非常感谢。评分(或排名)地址是我一直在寻找的不同项目,但现在我可以将它用于两者。我省略了一些地址组件,例如前向、后向、后缀和公寓号码。你有建议如何称重吗?我个人认为街道名称最高,然后是后缀、数字、前向、后向,然后是公寓编号。我的结果在不同的地址上返回了相同的分数,但我更喜欢其中一个而不是其他。
  • 加权不是一个精确的过程,通常很大程度上取决于偏好。这完全属于业务逻辑领域,将取决于结果的用途。但作为第一步,我建议采用由外而内的方法——也就是说,对更一般的内容给予更大的权重,对更具体的内容给予更少的权重,这与您提出的顺序非常相似。
  • 至于如何应用权重。您需要为每个部件(或部件组,如果您不需要单独的权重)定义一个权重,例如 streetWeight 像这样使用 let streetRank = address.Street == streetToMatch ? streetWeight : 0。权重值应间隔很大以“拉伸”结果值范围; 999999 等可能是一个不错的起点。当然,更高的数字代表更高的权重。
【解决方案2】:

类似地址是什么意思?我想您所说的相似地址是指同一州和国家/地区的相似地址?在这种情况下,您需要使用国家/地区过滤数据集,州可能按照国家第一、州第二、城市第三等顺序。您需要按此顺序缩小范围以减少您正在使用的行。完成此操作后,您可以使用按街道、号码等查找相似地址的逻辑。即使在这里,我也建议使用自上而下的方法。

您的查询需要时间可能是由于查询必须处理的数据量。所以过滤掉行是要走的路。

您还可以避免发送多个查询并进行联合。您不能在一个查询中使用适当的 AND OR 条件一次完成所有操作吗?

我的意思是这样的。结合使用 Inersect 和 Union 来重写你的逻辑。

   using System;
using System.Linq;
using System.Collections.Generic;

namespace mns
{
                    
public class Program
{
     private static readonly IEnumerable<Address> Addresses = new List<Address>
    {
           new Address{ Number = "1234", Direction = "South", Street = "Main" },
         new Address{ Number = "1234", Direction = "North", Street = "Broadway" },
         new Address{ Number = "1234", Direction = "North", Street = "Grand" },
      
      
        new Address{ Number = "1234", Direction = "South", Street = "Broadway" },
        new Address{ Number = "34", Direction = "East", Street = "Broadway" },
    };

    public static void Main()
    {
        const string streetToMatch = "Broadway";
        const string numberToMatch = "1234";
        const string directionToMatch = "South";
        var combinedAdrress = numberToMatch +" "+ streetToMatch + " "+ directionToMatch;

                    var rankedAddresses = from address in Addresses.Where(s=>numberToMatch== s.Number).Intersect(Addresses.Where(s=>directionToMatch==s.Direction)).Intersect(Addresses.Where(s=>streetToMatch == s.Street))
                        .Union(Addresses.Where(s=>numberToMatch== s.Number).Intersect(Addresses.Where(s=>streetToMatch == s.Street)))
                        .Union(Addresses.Where(s=>streetToMatch == s.Street))
              
                              select new
                              {
                                  Address = address.Number + " " + address.Street+ " "+ address.Direction
                                  
                              };
         Console.WriteLine("You are searching for: "+combinedAdrress);;

        foreach (var rankedAddress in rankedAddresses)
        {
            
            var address = rankedAddress.Address;
            Console.WriteLine(address);
        }
    }
}

public class Address
{
    public string Street { get; set; }
    public string Number { get; set; }
    public string Direction { get; set; }
}
}

您可以更改输入值来测试。我得到的是

您正在寻找:1234 Broadway South

1234 Broadway South 1234 Broadway North 34 Broadway East

小提琴:https://dotnetfiddle.net/Qpb5J1

【讨论】:

  • 我的意思是从我拥有的地址表中查询,而不是在整个国家、州或城市中都存在地址。这就是为什么我没有为城市、州、国家添加匹配条件的原因。目前,我在地址表中有 20 行,查询大约需要 40 秒。抱歉,我应该指定存储在数据库中的地址。我会尽快更新。
  • 40 秒对于 20 行来说太多了。我认为您需要将查询调整为单个查询,并且没有 3 个单独的查询并使用联合、使用 && 和 ||这样做的条件。
  • 我编辑了我的原始帖子。让我知道这对你是否有意义。我相信我在您最近的评论中回答了大部分问题。如果您对如何使其成为一个查询有任何建议,请告诉我。
  • 我已经编辑了我的答案,将您的逻辑组合到一个查询中。请查一下
【解决方案3】:

您为什么不先获取所有街道名称,然后将其用作您的主要列表从那里过滤?

var streetname = (from address in db.Addresses
                         where address.StreetName  == userInput.StreetName
                         select new IncidentSite
                         {
                             FullAddress = 'address components goes here'
                         });

var numberStreetname = (from address in streetname
                         where address.PrimaryAddressNumber == userInput.Number && address.StreetName  == userInput.StreetName
                         select new IncidentSite
                         {
                             FullAddress = 'address components goes here'
                         });

var numberDirectionStreetname = (from address in numberStreetname
                         where address.PrimaryAddressNumber == userInput.Number && address.Directional == userInput.Direction && address.StreetName  == userInput.StreetName
                         select new IncidentSite
                         {
                             FullAddress = 'address components goes here'
                         });

【讨论】:

  • 这假定匹配字段的优先顺序。具体来说,它会遗漏那些与街道名称不匹配但与数字匹配的内容。
  • 您的解决方案不适用于他的场景,因为这更多的是找到最相关的匹配,这涉及到几个排列和组合。
  • 我仍然不相信为什么这不起作用。假设用户正在搜索 1 Test Str。所以我们得到了 Test Str 中的所有地址。然后我们将它与特定数字的所有测试地址联合起来。然后我们将它与具有特定数字和方向的所有测试地址结合起来。没有它说我们应该得到一个特定数字的所有地址。
【解决方案4】:

只是一个解决方案,而不是解决问题的确切代码。 通过应用或用户输入的条件将所有地址获取到列表中。然后从过滤后的列表中找出计数最多的地址。

例如:

List<Address> listOfAddress = new List<Address>{
            new Address(){Street="street 1", FlatNum="15", City="Auckland"},
            new Address(){Street="street 2", FlatNum="20", City="Napier"},
            new Address(){Street="street 1", FlatNum="15", City="Hamilton"}
        };



        string userInputStree = "street 1";
        string userInputFlatnum = "15";
        string userInputCity = "Whangrey";

        var addressList = (from address in listOfAddress

                             where address.Street  == userInputStree || address.City==userInputCity || address.FlatNum == userInputFlatnum
                             select address.FlatNum + ", " + address.Street + ", " + address.City

                           ).ToList();

        //from address List find the address which has maximum count

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-08
    • 2020-03-26
    • 2022-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多