如何在SQL Server中编写带有2个表的递归查询

问题描述 投票:0回答:2

我有一个具有以下结构的表。表名:表0

结构如下

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

Process
-------
112
778
756

所有这些过程必须进入下表表名:表1

结构如下

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,否则表不返回任何行,最后的processid是父ID。

我没有参加过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数)。为什么会这样呢?它只需要显示一次结果。

c# sql-server common-table-expression recursive-query dynamicquery
2个回答
3
投票

没有光标的解决方案(我个人更喜欢):

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查询)不断从Table1(其Exec值等于先前CTE记录的parent值)和Table2(与Table1字段上的stepid相关)向CTE添加记录。 CTE中新添加的记录将具有以下字段值:

  • process将从之前的CTE记录中复制。
  • n将增加1。
  • Exec将得到加入的ExecTable1值的Exec值(等于之前的CTE记录的parent值)。
  • parent将 - 再次 - 从processid获得相应的Table2值,其中stepid值等于Table1stepid值。

整个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下方)将仅为CTE中的每个“最后”记录选择processparent字段(其中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. 通过将parent连接到Table1,CTE的锚点部分将进程的实际父级放在Table2字段中。此连接应该是左连接,因此没有父项的进程(如果可能)也将包含在结果中;他们的parent值将等于他们自己的进程ID。
  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)。这个逻辑是使用WHERE子句中的EXISTS运算符中的子查询实现的,但当然也可以使用CTE本身的LEFT JOIN来实现。
  4. 由于第3点中描述的升级逻辑,CTE的列n不再使用并已被删除。
  5. 为了避免数据中存在“菱形图案”(工艺A具有父B和C,并且进程B和C都具有父D)的重复,在主查询的SELECT子句中使用DISTINCT以避免重复(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
投票

您是否尝试使用游标并将第一个表存储在游标中并通过直到游标末尾来获取进程ID。

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;

© www.soinside.com 2019 - 2024. All rights reserved.