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

将含多递归成员的SQL Server CTE转换为PostgreSQL的报错解决

适配PostgreSQL的祖先数据递归CTE修改方案

问题背景

原SQL是Itzik Ben-Gan于2013年发表在ITProToday的SQL Server递归CTE,用于从家族树表中获取指定成员的所有祖先数据。在PostgreSQL中运行时会抛出错误:recursive reference to query "c" must not appear within its non-recursive term,而在SQL Server中移除recursive和Temporary关键字后可正常返回8行结果。

报错原因

PostgreSQL对递归CTE的语法规范要求更严格:递归CTE必须明确划分为非递归锚点段和递归段,且两段之间只能用一个UNION ALL分隔。原SQL的锚点段使用了两个UNION ALL拆分父亲、母亲的查询,导致PostgreSQL无法正确识别递归结构,误判递归引用位置。

修改后的SQL代码

DROP TABLE IF EXISTS tmp_FamilyTree;
CREATE temporary TABLE tmp_FamilyTree
(
    id INT NOT NULL CONSTRAINT PK_FamilyTree PRIMARY KEY,
    name VARCHAR(30) NOT NULL,
    mother INT NULL,
    father INT NULL
);
INSERT INTO tmp_FamilyTree
(
    id,
    name,
    mother,
    father
)
VALUES
(   1,
    'Balbo Baggins',
    NULL,
    NULL),
(   2,
    'Berylla Boffin',
    NULL,
    NULL),
(   3,
    'Mungo Baggins',
    1,
    2),
(   4,
    'Laura Grubb',
    NULL,
    NULL),
(   5,
    'Bungo Baggins',
    3,
    4),
(   6,
    'Belladonna Took',
    NULL,
    NULL),
(   7,
    'Bilbo Baggins',
    5,
    6),
(   8,
    'Largo Baggins',
    1,
    2),
(   9,
    'Tanta Hornblower',
    NULL,
    NULL),
(   10,
    'Fosco Baggins',
    8,
    9),
(   11,
    'Ruby Bolger',
    NULL,
    NULL),
(   12,
    'Dora Baggins',
    10,
    11),
(   13,
    'Drogo Baggins',
    10,
    11),
(   14,
    'Dudo Baggins',
    10,
    11),
(   15,
    'Primula Brandybuck',
    NULL,
    NULL),
(   16,
    'Frodo Baggins',
    13,
    15);

WITH recursive C
AS (
    -- 合并后的锚点查询:一次性获取目标成员的父母
    SELECT parent_id AS id, 1 AS lvl
    FROM (
        SELECT father AS parent_id FROM tmp_FamilyTree WHERE id = 16 AND father IS NOT NULL
        UNION ALL
        SELECT mother AS parent_id FROM tmp_FamilyTree WHERE id = 16 AND mother IS NOT NULL
    ) AS parents
    UNION ALL
    -- 递归查询:获取上一级成员的父母
    SELECT P.father AS id, C.lvl + 1 AS lvl
    FROM C
    JOIN tmp_FamilyTree AS P ON C.id = P.id
    WHERE P.father IS NOT NULL
    UNION ALL
    SELECT P.mother AS id, C.lvl + 1 AS lvl
    FROM C
    JOIN tmp_FamilyTree AS P ON C.id = P.id
    WHERE P.mother IS NOT NULL
)
SELECT C.id, FT.name, lvl
FROM C
JOIN tmp_FamilyTree AS FT ON C.id = FT.id;

修改说明

  • 将原锚点中拆分的父亲、母亲查询合并到一个子查询中,确保递归CTE的非递归锚点段是一个独立的整体。
  • 保留原递归逻辑不变,仅调整结构以符合PostgreSQL对递归CTE的语法要求。
  • 修改后在PostgreSQL中运行可返回与SQL Server一致的8行祖先数据结果。

内容的提问来源于stack exchange,提问作者Mark Gibson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 20:35:01