Как написать рекурсивный запрос с 2 таблицами в SQL Server - PullRequest
0 голосов
/ 08 апреля 2019

У меня есть таблица со следующей структурой.Имя таблицы: Table0

Структура выглядит следующим образом:

Select process from Table0 where Name like '%Aswini%'

Process
-------
112
778
756

Все эти процессы должны идти в таблицу ниже. Имя таблицы: Table1

Структура выглядит следующим образом

Select Exec, stepid, condition 
from Table1 
where Exec = 112

Exec   stepid condition
-----------------------
112     2233     0
112     2354     0
445     3455     0

Структура второй таблицы 'Таблица 2' выглядит следующим образом:

Select stepid, processid  
from Table2 
where stepid = 2233

Stepid processid
-----------------
2233      445
2354      566
3455      556

Таблица1 stepid вводится в Таблицу2 stepid, а Таблица2 Processid вводится в Таблицу1 Exec,Я должен рекурсивно получать processID до тех пор, пока условие не станет равным 0, в противном случае таблица не возвращает строк, а окончательный идентификатор процесса является родительским идентификатором.

Я не работал над CTE.Поэтому я использовал простое соединение, чтобы получить следующий результат.

select b.processid 
from Table1 a 
inner join Table2 b on a.stepid = b.stepid 
where a.condition = 0 
  and a.exec = 112(parent from table0)

Приведенный выше запрос даст мне родителя для Exec 112, если он удовлетворяет условию.

Я должен снова ввестиродительский запрос и выполнить его.

Я могу добиться этого с помощью C #, поместив его в цикл.Но я хочу это только в SQL Server.Это достижимо?

Отредактировано

Когда я выполняю CTE, я получаю следующий результат

Process Parent
  112     445
  112     566
  112     445
  112     566

Если начальный процесс имеет 2 exec, то конечная родительская структура процесса имеет виддублируется дважды (номер exec).Почему это происходит.Результат должен отображаться только один раз.

Ответы [ 2 ]

3 голосов
/ 08 апреля 2019

Решение без курсора (которое я лично предпочитаю):

WITH [CTE] AS
(
    SELECT
        T1.[Exec] AS [process],
        1 AS [n],
        T1.[Exec],
        T1.[Exec] AS [parent]
    FROM
        [Table1] AS T1
UNION ALL
    SELECT
        C.[process],
        C.[n] + 1,
        T1.[Exec],
        T2.[processid]
    FROM
        [CTE] AS C
        INNER JOIN [Table1] AS T1 ON T1.[Exec] = C.[parent]
        INNER JOIN [Table2] AS T2 ON T2.[stepid] = T1.[stepid]
)
SELECT C.[process], C.[parent]
FROM [CTE] AS C
WHERE C.[n] = (SELECT MAX([n]) FROM [CTE] WHERE [process] = C.[process])

Пояснение:

Якорная часть общего табличного выражения (запрос SELECT перед UNION ALL) определяет начальную точку операции. В этом случае он просто выбирает все данные из Table1 и имеет четыре поля:

  • process будет содержать значение процесса (Exec значение), родительский элемент которого должен быть определен.
  • n будет содержать порядковый номер, начиная с 1.
  • Exec будет содержать «смещающее» значение для объединения записей в следующей «рекурсивной» части общего табличного выражения.
  • parent будет содержать соответствующее поле processid из Table2, которое представляет собой прямой родительский элемент значения Exec.

Это выражение привязки даст следующие данные:

process      n      Exec      parent
112          1      112       112
445          1      445       445

Рекурсивная часть общего табличного выражения (запрос SELECT после UNION ALL) продолжает добавлять записи в CTE из Table1 (где его значение Exec равно значению parent предыдущего CTE запись) и Table2 (связано с Table1 в полях stepid). Эти новые записи в CTE будут иметь следующие значения полей:

  • process будет скопировано из предыдущей записи CTE.
  • n будет увеличено на 1.
  • Exec получит значение Exec объединенного значения Table1 Exec (равное значению parent предыдущей записи CTE).
  • parent будет - снова - получит соответствующее значение processid из Table2, где его значение stepid равно Table1 stepid значению.

Весь CTE даст следующие результаты:

process      n      Exec      parent
112          1      112       112
112          2      112       445
112          3      445       556
445          1      445       445
445          2      445       556

Основной запрос (ниже CTE) выберет только поля process и parent для каждой «последней» записи в CTE (где значение n является наибольшим значением для этого конкретного process значение, которое определяется с помощью подзапроса).

Это дает следующий конечный результат:

process      parent
445          556
112          556

Надеюсь, это немного поможет.

Изменить относительно обновления в вопросе относительно 3-й таблицы Table0:

Предполагая, что ваш запрос SELECT [process] FROM [Table0] WHERE [Name] LIKE '%Aswini%' будет содержать допустимые процессы для возврата из вышеприведенного запроса, необходимо изменить только предложение WHERE основного запроса, приведенного выше.

Предыдущий WHERE-пункт:

WHERE C.[n] = (SELECT MAX([n]) FROM [CTE] WHERE [process] = C.[process])

Обновлено WHERE-предложение:

WHERE
    C.[n] = (SELECT MAX([n]) FROM [CTE] WHERE [process] = C.[process]) AND
    C.[process] IN (SELECT [process] FROM [Table0] WHERE [Name] LIKE '%Aswini%')

Изменить в отношении возможных дубликатов, когда процессы имеют более одного родителя

Если процесс имеет более одного родителя (??), приведенный выше запрос создает дубликаты. Чтобы устранить дубликаты и обеспечить более надежный способ определения самого верхнего родителя процесса, были внесены следующие изменения:

  1. Якорная часть CTE помещает фактического родителя процесса в поле parent, соединяя Table1 с Table2. Это соединение должно быть левым, так что процессы без родителей (если возможно) также будут включены в результаты; их parent значение будет равно их собственному идентификатору процесса.
  2. Рекурсивная часть CTE должна добавлять родителей только для процессов, у которых есть фактический родительский элемент (где поле process не равно parent). Это позволяет избежать бесконечных циклов в рекурсивности (если это возможно).
  3. Основной запрос должен отфильтровывать все записи, в которых значение поля parent также используется в другой записи результата в качестве значения поля exec для того же базового процесса (значение в поле process ). Потому что в этом случае поле parent не является окончательным родительским значением, и эта другая запись результата может быть более подходящим кандидатом для содержания фактического родителя. Другими словами: если у процесса A есть родительский B, а у процесса B родительский C, в CTE есть три связанных результата: (A, A, B), (A, B, C) и (B, B, C). ). Результат (A, A, B) недействителен, поскольку в результатах также доступен более подходящий кандидат (A, B, C). Окончательные результаты должны включать (A, C) и (B, C), но не (A, B).Эта логика реализована с использованием подзапроса в операторе EXISTS в предложении WHERE, но она также может быть реализована с использованием LEFT JOIN и на самом CTE.
  4. Из-за обновленной логики, описанной в пункте 3, столбец n CTE больше не используется и был удален.
  5. Чтобы избежать дублирования в случае «ромбовидной структуры» в данных (у процесса A есть родительские элементы B и C, а у обоих процессов B и C есть родительский D), DISTINCT используется в предложении SELECT основного запроса, чтобы избежать дублирования (A, D).

Окончательный запрос будет выглядеть так:

WITH [CTE] AS
(
    SELECT
        T1.[exec] AS [process],
        T1.[exec],
        COALESCE(T2.[processid], T1.[exec]) AS [parent]
    FROM
        [Table1] AS T1
        LEFT JOIN [Table2] AS T2 ON T2.[stepid] = T1.[stepid] 
UNION ALL
    SELECT
        C.[process],
        T1.[exec],
        T2.[processid]
    FROM
        [CTE] AS C
        INNER JOIN [Table1] AS T1 ON T1.[exec] = C.[parent]
        INNER JOIN [Table2] AS T2 ON T2.[stepid] = T1.[stepid]
    WHERE
        C.[parent] <> C.[process]
)
SELECT DISTINCT C.[process], C.[parent]
FROM [CTE] AS C
WHERE
    NOT EXISTS (SELECT 1 FROM [CTE]
                WHERE [process] = C.[process] AND [exec] = C.[parent])
    AND C.[process] IN (SELECT [process] FROM [Table0] WHERE [name] LIKE '%Aswini%')

Я надеюсь, что это работает достаточно хорошо для вас.

0 голосов
/ 08 апреля 2019

Попытаетесь ли вы использовать курсор и сохранить первую таблицу в курсоре и получить идентификатор процесса, пройдя до конца курсора.

DECLARE f_cursor CURSOR FOR     
Select Exec, stepid
from Table1

OPEN f_cursor    

FETCH NEXT FROM f_cursor     
INTO @exec,@stepid
WHILE @@FETCH_STATUS = 0    
BEGIN
select b.processid 
from Table1 a 
inner join Table2 b on a.stepid = b.stepid 
where a.condition = 0 
  and a.exec =@exec
//store the processid somewhere for later use.
END     
CLOSE f_cursor;    
DEALLOCATE f_cursor;

...