代码之家  ›  专栏  ›  技术社区  ›  DanP

复杂多阶段搜索的一般策略

  •  5
  • DanP  · 技术社区  · 14 年前

    我有一个应用程序,允许根据几个不同的标准(总共20种不同的方法的顺序)搜索某个实体。我希望能够合并多个搜索的结果,以便生成单个结果集。

    例如:

    results = (entities from search 1 AND entities from search 2) OR (entities from search 3)
    

    让我们假设搜索在性质上足够复杂,以至于无法将它们组合成一个逻辑查询(因为需要查询的复杂关系等)。

    我们还假设所涉及的实体的数量(可能)使得任何类型的内存策略都不可行。

    我最初的想法大致如下:

    1) 分别执行搜索,从每个搜索中获取匹配的“实体ID”列表,然后基于这些搜索执行“根级别”搜索。

    例如:

    select * from entity e
    where 
    (e.Id in (search 1 id list) AND e.Id in(search 2 id list))
    OR e.Id in (search 3 id list)
    

    2) 执行外部查询,根据我的(复杂)子查询返回的结果选择实体。

    例如:

    select * from entity e
    where (e.Id in (select e1.id from entity e1 where ...) AND e.Id in (select e2.id from entity e2 where...))
    OR e.Id in (select e3.id from entity e3 where...)
    

    显然,为了便于说明,这些示例被大大简化了;单独的查询将更加复杂,并且它们的组合将是任意的(我刚刚在这里演示了一个有代表性的示例)。

    我很想听听别人如何处理这种情况的建议。我当然愿意接受任何我没有在上面探索过的可能性。

    作为参考,这是一个.NET应用程序,它使用了一个由SQL Server 2008 R2数据库支持的NHibernate ORM。

    我已经决定使用HQL或本机SQL作为IrrTeria/LINQ不提供执行单个查询所需的灵活性,也不需要所需的组合操作。

    3 回复  |  直到 14 年前
        1
  •  2
  •   John    14 年前

    我把搜索性能计数器保存在一个表中。基本上监视搜索筛选的行的平均百分比和运行时。

    然后根据 TotalNumberOfRowsToSearch*不匹配的百分比/RunTimeInSeconds 这个数字是每秒可以过滤掉的行数的直接关联。平均上千次,这是一个相当好的预测。

    然后,我按照最高性能图1的顺序运行每个查询。

    如果对总结果执行逻辑和,则仅对上一个查询的结果运行每个后续查询。

    如果要执行逻辑“或”,请仅对合并的先前搜索结果以外的结果运行每个后续查询。

    这样做,查询将根据索引和数据类型进行更改。

    如果您想要一个动态性较低的解决方案,只需计算搜索每个部分的性能数字,然后首先使用性能较好的部分。记住,一个运行时间为55ms但与99%的结果匹配的查询不如运行时间为1秒且与1%的结果匹配的查询有用,所以要小心,结果可能与您最初的想法相悖。

    计算性能数据时,请注意除以0的误差。

        2
  •  2
  •   Variant    14 年前

    我使用Linq的方法是建立一个where表达式的列表来构造复杂的条件,并最终将它们一起应用。

    像这样的:

    List<Expression<Func<WorkItem, bool>>> whereExpressions = new List<Expression<Func<WorkItem, bool>>>();
    if (!string.IsNullOrEmpty(searchMask))
                {
                    whereExpressions.Add(
                                            x =>
                                            (x.Name.ToLower().IndexOf(searchMask.ToLower()) > -1 ||
                                             x.Id.ToString().IndexOf(searchMask) > -1 ||
                                             (x.Description != null &&
                                              x.Description.ToLower().IndexOf(searchMask.ToLower()) > -1)));
                }
    
    whereExpressions.Add(x => (x.Status == status));   
    

    最终,在构建表达式列表之后,应用表达式:

    IQueryable<WorkItem> result = Session.Linq<WorkItem>();
    foreach (Expression<Func<WorkItem, bool>> whereExpression in whereExpressions)
                {
                    result = result.Where(whereExpression);
                }
    

    您还可以在排序方法中提供灵活性,并允许分页:

    IQueryable<WorkItem> items;
                if (ascOrDesc == "asc")
                {
                    items = result.OrderBy(DecideSelector(indexer)).Skip(startPoint - 1).Take(numOfrows);
                }
                else
                {
                    items = result.OrderByDescending(DecideSelector(indexer)).Skip(startPoint - 1).Take(numOfrows);
                }
    

    在哪里? DecideSelector 定义如下:

    private Expression<Func<WorkItem, object>> DecideSelector(string fieldCode)
            {
                switch (fieldCode)
                {
                    case "Deadline":
                        return item => item.Deadline;
                    case "name":
                        return item => item.Name;
                    case "WiStatus":
                        return item => item.Status;
                    case "WiAssignTo":
                        return item => item.AssignedUser;
                    default:
                        return item => item.Id;
                }
            }
    
        3
  •  0
  •   Adam Boddington    14 年前

    如果你能用ICriteria,我推荐。它可以大大减少复杂搜索的代码量。例如,单独使用一个搜索与在聚合搜索中将其用作子查询之间的区别是添加了投影。

    我还没有试着把复杂的搜索分开,单独运行它们。根据您的第二个示例,将整个搜索组合到对数据库的一个调用中,到目前为止对我很有用。如果我没有得到一个合适的响应时间(分而不是秒),数据库引擎优化顾问已经证明了它的宝贵的索引和统计建议。