Общая стратегия сложных многоэтапных поисков - PullRequest
5 голосов
/ 17 ноября 2010

У меня есть приложение, которое позволяет искать определенную сущность на основе нескольких различных критериев (где-то порядка 20 различных методов). Я хочу, чтобы была возможность объединить результаты нескольких поисков, чтобы создать один набор результатов.

Например:

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

Предположим, что поиски достаточно сложны по своей природе, так что объединить их в один логический запрос невозможно (из-за сложных взаимосвязей, которые необходимо запрашивать и т. Д.).

Предположим также, что количество задействованных сущностей (вероятно) делает какую-либо стратегию в памяти недопустимой.

Мои первоначальные мысли были чем-то вроде:

1) Выполнить поиск отдельно, получить список подходящих «идентификаторов сущностей» для каждого из них, а затем выполнить поиск «корневого уровня» на основании этих данных.

Например:

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, использующее NHibernate ORM, поддерживаемое базой данных SQL Server 2008 R2.

Я уже решил использовать для этого либо hql, либо собственный sql, поскольку ICriteria или Linq не обеспечивают гибкость, необходимую для выполнения отдельных запросов, а также необходимые операции объединения.

Ответы [ 3 ]

2 голосов
/ 23 ноября 2010

Мой подход с использованием 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;
            }
        }
2 голосов
/ 17 ноября 2010

Я сделал это, сохранив счетчики производительности поиска в таблице. В основном отслеживание среднего процента строк, которые фильтрует поиск и время выполнения.

Затем я создаю показатель производительности на основе TotalNumberOfRowsToSearch * Percent_Not_Matched / RunTimeInSeconds Эта цифра является прямой корреляцией строк в секунду, которую она может отфильтровать. В среднем за тысячи трасс это довольно хороший прогноз.

Затем я запускаю каждый запрос по порядку с наивысшей производительностью.

Если вы выполняете логическое И для общего результата, запускайте каждый последующий запрос только для результатов предыдущего запроса.

Если вы выполняете логическое ИЛИ, запускайте каждый последующий запрос только для результатов НЕ В комбинированных предыдущих результатах поиска.

При этом ваш запрос будет меняться в зависимости от индексов и типов данных.

Если вам нужно менее динамичное решение, просто рассчитайте показатели производительности для каждой части поиска и в первую очередь используйте более эффективные. Помните, что запрос, который выполняется за 55 мс, но соответствует 99% результатов, не так полезен, как запрос, который выполняется за 1 секунду и соответствует 1% результатов, поэтому будьте осторожны, поскольку результаты могут пойти вразрез с вашими первоначальными идеями.

Просто обратите внимание на ошибку деления на 0 при расчете показателей производительности.

0 голосов
/ 18 ноября 2010

Если вы можете использовать ICriteria, я бы порекомендовал это. Это может резко сократить объем кода при сложном поиске. Например, разница между использованием одного поиска и его использованием в качестве подзапроса в совокупном поиске будет дополнительной проекцией.

Я еще не пытался разделить сложные поиски и запустить их отдельно. Объединение всего поиска в один вызов к базе данных, согласно вашему второму примеру, до сих пор работало для меня. Если я не получаю приличного времени ответа (минуты, а не секунды), советник по настройке ядра СУБД оказался неоценимым с предлагаемыми индексами и статистикой.

...