【问题标题】:SQL - Combine search queries instead of multiple DB tripsSQL - 组合搜索查询而不是多个 DB 行程
【发布时间】:2019-06-13 17:48:50
【问题描述】:

搜索词来自 UI 以搜索表的实体。这些搜索结果在 UI 中的显示顺序如下:

  • 第一:完全匹配
  • 第二:从那个词开始
  • 第三个:包含该词的一个词
  • Forth:以那个词结束
  • 第五:包含任何事物中的术语

所以我首先从 DB 中获取实体:

result = entities.Where(e => e.Name.Contains(searchTerm)).ToList();

然后我在内存中重新排列它们:

var sortedEntities = result.Where(e => e.Name.ToLower() == searchTerm.ToLower())
    .Union(result.Where(e => e.Name.StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase)))
    .Union(result.Where(e => e.Name.Contains($" {searchTerm} ")))
    .Union(result.Where(e => e.Name.EndsWith(searchTerm, StringComparison.OrdinalIgnoreCase)))
    .Union(result.Where(e => e.Name.Contains(searchTerm)));

在我添加分页之前它工作正常。现在,如果完全匹配在第 2 页(来自 DB 的数据中),它将不会首先显示。

我能想到的唯一解决方案是分离请求(在这种情况下是 5 个请求)并手动跟踪页面大小。我的问题是,有没有办法告诉 DB 尊重该顺序并在一次 DB 行程中获取排序数据?

【问题讨论】:

  • 请提供您的分页代码,目前无法使用。
  • entities.Where(e => e.Name.Contains(searchTerm)).Skip((pageNumber - 1) * pageSize) .Take(pageSize).ToList();
  • 一个主要问题是,由于 StringComparison.OrdinalIgnoreCase 您正在使用不受支持的字符串方法,这使得 EF-core 自动切换到客户端评估。您可能不需要此比较,因为数据库排序规则可能不区分大小写。请先检查一下,因为这对您的问题的可能解决方案很重要。
  • @Gert Arnold - 我将Unions 移至请求IQueryable 并删除了StringComparison,看来它正在工作。非常感谢。
  • 然后您可以将Unions 转换为OrderByDescending/ThenByDescending 子句以获得最佳匹配。

标签: sql asp.net entity-framework linq entity-framework-core


【解决方案1】:

我花了一些时间才意识到您使用Union 试图通过“匹配强度”对数据进行排序:首先是完全匹配的那些,然后是与不同大小写匹配的那些,等等。当我看到@ 987654324@s 带有谓词,我的巴甫洛夫条件思维将其转换为ORs。我不得不从thinking fast to slow切换。

所以问题是没有可预测的排序。毫无疑问,链接的Union 语句确实会产生确定性的最终排序顺序,但不一定是Unions 的顺序,因为每个Union 还执行一个隐含的Distinct。一般规则是,如果您需要特定的排序顺序,请使用OrderBy 方法。

说了这么多,然后……

var result = entities
    .Where(e => e.Name.Contains(searchTerm))
    .Skip((pageNumber - 1) * pageSize)
    .Take(pageSize).ToList();

...想要的结果似乎可以通过以下方式获得:

var sortedEntities = result
    .OrderByDescending(e => e.Name == searchTerm)
    .ThenByDescending(e => e.Name.ToLower() == searchTerm.ToLower())
    .ThenByDescending(e => e.Name.StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase))
    ... etc.

(降序,因为false 订单before true)

但是,如果匹配项多于pageSize,则排序为时已晚。如果pageSize = 20 和项目 21 是第一个完全匹配的项目,则该项目不会出现在第 1 页上。这意味着:应该在分页之前进行排序。

第一步是从第一条语句中删除.ToList()。如果删除它,第一条语句是一个IQueryable 表达式,Entity Framework 能够将完整的语句组合成一个 SQL 语句。下一步是将Skip/Take 移动到完整语句的末尾,它也将成为 SQL 的一部分。

var result = entities.Where(e => e.Name.Contains(searchTerm));

var sortedEntities = result
    .OrderByDescending(e => e.Name == searchTerm)
    .ThenByDescending(e => e.Name.ToLower() == searchTerm.ToLower())
    .ThenByDescending(e => e.Name.StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase))
    ... etc
    .Skip((pageNumber - 1) * pageSize)
    .Take(pageSize).ToList();

但现在又出现了一个新问题。

由于不支持与 StringComparison.OrdinalIgnoreCase 的字符串比较,实体框架将自动切换到 client-side evaluation 以获取部分语句。所有过滤后的结果都会从数据库中返回,但大部分排序和所有分页都会在内存中完成。

当过滤器很窄时这可能还不错,但当它很宽时就很糟糕了。因此,最终,要正确执行此操作,您必须删除 StringComparison.OrdinalIgnoreCase 并使用稍微不那么精致的匹配强度来解决。带我们去

最终结果

var result = entities.Where(e => e.Name.Contains(searchTerm));
var sortedEntities = result
    .OrderByDescending(e => e.Name == searchTerm)
    .ThenByDescending(e => e.Name.StartsWith(searchTerm))
    .ThenByDescending(e => e.Name.Contains($" {searchTerm} "))
    .ThenByDescending(e => e.Name.EndsWith(searchTerm))
    .ThenByDescending(e => e.Name.Contains(searchTerm))
    .Skip((pageNumber - 1) * pageSize)
    .Take(pageSize).ToList();

为什么“不那么精致”?因为,根据您的 cmets,数据库排序规则不区分大小写,因此 SQL 无法在不添加 COLLATE 语句的情况下区分完全匹配按大小写。这是 LINQ 无法做到的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多