2014-10-20 2 views
1

Я использую MS SqlServer 2008. И у меня есть таблица «Пользователи». Эта таблица имеет идентификатор ключевого поля bigint. А также поле Parents varchar, которое кодирует всю цепочку родительских идентификаторов пользователя. Например:Иерархический SQL-запрос-запрос

пользователя стол:

ID | Parents 
1 | null 
2 | .. 
3 | .. 
4 | 3,2,1 

Здесь пользователь 1 не имеет родителей и пользователь 4 имеет цепь родителей 3-> 2-> 1. Я создал функцию, которая анализирует поле «Родители» пользователя и возвращает таблицу результатов с идентификаторами пользователя bigint.

Теперь мне нужен запрос, который будет выбирать и присоединять идентификаторы некоторых запрашиваемых пользователей и их родителей (порядок пользователей и их родителей не важен). Я не эксперт по SQL, поэтому все, что я мог придумать, следующее:

WITH CTE AS(
SELECT 
    ID, 
    Parents 
FROM 
[Users] 
WHERE 
(
    [Users].Name = 'John' 
) 

UNION ALL 

SELECT 
    [Users].Id, 
    [Users].Parents 
FROM [Users], CTE 
WHERE 
(
    [Users].ID in (SELECT * FROM GetUserParents(CTE.ID, CTE.Parents)) 
)) 
SELECT * FROM CTE 

И в основном это работает. Но производительность этого запроса очень плохая. Я считаю, ГДЕ .. IN .. выражение здесь - горло бутылки. Насколько я понимаю, вместо того, чтобы просто присоединяться к первому подзапросу CTE (идентификаторы найденных пользователей) с результатами GetUserParents (идентификаторы родителей пользователей), он должен перечислить всех пользователей в таблице Users и проверить, является ли каждый из них частью результат функции (и, судя по плану выполнения), Sql Server делает отчетливый порядок результата для повышения производительности оператора WHERE .. IN .., который логичен сам по себе, но в целом не требуется для моей цели. 70% времени выполнения запроса). Поэтому мне интересно, как этот запрос может быть улучшен или кто-то может предложить другой подход для решения этой проблемы?

Спасибо за помощь!

+0

Что делает 'GetUserParents()' do? – DavidG

+0

Он получает в качестве идентификатора параметра пользователя и его varchar-представление родительских идентификаторов. И возвращает таблицу проанализированных родительских идентификаторов: – Kreol

+0

О, я вижу. Лично я бы не использовал столбец varchar и создал другую таблицу со строкой для каждого родителя. Таким образом, вам не нужна функция для синтаксического анализа строки. – DavidG

ответ

1

Рекурсивный запрос в вопросе выглядит излишним, поскольку вы уже составляете список идентификаторов, необходимых в GetUserParents. Возможно, измените это на SELECT от Users и GetUserParents() с WHERE/JOIN.

select Users.* 
from Users join 
    (select ParentId 
     from (SELECT * FROM Users where Users.Name='John') as U 
      cross apply [GetDocumentParents](U.ID, U.Family, U.Parents)) 
    as gup 
on Users.ID = gup.ParentId 

С GetDocumentParents ожидает скаляры и select... where создает таблицу, нам необходимо применить функцию к каждой строке таблицы (даже если мы «знаем», есть только один). Вот что делает apply.

Я использовал отступы, чтобы подчеркнуть концептуальные части запроса. (select...) as gup является предприятие Users is join 'd с; (select...) as U cross apply fn() является аргументом FROM.

Ключ знаний для понимания этого запроса, чтобы знать, как cross apply работы:

  • это часть пункта FROM (совершенно неожиданно, так что синтаксис в FROM (Transact-SQL))
  • он превращает табличное выражение слева от него, и результат становится аргументом в пользу FROM (я подчеркивал это с отступом)

Преобразования: для каждой строки, он

  • запускает выражение право таблицы его (в данном случае, вызов функции табличного значения), используя эту строку
  • добавляет к результату установите столбцы из строки, , а затем столбцы вызова. (В нашем случае таблица, возвращаемая из функции, имеет один столбец с именем ParentId)
    • Итак, если вызов возвращает несколько строк, добавленные записи будут той же строки из таблицы, добавленной каждой строкой из этой функции.

Это cross apply так строки будут добавлены только если функция возвращает ничего. Если бы это был другой вкус, outer apply, в любом случае будет добавлена ​​одна строка, за которой следует NULL в столбце функции, если он ничего не возвращает.

+0

Точно. Здесь не требуется рекурсия. Я просто не знаю, как объединить идентификаторы этих родителей. Если я написал код в C# .NET, я бы использовал нечто вроде Users.Where (user => user.Name = "John"). SelectMany (user => GetUserParents (user.Id)). Но, как я уже сказал, моих знаний в SQL недостаточно, чтобы найти изящную и эффективную альтернативу для моего рекурсивного варианта. Я вижу еще один простой подход с открытием курсора для каждого найденного ребенка, а затем извлечения его родителей и вставки их во временную таблицу.но это уродливое решение и курсоры, временные таблицы ... это слишком много для этой задачи. – Kreol

+0

Спасибо за образец, отлично выглядит, и я попробовал это раньше. Но он не работает со следующей ошибкой: «Идентификатор с несколькими частями« CTE.id »не может быть связан. Msg 4104, уровень 16, состояние 1, строка 44 Идентификатор многочастности« CTE.Parents »не может быть связаны «. Вероятно, таблицы CTE имеют некоторые ограничения и не могут быть использованы таким образом? – Kreol

+0

Или, скорее, запрос не знает, откуда взять CTE.ID. Вот как я использовал в моем запросе этого: [Пользователи], КТР ГДЕ ( [Пользователи] .id в (SELECT * FROM GetUserParents (CTE.ID, CTE.Parents)) )) – Kreol

0

Эта вещь «разбора» нарушает даже 1NF. Поле Parents содержит только непосредственный родительский элемент (предпочтительно внешний ключ), после чего можно получить полное поддерево с помощью рекурсивного запроса.

+0

Да, я бы сделал это точно. Но, к сожалению, я работаю с очень сложным устаревшим приложением и не могу изменить структуру данных и всего остального. Все, что я могу сделать здесь - просто добавьте некоторое выражение SELECT для извлечения необходимых данных из базы данных. – Kreol

+0

Вы можете обезвредить это путем внедрения дублирующих сущностей и триггеров обновлений, чтобы синхронизировать их - за счет производительности обновления. Разумеется, это не самое элегантное решение. –

Смежные вопросы