在 Redshift 中连接字符串的递归 CTE 替代方案

Posted

技术标签:

【中文标题】在 Redshift 中连接字符串的递归 CTE 替代方案【英文标题】:Recursive CTE Alternative for Concatenating Strings in Redshift 【发布时间】:2020-05-18 11:21:11 【问题描述】:

我有一个表,其中有 4 列,即:Book_no,Prev_Book_no(上一版 book_no),Edition_no(从 0 开始到最新版 no(以 1 递增)),Tree(最初为 null)。

我打算做的是从最高版本到第0版创建一棵Book_no树。

例如: (Book_no,Prev_Book_no,Edition_no) 值: (bbb,null,0), (ccc,bbb,1), (ddd,ccc,2), (eee,ddd,3), (fff,eee,4)

那么每一行的树应该是:(bbb),(ccc,bbb),(ddd,ccc,bbb),(eee,ddd,ccc,bbb) 和 (fff,eee,ddd,ccc,bbb) .

为此,我尝试使用递归 CTE,代码如下:

with cte(book_no,prev_book_no,tree,edition_no) as

(select

book_no,prev_book_no,tree,edition_no

from books

where edition_no>=0

union all

select e.book_no,e.prev_book_no,concat(nvl(e.tree,''),','+e.prev_book_no),e.edition_no

from

cte e inner join books f

on e.prev_book_no=f.book_no

)

select distinct * from cte

order by edition_no;

但是 Redshift 不允许在 from 子句中使用 cte table 并给出错误。 '表 cte 不存在'。

P.S 表中有多种不同的书。例如我只提到了一本。

在 Redshift 中是否有任何替代方案?

编辑:

样本数据:

预期输出:

逻辑:我想获得书号的层次结构,并且正在使用 concat 操作。(当前代码可能是错误的) 谢谢!

【问题讨论】:

请提供示例数据、期望的结果以及您要实现的逻辑的清晰说明。 @GordonLinoff 我已经添加了相关图片,并试图解释我打算做什么。 【参考方案1】:

从 2021 年 4 月 29 日开始,Redshift 现在使用 WITH RECURSIVE 语法支持递归 CTE:

WITH RECURSIVE cte(book_no, prev_book_no, tree, edition_no) AS (
    SELECT book_no, prev_book_no, tree, edition_no
    FROM books
    WHERE edition_no >= 0
    UNION ALL
    SELECT e.book_no, e.prev_book_no, NVL(e.tree,'')||','||e.prev_book_no), e.edition_no
    FROM cte         e
    INNER JOIN books f ON e.prev_book_no = f.book_no)
SELECT DISTINCT *
FROM cte
ORDER BY
    edition_no;

https://aws.amazon.com/about-aws/whats-new/2021/04/amazon-redshift-announces-support-for-heirarchical-data-queries-with-recursive-cte/ https://docs.aws.amazon.com/redshift/latest/dg/r_WITH_clause.html#r_WITH_clause-recursive-cte

【讨论】:

【参考方案2】:

您正确选择了递归 CTE。不幸的是,Redshift does not support them。另见this similar question。我不知道任何可以被视为全值解决方法的 SQL 功能。也许如果级别的数量实际上是有限的,您可以使用有限数量的左自连接(丑陋,我知道)。

(对于您的 CTE - 虽然有点偏离主题 - 似乎条件 where edition_no=0 足以作为联合第一部分的种子,第二个查询中的连接条件也应转换为 on f.prev_book_no=e.book_no。)

【讨论】:

谢谢!但关卡数量不限,因书而异。【参考方案3】:

Amazon Redshift 数据库不支持递归查询。 https://docs.aws.amazon.com/redshift/latest/dg/c_unsupported-postgresql-features.html

我不擅长这个方言。试试这个代码,可能它可以工作并解决你的问题:

select book_no,prev_book_no,edition_no,
(
select listagg(innr.book_no,',') within group (order by innr.edition_no asc) over() 
from books innr 
where innr.book_no=outr.book_no and innr.edition_no<=outr.edition_no
) tree
from books outr
order by book_no

带有循环的变体。尝试执行它:

create local temporary table IF NOT EXISTS 
table1 (book_id bigint, book_no varchar(10), edition_no varchar(10));

insert into table1 (book_id, book_no, edition_no) 
select row_number () over (order by book_no) as book_id, book_no, edition_no 
from books where edition_no=0;

FOR iter IN SELECT distinct edition_no 
FROM books where edition_no!=0 ORDER BY edition_no LOOP
  insert into table1 (book_id, book_no, edition_no) 
  select t.book_id, b.book_no, b.edition_no 
  from table1 t join books b on t.book_id=b.prev_book_no
  where b.edition_no = iter
  ;
  commit;
  END LOOP;

select book_no,prev_book_no,edition_no,
(
select listagg(innr.book_no,',') within group (order by innr.edition_no asc) over() 
from books innr inner join table1 tbl_innr on innr.book_no=tbl_innr.book_no
where tbl_innr.book_id=tbl_outr.book_id and innr.edition_no<=outr.edition_no
) tree
from books outr join table1 tbl_outr on outr.book_no=tbl_outr.book_no
order by book_no
;
drop table table1;

【讨论】:

感谢您的解决方案。但它似乎只适用于一棵这样的树,当我在整个数据集上尝试它时,它会给出错误:结果大小超过 LISTAGG 限制。 (可能它将所有书籍分组为特定版本号)。你能把它也包含在你的代码中吗? @NewCoder 你有整本书的ID吗(所有版本都一样)? 不,没有这个ID栏 @NewCoder 我刚刚添加了新版本。试试看

以上是关于在 Redshift 中连接字符串的递归 CTE 替代方案的主要内容,如果未能解决你的问题,请参考以下文章

Redshift:连接到由表中的 SELECT * 组成的子查询/CTE 相当于连接表本身,还是性能下降?

为啥不能在 Redshift 的 CTE 的某些子句中调用不可变的 UDF?

锚和递归查询“CTE”列“ColumnName”中的递归部分之间的类型不匹配

CTE 递归语句,带自连接,不带 id

在CTE中使用最大递归来生成CTE,而不是仅在最终的SELECT语句中生成CTE

在 C# 中模拟 CTE 递归