SQL递归CTE替换语句太慢

时间:2018-08-11 22:15:48

标签: sql sql-server

我有一个递归CTE来替换一个表达式中的多个值,但是当有很多表达式时,它太慢了。

CREATE TABLE #table1(IdExpresion INT, expresion VARCHAR(MAX))
CREATE TABLE #table2(IdExpresion INT, searchExpresion VARCHAR(50), replacementExpresion VARCHAR(50))

INSERT INTO #table1(IdExpresion, expresion)
VALUES(1, 'Mary had a little lamb'),
      (2, 'The new student, student_name has the following grades Math - math_grade, Science - Science_grade')

INSERT INTO #table2(IdExpresion, searchExpresion, replacementExpresion)
VALUES(1, 'lamb','dog'),
      (2, 'student_name','Joe Smith'),
      (2, 'math_grade','A'),
      (2, 'Science_grade','B+')

;WITH cte(IdExpresion, expresion, lvl) AS
(
    SELECT t1.IdExpresion, t1.expresion, 1
    FROM #table1 t1
    UNION ALL    
    SELECT cte.IdExpresion, REPLACE(cte.expresion, t2.searchExpresion, t2.replacementExpresion), cte.lvl + 1 
    FROM cte  
    INNER JOIN #table2 t2
    ON cte.IdExpresion = t2.IdExpresion
       AND CHARINDEX(t2.searchExpresion, cte.expresion) > 0
)
SELECT DISTINCT c2.expresion
FROM (SELECT IdExpresion, MAX(lvl) AS lvl
      FROM cte
      GROUP BY IdExpresion) c1
INNER JOIN cte c2
   ON c1.IdExpresion = c2.IdExpresion 
      AND c1.lvl = c2.lvl
OPTION (MAXRECURSION 0);

有人有什么建议吗?我正在使用SQL Server

3 个答案:

答案 0 :(得分:1)

您可以向其添加另一个CTE,该CTE将为每个替换获得一个row_number,并由IdExpresion进行分区。

然后在递归CTE中,而不是递增计数,递减计数直到与替换的row_number不匹配为止。

CTE中具有所有替换项的最后一个条目将具有Lvl 0。

;WITH SEARCH AS (
  SELECT 
    IdExpresion, 
    row_number() over (partition by IdExpresion order by searchExpresion) as rn,
    searchExpresion, replacementExpresion
  FROM #table2
), CTE(IdExpresion, expresion, lvl) AS
(
    SELECT t1.IdExpresion, t1.expresion, count(*)
    FROM #table1 t1
    JOIN #table2 t2 ON t2.IdExpresion = t1.IdExpresion
    GROUP BY t1.IdExpresion, t1.expresion

    UNION ALL

    SELECT c.IdExpresion, REPLACE(c.expresion, s.searchExpresion, s.replacementExpresion), c.lvl - 1
    FROM CTE c
    JOIN SEARCH s
    ON s.IdExpresion = c.IdExpresion AND s.rn = c.lvl
)
SELECT IdExpresion, expresion
FROM CTE
WHERE lvl = 0
OPTION (MAXRECURSION 0);

这样,每个IdExpresion只能对每个REPLACE执行一次。
而且不必使用CHARINDEX。

您还可以使用临时表替换该SEARCH cte。
具有#table2中具有该row_number的记录的记录。
这样的好处是,使用表可以添加复合索引。
在大表上,它应该加快对替换的递归联接。

测试妊娠here

CREATE TABLE #tmpSearch (
    IdExpresion INT, 
    rn INT,
    searchExpresion VARCHAR(50), 
    replacementExpresion VARCHAR(50),
    primary key (IdExpresion, rn));

insert into #tmpSearch (IdExpresion, rn, searchExpresion, replacementExpresion)
select 
 IdExpresion,
 row_number() over (partition by IdExpresion order by searchExpresion) as rn,
 searchExpresion, 
 replacementExpresion 
from #table2 
order by IdExpresion, searchExpresion;

;WITH CTE(IdExpresion, expresion, lvl) AS
(
    SELECT t1.IdExpresion, t1.expresion, max(s.rn)
    FROM #table1 t1
    JOIN #tmpSearch s ON s.IdExpresion = t1.IdExpresion
    GROUP BY t1.IdExpresion, t1.expresion

    UNION ALL

    SELECT c.IdExpresion, REPLACE(c.expresion, s.searchExpresion, s.replacementExpresion), c.lvl - 1
    FROM CTE c
    JOIN #tmpSearch s
    ON s.IdExpresion = c.IdExpresion AND s.rn = c.lvl
)
SELECT IdExpresion, expresion
FROM CTE
WHERE lvl = 0
OPTION (MAXRECURSION 0);

答案 1 :(得分:1)

不确定性能是否更高,但这是一种蛮力的方法,只是为了娱乐

已经+1 LukStorm的答案,我怀疑这是要走的路。

示例

Declare @S varchar(max) = (Select IdExpresion,expresion = replace(' '+expresion,' ',concat(' ',IdExpresion,'|||')) From #Table1 For XML Raw )

Select @S = replace(@S,concat(IdExpresion,'|||',searchExpresion),replacementExpresion) From  #table2

Select IdExpresion = B.i.value('@IdExpresion', 'int')
      ,expresion   = ltrim(replace(B.i.value('@expresion', 'varchar(max)'),B.i.value('@IdExpresion', 'varchar(25)')+'|||',''))
 From  (Select x = Cast(@S as xml).query('.')) as A 
 Cross Apply x.nodes('row') AS B(i)

返回

IdExpresion expresion
1           Mary had a little dog
2           The new student, Joe Smith has the following grades Math - A, Science - B+

答案 2 :(得分:0)

美好的一天,

这是另一种解决方案。请检查是否符合您的需求。该解决方案不使用任何循环,而是使用简单的动态查询。

DECLARE @SQLString nvarchar(MAX);
-- do not make mistake, this is simple CTE and not a recursive CTE (no Loop)
;With MyCTE as (
    select R
    From table1 t1
    CROSS APPLY (
        SELECT R = 'SELECT ' + CONVERT (NVARCHAR(MAX),t1.IdExpresion) + ' as IdExpresion,' + STRING_AGG ('REPLACE','(') + '(' + 't1.expresion,''' + STRING_AGG(t2.searchExpresion + ''',''' + t2.replacementExpresion , '''),''') + ''') as expresion FROM table1 t1 where t1.IdExpresion = ' + CONVERT (NVARCHAR(MAX),t1.IdExpresion)
        from table2 t2
        where t2.IdExpresion = t1.IdExpresion
    ) C
)
SELECT @SQLString = STRING_AGG(R,'
UNION ALL
')
FROM MyCTE
--PRINT @SQLString
EXECUTE sp_executesql @SQLString
GO
  

注意!我建议执行一些测试以确认这可以解决所有情况

     

注意!我正在使用SQL Server 2017中添加的功能STRING_AGG。在旧版本中,您可以使用FOR XML语句获得完全相同的解决方案。

由于我们没有真正的DDL + DML,因此我们无法真正讨论性能,但是解决方案的执行计划之间的差异为10%到90%(通常,您应该检查生产中的IO和时间统计信息此外,在选择解决方案之前)

所以...这是执行计划图像(上面的查询是我的动态SQL解决方案,下面是使用递归CTE =循环的LukStorms解决方案)

enter image description here