每次递归中的SQL递归CTE替换记录

问题描述 投票:1回答:3

我有一张这样的桌子:

ItemID  ItemFormula
100     'ID_3+ID_5'
110     'ID_2+ID_6'
120     'ID_100+ID_110'
130     'ID_120+ID_4'

这是公式表的简化版本,具有近1000条记录和多达40个参考水平(其他项目中使用的项目)。任务是将公式分解为仅一个级别的引用,而一个项中没有其他项。例如,在上表中,id = 130,我应该具有'(((ID_3 + ID_5)+(ID_2 + ID_6))+ ID_4'

我可以为此使用递归CTE。但是我的问题是,由于参考水平很高,我的递归选择有很多记录要加入,因此需要很多时间才能完成。

我的问题是:我可以仅在每次递归发生时才保留以前的递归吗?

这是我的CTE代码

WITH Formula
  AS  (SELECT A.ItemID
             ,'ID_' + CONVERT(VARCHAR(20), A.ItemID) AS ItemText
             ,CONVERT(VARCHAR(MAX), A.ItemFormula) AS ItemFormula 
       FROM (VALUES (100,'ID_3+ID_5'),
                    (110,'ID_2+ID_6'),
                    (120,'ID_100+ID_110'),
                    (130,'ID_120+ID_4')   
                ) A (ItemID,ItemFormula)

  )
    ,REC
  AS
      (
          SELECT A.ItemID
                ,A.ItemText
                ,A.ItemFormula
                ,1 AS LevelID
          FROM Formula A
          UNION ALL
          SELECT A.ItemID
                ,A.ItemText
                ,' '
                 + TRIM (REPLACE (REPLACE (A.ItemFormula, B.ItemText, ' ( ' + B.ItemFormula + ' ) '), '  ', ' '))
                 + ' ' AS ItemFormula
                ,A.LevelID + 1 AS LevelID
          FROM REC A
              CROSS APPLY
          (
              SELECT *
              FROM
              (
                  SELECT *
                        ,ROW_NUMBER () OVER (ORDER BY GETDATE ()) AS RowNum
                  FROM Formula B2
                  WHERE CHARINDEX (B2.ItemText, A.ItemFormula) > 0
              ) B3
              WHERE B3.RowNum = 1
          ) B
      )
    ,FinalQ
  AS
      (
          SELECT A2.ItemID
                ,A2.ItemFormula
                ,A2.LevelID
          FROM
          (
              SELECT A.ItemID
                    ,REPLACE (TRIM (A.ItemFormula), ' ', '') AS ItemFormula
                    ,A.LevelID
                    ,ROW_NUMBER () OVER (PARTITION BY A.ItemID ORDER BY A.LevelID DESC) AS RowNum
              FROM REC A
          ) A2
          WHERE A2.RowNum = 1
      )
SELECT * FROM FinalQ A2 ORDER BY A2.ItemID;

提前感谢。

sql sql-server recursion replace
3个回答
0
投票

我的问题是:我可以仅在每次递归发生时才保留以前的递归吗?

没有递归CTE将继续向先前迭代中找到的行添加行。您没有某种控件可以删除递归CTE during其迭代的行。

但是,您可以将它们过滤出之后,递归CTE可能已经完成,也许是在仅考虑最后meaninful行(根据要定义的某种规则)的辅助CTE上。

在PostgreSQL中发现了唯一类似的想法,您可以在UNION之外使用UNION ALL子句,以避免产生更多相同的行。但这与您需要的有所不同。


0
投票

这是一个非常复杂的问题。这里是想法:

  1. 查找哪些项目不需要任何插入。这些是没有任何其他参考的。
  2. 为商品插入创建订单。假设项目已定义,则可以将插入项插入到项目中。可以使用递归CTE。
  3. 枚举插入。 (1)中的所有内容都为“ 1”。其余的按顺序排列。
  4. 按插入顺序处理插入。

这是我的解决方法:

with ordering as (
      select itemid, itemtext, itemformula, convert(varchar(max), null) as otheritemtext, 1 as lev
      from formula f
      where not exists (select 1
                        from formula f2 join
                             string_split(f.itemformula, '+') s
                             on f2.itemtext = s.value
                        where f2.itemid <> f.itemid
                       )
       union all
       select f.itemid, f.itemtext, f.itemformula, convert(varchar(max), s.value), lev + 1
       from formula f cross apply
            string_split(f.itemformula, '+') s join
            ordering o
            on o.itemtext = s.value
        -- where lev <= 2
     ),
     ordered as (
      select distinct o.*,
             dense_rank() over (order by (case when lev = 1 then -1 else lev end), (case when lev = 1 then '' else otheritemtext end)) as seqnum
      from ordering o
     ),
     cte as (
      select o.itemid, o.itemtext, o.itemformula, convert(varchar(max), o.otheritemtext) as otheritemtext,
             o.itemformula as newformula, o.seqnum, 1 as lev
      from ordered o
      where seqnum = 1
      union all
      select cte.itemid, o.itemtext, o.itemformula, convert(varchar(max), cte.itemtext),
             replace(o.itemformula, o.otheritemtext, concat('(', cte.newformula, ')')),  o.seqnum, cte.lev + 1
      from cte join
           ordered o
           on cte.itemtext = o.otheritemtext and cte.seqnum < o.seqnum
     )
select *
from cte;

db<>fiddle


0
投票

如果存在任何公式(Item_100无法引用Item_150),您可以利用公式的逻辑顺序,并以降序顺序处理项目。以下使用LIKE,不适用于具有重叠模式(例如ID_10和ID_100)的公式,您可以通过一些字符串操作或保持固定长度的ItemID(例如ID_0010和ID_0100)来解决此问题]

declare @f table
(
ItemId int,
ItemFormula varchar(1000)
);

insert into @f(ItemId, ItemFormula)
values 
(100, 'ID_3+ID_5'),
(110, 'ID_2+ID_6'),
(120, 'ID_100+ID_110'),
(130, 'ID_120+ID_4'),
(140, '(ID_130+ID_110)/ID_100'),
(150, 'sqrt(ID_140, ID_130)'), 
(160, 'ID_150-ID_120+ID_140');


;with cte
as
(
select f.ItemId, replace(cast(f.ItemFormula as varchar(max)), isnull('ID_' + cast(r.ItemId as varchar(max)), ''), isnull('(' + r.ItemFormula+ ')', '')) as therepl, 1 as lvl
from @f as f
outer apply (
    select *
    from
    (
    select rr.*, row_number() over(order by rr.ItemId desc) as rownum
    from @f as rr
    where f.ItemFormula like '%ID_' + cast(rr.ItemId as varchar(1000)) + '%'
    ) as src
    where rownum = 1
    ) as r
union all
select c.ItemId, replace(c.therepl, 'ID_' + cast(r.ItemId as varchar(max)), '(' + r.ItemFormula+ ')'), c.lvl+1
from cte as c
cross apply (
    select *
    from
    (
    select rr.*, row_number() over(order by rr.ItemId desc) as rownum
    from @f as rr
    where c.therepl like '%ID_' + cast(rr.ItemId as varchar(1000)) + '%'
    ) as src
    where rownum = 1
    ) as r
),
rown
as
(
select *, row_number() over (partition by itemid order by lvl desc) as rownum
from cte
)
select *
from rown
where rownum = 1;
© www.soinside.com 2019 - 2024. All rights reserved.