You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SQL Server中递归获取存储过程内的嵌套函数列表?

嘿,你已经搞定了最基础的一步——从存储过程里揪出直接调用的函数,这已经很棒了!接下来要实现函数的递归嵌套检索,核心思路其实很简单:把你已经找到的函数当作新的「检索目标」,重复你之前用来提取函数的逻辑,直到挖不出新的函数为止。下面我给你两种实用的实现思路,你可以根据你的数据库环境来选:

方法一:用递归CTE(适合SQL Server、PostgreSQL等支持的数据库)

如果你的数据库支持递归公共表表达式(CTE),这是最简洁的实现方式。假设你之前是用系统视图来提取函数调用的,那可以直接套递归逻辑:

-- 递归CTE:层层挖掘函数的嵌套调用
WITH RecursiveFunctionCalls AS (
    -- 锚点成员:从存储过程提取直接调用的函数
    SELECT
        OBJECT_NAME(sp.object_id) AS ParentObject,
        sp.object_id AS ParentObjectId,
        OBJECT_NAME(re.referenced_id) AS CalledFunction,
        re.referenced_id AS CalledFunctionId,
        1 AS Depth  -- 标记层级,1表示存储过程直接调用
    FROM sys.procedures sp
    CROSS APPLY sys.dm_sql_referenced_entities(OBJECT_SCHEMA_NAME(sp.object_id) + '.' + OBJECT_NAME(sp.object_id), 'OBJECT') re
    WHERE re.referenced_entity_name IS NOT NULL
      AND re.referenced_class_desc = 'SQL_SCALAR_FUNCTION'  -- 可按需调整为TABLE_VALUED_FUNCTION或去掉筛选所有函数

    UNION ALL

    -- 递归成员:从已发现的函数里提取它们调用的函数
    SELECT
        OBJECT_NAME(rfc.CalledFunctionId) AS ParentObject,
        rfc.CalledFunctionId AS ParentObjectId,
        OBJECT_NAME(re.referenced_id) AS CalledFunction,
        re.referenced_id AS CalledFunctionId,
        rfc.Depth + 1 AS Depth
    FROM RecursiveFunctionCalls rfc
    CROSS APPLY sys.dm_sql_referenced_entities(OBJECT_SCHEMA_NAME(rfc.CalledFunctionId) + '.' + OBJECT_NAME(rfc.CalledFunctionId), 'OBJECT') re
    WHERE re.referenced_entity_name IS NOT NULL
      AND re.referenced_class_desc = 'SQL_SCALAR_FUNCTION'
      -- 关键:避免循环引用(比如函数A调用B,B又调用A)导致无限递归
      AND NOT EXISTS (
          SELECT 1
          FROM RecursiveFunctionCalls rc
          WHERE rc.ParentObjectId = re.referenced_id
            AND rc.CalledFunctionId = rfc.ParentObjectId
      )
)
-- 输出所有层级的函数调用关系
SELECT *
FROM RecursiveFunctionCalls
ORDER BY Depth, ParentObject;

代码解释:

  • 锚点成员:就是你之前已经实现的逻辑——从存储过程中提取直接调用的函数,同时标记层级为1。
  • 递归成员:把上一层找到的CalledFunction当作新的父对象,重复调用提取逻辑,层级加1。
  • 循环引用处理:通过判断避免互为调用的函数导致无限递归。

方法二:循环迭代法(通用所有数据库)

如果你的数据库不支持递归CTE,或者你更倾向于可控性更强的方式,可以用临时表+循环的方式实现:

-- 创建临时表存储所有函数调用关系,标记是否已处理
CREATE TABLE #FunctionCalls (
    ParentObject NVARCHAR(128),
    ParentObjectId INT,
    CalledFunction NVARCHAR(128),
    CalledFunctionId INT,
    Depth INT,
    Processed BIT DEFAULT 0  -- 0=未处理,1=已处理
);

-- 第一步:插入存储过程直接调用的函数
INSERT INTO #FunctionCalls (ParentObject, ParentObjectId, CalledFunction, CalledFunctionId, Depth)
SELECT
    OBJECT_NAME(sp.object_id),
    sp.object_id,
    OBJECT_NAME(re.referenced_id),
    re.referenced_id,
    1
FROM sys.procedures sp
CROSS APPLY sys.dm_sql_referenced_entities(OBJECT_SCHEMA_NAME(sp.object_id) + '.' + OBJECT_NAME(sp.object_id), 'OBJECT') re
WHERE re.referenced_entity_name IS NOT NULL
  AND re.referenced_class_desc = 'SQL_SCALAR_FUNCTION';

-- 循环处理未处理的函数,直到没有新函数被发现
WHILE EXISTS (SELECT 1 FROM #FunctionCalls WHERE Processed = 0 AND ParentObjectId IN (SELECT CalledFunctionId FROM #FunctionCalls))
BEGIN
    -- 插入当前未处理函数调用的新函数
    INSERT INTO #FunctionCalls (ParentObject, ParentObjectId, CalledFunction, CalledFunctionId, Depth)
    SELECT
        OBJECT_NAME(fc.CalledFunctionId),
        fc.CalledFunctionId,
        OBJECT_NAME(re.referenced_id),
        re.referenced_id,
        fc.Depth + 1
    FROM #FunctionCalls fc
    CROSS APPLY sys.dm_sql_referenced_entities(OBJECT_SCHEMA_NAME(fc.CalledFunctionId) + '.' + OBJECT_NAME(fc.CalledFunctionId), 'OBJECT') re
    WHERE fc.Processed = 0
      AND re.referenced_entity_name IS NOT NULL
      AND re.referenced_class_desc = 'SQL_SCALAR_FUNCTION'
      -- 避免重复插入相同的调用关系
      AND NOT EXISTS (
          SELECT 1
          FROM #FunctionCalls fc2
          WHERE fc2.ParentObjectId = fc.CalledFunctionId
            AND fc2.CalledFunctionId = re.referenced_id
      );

    -- 标记这些函数为已处理,避免重复解析
    UPDATE #FunctionCalls
    SET Processed = 1
    WHERE Processed = 0
      AND ParentObjectId IN (SELECT CalledFunctionId FROM #FunctionCalls);
END

-- 查看最终结果
SELECT ParentObject, CalledFunction, Depth
FROM #FunctionCalls
ORDER BY Depth, ParentObject;

-- 清理临时表
DROP TABLE #FunctionCalls;

代码解释:

  • 用临时表存储所有已发现的调用关系,通过Processed字段标记哪些函数已经被解析过。
  • 每次循环只处理未解析的函数,把它们调用的新函数插入表中,直到没有新的函数可以添加。
  • 同样加入了重复调用和循环引用的判断,保证逻辑稳定。

关键注意事项

  • 函数类型筛选:根据你的需求调整referenced_class_desc的值,比如要包含表值函数就改成'TABLE_VALUED_FUNCTION',去掉该条件则会提取所有类型的函数。
  • 权限问题:确保你的数据库账号有访问系统视图(如sys.procedures、sys.dm_sql_referenced_entities)的权限,否则会查询不到数据。
  • 自定义解析逻辑:如果你之前是通过解析存储过程的文本定义(比如用字符串函数找函数名)来提取函数的,只需要把上述代码中调用系统视图的部分替换成你的解析逻辑即可,核心思路不变——重复解析已发现的函数定义。

内容的提问来源于stack exchange,提问作者JJ.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:41:00