我有一个递归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
答案 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解决方案)