需要将递归 CTE 查询转换为索引友好查询
在经历了编写递归 CTE 查询以满足我的需求的所有艰苦工作之后,我意识到我无法使用它,因为它在索引视图中不起作用。所以我需要其他东西来代替下面的 CTE。 (是的,您可以在非索引视图中使用 CTE,但这对我来说太慢了)。
要求:
我的最终目标是拥有一个自我更新的索引视图(它不一定是视图,而是类似的东西)......也就是说,如果数据发生任何变化视图连接的表的数量,则视图需要自行更新。
视图需要建立索引,因为它必须非常快,并且数据不会经常更改。不幸的是,使用 CTE 的非索引视图需要 3-5 秒才能运行,这对于我的需求来说太长了。我需要查询以毫秒为单位运行。递归表中有几十万条记录。
就我的研究而言,满足所有这些要求的最佳解决方案是索引视图,但我对任何解决方案都持开放态度。
CTE 可以在我的 其他帖子的答案中找到。 或者又是这样:
DECLARE @tbl TABLE (
Id INT
,[Name] VARCHAR(20)
,ParentId INT
)
INSERT INTO @tbl( Id, Name, ParentId )
VALUES
(1, 'Europe', NULL)
,(2, 'Asia', NULL)
,(3, 'Germany', 1)
,(4, 'UK', 1)
,(5, 'China', 2)
,(6, 'India', 2)
,(7, 'Scotland', 4)
,(8, 'Edinburgh', 7)
,(9, 'Leith', 8)
;
DECLARE @tbl2 table (id int, abbreviation varchar(10), tbl_id int)
INSERT INTO @tbl2( Id, Abbreviation, tbl_id )
VALUES
(100, 'EU', 1)
,(101, 'AS', 2)
,(102, 'DE', 3)
,(103, 'CN', 5)
;WITH abbr AS (
SELECT a.*, isnull(b.abbreviation,'') abbreviation
FROM @tbl a
left join @tbl2 b on a.Id = b.tbl_id
), abcd AS (
-- anchor
SELECT id, [Name], ParentID,
CAST(([Name]) AS VARCHAR(1000)) [Path],
cast(abbreviation as varchar(max)) abbreviation
FROM abbr
WHERE ParentId IS NULL
UNION ALL
--recursive member
SELECT t.id, t.[Name], t.ParentID,
CAST((a.path + '/' + t.Name) AS VARCHAR(1000)) [Path],
isnull(nullif(t.abbreviation,'')+',', '') + a.abbreviation
FROM abbr AS t
JOIN abcd AS a
ON t.ParentId = a.id
)
SELECT *, [Path] + ':' + abbreviation
FROM abcd
After going through all the hard work of writing a recursive CTE query to meet my needs, I realize I can't use it because it doesn't work in an indexed view. So I need something else to replace the CTE below. (Yes you can use a CTE in a non-indexed view, but that's too slow for me).
The requirements:
My ultimate goal is to have a self updating indexed view (it doesn't have to be a view, but something similar)... that is, if data changes in any of the tables the view joins on, then the view needs to update itself.
The view needs to be indexed because it has to be very fast, and the data doesn't change very frequently. Unfortunately, the non-indexed view using a CTE takes 3-5 seconds to run which is way too long for my needs. I need the query to run in milliseconds. The recursive table has a few hundred thousand records in it.
As far as my research has taken me, the best solution to meet all these requirements is an indexed view, but I'm open to any solution.
The CTE can be found in the answer to my other post.
Or here it is again:
DECLARE @tbl TABLE (
Id INT
,[Name] VARCHAR(20)
,ParentId INT
)
INSERT INTO @tbl( Id, Name, ParentId )
VALUES
(1, 'Europe', NULL)
,(2, 'Asia', NULL)
,(3, 'Germany', 1)
,(4, 'UK', 1)
,(5, 'China', 2)
,(6, 'India', 2)
,(7, 'Scotland', 4)
,(8, 'Edinburgh', 7)
,(9, 'Leith', 8)
;
DECLARE @tbl2 table (id int, abbreviation varchar(10), tbl_id int)
INSERT INTO @tbl2( Id, Abbreviation, tbl_id )
VALUES
(100, 'EU', 1)
,(101, 'AS', 2)
,(102, 'DE', 3)
,(103, 'CN', 5)
;WITH abbr AS (
SELECT a.*, isnull(b.abbreviation,'') abbreviation
FROM @tbl a
left join @tbl2 b on a.Id = b.tbl_id
), abcd AS (
-- anchor
SELECT id, [Name], ParentID,
CAST(([Name]) AS VARCHAR(1000)) [Path],
cast(abbreviation as varchar(max)) abbreviation
FROM abbr
WHERE ParentId IS NULL
UNION ALL
--recursive member
SELECT t.id, t.[Name], t.ParentID,
CAST((a.path + '/' + t.Name) AS VARCHAR(1000)) [Path],
isnull(nullif(t.abbreviation,'')+',', '') + a.abbreviation
FROM abbr AS t
JOIN abcd AS a
ON t.ParentId = a.id
)
SELECT *, [Path] + ':' + abbreviation
FROM abcd
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
在克服了索引视图的所有障碍(自连接、cte、udf 访问数据等)之后,我建议将以下内容作为您的解决方案。
创建支撑函数
基于距根部的最大深度 4(总共 5 个)。或者使用 CTE
将列添加到表本身
例如完整路径列,如果需要,可以通过在 ':'
(left=>path, right=>abbreviations)
维护列
由于层次性质,记录 X 可能会被删除,从而影响 Y 后代和 Z 祖先,这在 INSTEAD OF 或 AFTER 触发器中很难识别。因此,替代方法是基于以下条件:
我们只需再次运行整个表来维护数据,每次更新需要 3-5 秒(如果 5-5 秒则更快)连接查询效果更好)。
最后,在表本身上为新列建立索引
如果您打算通过 id 访问路径数据,那么它已经在表本身中,无需添加额外的索引。
上面的 TSQL 引用这些对象
修改表和列名称以适合您的架构。
编辑 - 可能更快的替代方案
鉴于每次都会重新计算所有记录,因此实际上不需要返回单个 HIER.ID 的
FullPath
的函数。支持函数
中的查询可以在末尾不使用where H1.id = @hier_id
过滤器的情况下使用。此外,FullPath
的表达式可以轻松地从中间分解为PathOnly
和Abbreviation
。或者只使用原始 CTE,以更快者为准。After hitting all the roadblocks with indexed views (self join, cte, udf accessing data etc), I propose that the below as a solution for you.
Create support function
Based on maximum depth of 4 from root (5 total). Or use a CTE
Add columns to the table itself
For example the fullpath column, if you need, add the other 2 columns in the CTE by splitting the result of dbo.GetHierPath on ':'
(left=>path, right=>abbreviations)
Maintain the columns
Because of the hierarchical nature, record X could be deleted that affects a Y descendent and Z ancestor, which is quite hard to identify in either of INSTEAD OF or AFTER triggers. So the alternative approach is based on the conditions
We maintain the data simply by running through the entire table again, taking 3-5 seconds per update (or faster if the 5-join query works out better).
Finally, index the new column(s) on the table itself
If you intended to access the path data via the id, then it is already in the table itself without adding an additional index.
The above TSQL references these objects
Modify the table and column names to suit your schema.
EDIT - Possibly faster alternative
Given that all records are recalculated each time, there is no real need for a function that returns the
FullPath
for a single HIER.ID. The query in thesupport function
can be used without thewhere H1.id = @hier_id
filter at the end. Furthermore, the expression forFullPath
can be broken intoPathOnly
andAbbreviation
easily down the middle. Or just use the original CTE, whichever is faster.