SQL应用之依赖计算与回路检测

本文介绍了一种在数据库设计中的依赖关系处理方案,包括依赖计算、循环检测等关键步骤,并通过薪资模块实例展示了如何确保数据的有效性和正确计算顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

依赖关系是数据库表中比较常见的一种设计,一个典型的应用就是薪资模块。具备这种依赖关系的数据结构需要解决“依赖计算”“循环检测”“依赖查看”等问题。本文旨在提供解决这些问题的一种通用解决方案。

什么是依赖计算?

举一个典型的例子,设计薪资模块,一个工资项,即可以是固定值value,也可以是由其它的一个或多个工资项通过表达式得到的结果。

工资项依赖工资项
Avalue1
Bvalue2
CA - B + value3
DC * 0.8

很明显工资项A、B不依赖任何项,工资项C依赖于A和B,而D依赖于C。在计算最终工资时,必须按照这种依赖顺序,才能够得到最终的准确结果。把工资按这种依赖顺序计算出来,就是“依赖计算”。

什么是回路检测呢?

细心的人,也许很快就反应过来,如果这里的依赖关系不严格(如A依赖B,B又依赖A),出现“依赖死循环”,该怎么办呢?这就是下面要解决的问题:“回路检测”,也许称为“循环检测”更恰当。在计算依赖之前必须通过“回路检测”保证数据有效性。

回路检测,除了找出具有循环依赖的数据项之外,还要找出这些循环依赖的过程。

-- @RTable存储依赖关系的数据
declare @RTable table(Id int,Name varchar(50),RefId int)
-- @table数据项列表,OrderNo表示其计算顺序编号
declare @table table(Id int,Name varchar(50),OrderNo int)

insert into @RTable values(1,'A',2),(2,'B',3),(3,'C',4),(4,'D',1),
    (5,'E',6),(6,'F',7),(7,'G',5),
    (8,'H',9),(9,'I',8),
    (10,'J',10),
    (11,'K',12),(12,'L',13),(12,'L',14),(13,'M',15),(14,'N',16),(15,'O',16)

insert into @table(Id,Name) select distinct Id,Name from @RTable
insert into @table(Id,Name) values(28,'AB'),(28,'AC'),(16,'P')

-- ===================== 回路检测(正向) =======================
-- 1、检测回路(IsCircle就是用于判断是否循环)
declare @RouteTable table(RouteNo int,RouteOrder int,Id int,IsCircle bit)
declare @resultTable table(RowNum int identity(1,1),Id int,Name varchar(50),InCircle bit,Marked bit/*查找回路路线使用*/)
insert into @resultTable(Id,Name) select Id,Name from @table

declare @i int,@maxi int,@tmpId int,@tmpRefId int--,@flag bit = 0
set @i = 1
set @maxi = (select COUNT(*) from @resultTable)
while @i <= @maxi
  begin
    select @tmpId = Id from @resultTable where RowNum = @i

    set @tmpRefId = @tmpId
    --set @flag = 0
    while 1=1
      begin
        -- 判断引用到末结点时跳出循环
        if not exists(select 1 from @RTable where Id = @tmpRefId)
          begin
            break
          end
        -- 下一步
        select @tmpRefId = RefId from @RTable where Id = @tmpRefId 
        -- 判断循环引用时跳出循环
        if @tmpRefId = @tmpId
          begin
            --set @flag = 1
            update @resultTable set InCircle = 1 where RowNum = @i -- 更新状态
            break
          end
      end

    set @i += 1
  end

select * from @resultTable where InCircle = 1

-- 2、计算回路
declare @tmpNode table(Id int)
declare @RouteCount int = 1,@IsMarked bit
set @i = 1
set @maxi = (select COUNT(*) from @resultTable)
while @i <= @maxi
  begin
    select @tmpId = Id,@IsMarked = Marked from @resultTable where RowNum = @i
    if @IsMarked = 1
      begin
        set @i += 1
        continue
      end

    set @tmpRefId = @tmpId
    delete @tmpNode
    while 1=1
      begin
        -- 判断引用到末结点时跳出循环(非回路)
        if not exists(select 1 from @RTable where Id = @tmpRefId)
          begin
            break
          end
        -- 下一步
        select @tmpRefId = RefId from @RTable where Id = @tmpRefId 
        insert into @tmpNode select @tmpRefId
        -- 判断循环引用时跳出循环(是回路)
        if @tmpRefId = @tmpId
          begin
            --set @flag = 1
            insert into @RouteTable(RouteNo,RouteOrder,Id,IsCircle) select @RouteCount,ROW_NUMBER() over(order by Id),Id,1 from @tmpNode
            set @RouteCount += 1
            update @resultTable set Marked = 1 where Id in(select Id from @tmpNode)

            update @resultTable set InCircle = 1 where RowNum = @i -- 更新状态
            break
          end
      end

    set @i += 1
  end

select * from @RouteTable

---- ===================== 忽略策略计算依赖顺序 =======================
---- 排除自身依赖(原因:1->1,1->2 => 1->2)
--delete @RTable where Id = RefId
--update @table set OrderNo = 0 where Id not in(select Id from @RTable)

---- 3、采用回路忽略策略,计算依赖顺序
--declare @tmpTable table(Id int,RefMax int)
--while 1=1
--  begin
--  -- 清空临时表
--  delete @tmpTable

--  insert into @tmpTable(Id,RefMax)
--  select t.Id,t2.TMax 
--  from @table t 
--  left join (
--      select r.Id,COUNT(*) as Total,MAX(t.OrderNo) as TMax,
--          SUM(CASE WHEN t.OrderNo IS NOT NULL THEN 1 ELSE 0 END) as TCount
--      from @RTable r  
--          left join @table t on t.Id = r.RefId
--      group by r.Id
--  ) t2 on t2.Id = t.Id 
--  where t2.Total = t2.TCount
--      and t.OrderNo is null       

--  if not exists(select 1 from @tmpTable)
--    begin
--      break;
--    end

--  update @table set OrderNo = t.RefMax + 1 from @tmpTable t where t.Id = [@table].Id
--  end

---- 查看计算顺序
--select * from @table

---- 查询所有回路数据(从侧面查看回路数据)
--select * from @table where OrderNo is null

-- ======================= 向上向下查找所有依赖项 ========================
---- 4、向上找某结点所有依赖项
declare @Id int = 12
;WITH cte AS
(  
    -- 起始条件
    SELECT Id,RefId FROM @RTable as t WHERE Id = @Id
    UNION ALL  
    -- 递归条件
    SELECT t.Id,t.RefId FROM @RTable as t,cte t2 WHERE t.RefId = t2.Id and t.Id <> @Id
)
select c.Id,t.Name,c.RefId from cte c left join @table t on t.Id = c.Id

---- 5、向下找某结点所有依赖项:查看某结点所在路线之后的所有数据(在回路返回当前结点时中止)
--declare @Id int = 11
set @Id = 12
;WITH cte AS
(  
    -- 起始条件
    SELECT Id,RefId FROM @RTable as t WHERE Id = @Id
    UNION ALL  
    -- 递归条件
    SELECT t.Id,t.RefId FROM @RTable as t,cte t2 WHERE t.Id = t2.RefId and t.RefId <> @Id
)
select c.Id,t.Name,c.RefId from cte c left join @table t on t.Id = c.Id
### 有向无环图(DAG)概念 有向无环图(Directed Acyclic Graph, DAG),是一种特殊的有向图,在这种图形结构中,任意两个顶点之间不存在回路。即对于每一条边的方向而言,无法通过这些边形成闭合路径[^1]。 #### 性质描述 - **唯一性**:给定一组节点及其连接关系后形成的DAG是唯一的。 - **拓扑排序可行性**:由于不含有圈,因此可以对其进行有效的拓扑排序操作;也就是说能够找到一种排列方式使得所有的箭头都指向右侧方向。 - **子图继承特性**:如果删除某些结点连同它们所关联的所有边之后剩下的部分仍然是一个DAG。 ```java // Java代码片段用于检测是否存在环并判断是否为DAG public class DirectedGraph { private final int V; private int[] indegree; public boolean isDag() { Queue<Integer> queue = new LinkedList<>(); for (int i = 0; i < V; ++i) { if (indegree[i] == 0) queue.add(i); } int count = 0; while (!queue.isEmpty()) { Integer v = queue.poll(); ++count; // 对于v的每一个邻接点w for (Integer w : adj(v)) { --indegree[w]; if (indegree[w] == 0) queue.offer(w); } } return count == V; // 如果访问过的节点数等于总节点数则说明没有环存在 } } ``` ### 应用场景举例 - **编译器优化领域中的表达式求值顺序安排** 编译过程中会遇到复杂的算术逻辑运算组合情况,此时利用DAG可以帮助合理规划计算次序从而提高效率。 - **项目管理工具里的任务依赖关系建模** 如甘特图等软件用来表示各阶段工作之间的先后制约条件,确保只有当先决事项完成后才能启动后续活动。 - **数据库查询处理框架下的执行计划制定** 数据库管理系统内部为了高效完成SQL语句解析后的指令集转换过程,常采用基于DAG的方式来进行多表联查、聚合统计等功能模块的设计[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值