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

时间:2010-11-17 01:10:10

标签: sql nhibernate sql-server-2008 search

我有一个应用程序允许基于几个不同的标准(总共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或native sql,因为ICriteria或Linq不提供执行单个查询所需的灵活性,也不提供所需的组合操作。

3 个答案:

答案 0 :(得分:2)

我通过将搜索性能计数器保存在表中来完成此操作。基本上监控搜索过滤的行的平均百分比和运行时间。

然后,我创建了一个基于的性能指标 TotalNumberOfRowsToSearch * Percent_Not_Matched / RunTimeInSeconds 这个数字是它可以过滤掉的每秒行数的直接相关性。平均数千次运行,这是一个相当好的预测。

然后我按顺序运行每个查询,最高性能数字为第一。

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

如果您正在执行逻辑OR,则仅对结果中的每个后续查询运行NOT在以前的搜索结果中。

通过这种方式,您的查询将根据索引和数据类型进行更改。

如果您想要一个不太动态的解决方案,只需计算搜索的每个部分的性能数据,并首先使用性能更好的解决方案。记住一个运行在55毫秒但匹配99%结果的查询不如在1秒内运行并且匹配1%结果的查询有用,所以要小心结果可能违背你的初步想法。

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

答案 1 :(得分:2)

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

类似的东西:

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;
            }
        }

答案 2 :(得分:0)

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

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