Хороша ли рекурсия в SQL Server? - PullRequest
9 голосов
/ 10 октября 2008

У меня есть таблица на сервере SQL, которая имеет нормальную древовидную структуру Item_ID, Item_ParentID. Предположим, я хочу выполнить итерацию и получить всех ДЕТЕЙ с определенным Item_ID (на любом уровне).

Рекурсия кажется интуитивным кандидатом на решение этой проблемы, и я могу написать функцию SQL Server для этого.

Повлияет ли это на производительность, если в моей таблице много записей? Как избежать рекурсии и просто запросить таблицу? Пожалуйста, какие-либо предложения?

Ответы [ 8 ]

5 голосов
/ 10 октября 2008

С новым MS SQL 2005 вы можете использовать ключевое слово WITH

Проверьте этот вопрос и особенно этот ответ .

В Oracle вы можете использовать ключевое слово CONNECT BY для генерации иерархических запросов ( синтаксис ).

AFAIK с MySQL вам придется использовать рекурсию.

В качестве альтернативы, вы всегда можете создать кеш-таблицу для своих записей parent-> дочерние отношения

2 голосов
/ 10 октября 2008

Как общий ответ, в SQL Server можно сделать довольно сложные вещи, которые обычно требуют рекурсии, просто с помощью итерационного алгоритма. Мне удалось создать XHTML-парсер в Transact SQL, который работал на удивление хорошо. Преттификатор кода, который я написал, был сделан в хранимой процедуре. Это не элегантно, это скорее похоже на буйвол, балетный спектакль. но это работает.

1 голос
/ 10 октября 2008

У Джо Селко есть книга (<- ссылка на Amazon), специально посвященная древовидным структурам в базах данных SQL. Хотя для вашей модели вам потребуется рекурсия, и в ней наверняка могут возникнуть проблемы с производительностью, существуют альтернативные способы моделирования древовидной структуры в зависимости от конкретной проблемы, которые могут избежать рекурсии и повысить производительность. </p>

1 голос
/ 10 октября 2008

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

В SQL 2k5 вы можете использовать общее табличное выражение для обработки этой рекурсии:

WITH Managers AS 
( 
--initialization 
SELECT EmployeeID, LastName, ReportsTo  
FROM Employees 
WHERE ReportsTo IS NULL 
UNION ALL 
--recursive execution 
SELECT e.employeeID,e.LastName, e.ReportsTo 
FROM Employees e INNER JOIN Managers m  
ON e.ReportsTo = m.employeeID 
) 
SELECT * FROM Managers  

или другое решение - объединить иерархию в другую таблицу

Employee_Managers
ManagerId (ПК, FK для таблицы сотрудников)
EmployeeId (PK, FK для таблицы Employee)

Все родительские дочерние корабли будут храниться в этой таблице, поэтому, если Менеджер 1 управляет Менеджером 2, управляет сотрудником 3, таблица будет выглядеть так:

ManagerId EmployeeId
1         2
1         3
2         1

Это позволяет легко запрашивать иерархию:

select * from employee_managers em 
inner join employee e on e.employeeid = em.employeeid and em.managerid = 42

Что вернет всех сотрудников, у которых есть менеджер 42. Потенциалом будет повышение производительности, но недостатком будет поддержание иерархии

1 голос
/ 10 октября 2008

Вы используете SQL 2005?

Если это так, вы можете использовать Common Table Expressions для этого. Что-то вроде этого:

;
with CTE (Some, Columns, ItemId, ParentId) as 
(
    select Some, Columns, ItemId, ParentId
    from myTable 
    where ItemId = @itemID
    union all
    select a.Some, a.Columns, a.ItemId, a.ParentId
    from myTable as a
    inner join CTE as b on a.ParentId = b.ItemId
    where a.ItemId <> b.ItemId
)
select * from CTE
0 голосов
/ 10 октября 2008

Вам вообще не нужна рекурсия .... Обратите внимание, я изменил столбцы на ItemID и ItemParentID для простоты ввода ...

DECLARE @intLevel INT
SET @intLevel = 1

INSERT INTO TempTable(ItemID, ItemParentID, Level)
SELECT ItemID, ItemParentID, @intLevel
WHERE ItemParentID IS NULL

WHILE @intLevel < @TargetLevel
BEGIN
     SET @intLevel = @intLevel + 1
     INSERT INTO TempTable(ItemID, ItemParentID, Level)
          SELECt ItemID, ItemParentID, @intLevel
          WHERE ItemParentID IN (SELECT ItemID FROM TempTable WHERE Level = @intLevel-1)
     -- If no rows are inserted then there are no children
     IF @@ROWCOUNT = 0
       BREAK
END

SELECt ItemID FROM TempTable WHERE Level = @TargetLevel
0 голосов
/ 10 октября 2008

Вам не нужна рекурсия для детей - вы смотрите только на уровень ниже (т.е. select * from T where ParentId = @parent) - вам нужна только рекурсия для всех потомков .

В SQL2005 вы можете получить потомков с помощью:

with AllDescendants (ItemId, ItemText) as (
    select t.ItemId, t.ItemText 
        from [TableName] t
    where t.ItemId = @ancestorId
    union
    select sub.ItemId, sub.ItemText 
        from [TableName] sub
            inner join [TableName] tree
            on tree.ItemId = sub.ParentItemId
)
0 голосов
/ 10 октября 2008

Возможно, более подробная информация в порядке.

Если у вас есть отношения мастер-детали, как вы описываете, то не получит ли просто JOIN то, что вам нужно?

Как в:

SELECT
  SOME_FIELDS
FROM
  MASTER_TABLE MT
 ,CHILD_TABLE CT
WHERE CT.PARENT_ID = MT.ITEM_ID
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...