Необходимо преобразовать рекурсивный запрос CTE в индексный запрос - PullRequest
3 голосов
/ 26 января 2011

Пройдя всю тяжелую работу по написанию рекурсивного запроса CTE для удовлетворения моих потребностей, я понимаю, что не могу использовать его, потому что он не работает в индексированном представлении. Поэтому мне нужно что-то еще, чтобы заменить CTE ниже. (Да, вы можете использовать CTE в неиндексированном представлении, но это слишком медленно для меня).

Требования:

  1. Моя конечная цель - иметь самообновляющееся индексированное представление (это не должно быть представление, но что-то похожее) ... то есть, если данные изменяются в любой из таблиц, к которой присоединяется представление , то представление необходимо обновить само.

  2. Представление необходимо индексировать, поскольку оно должно быть очень быстрым, а данные не очень часто меняются. К сожалению, неиндексированное представление с использованием 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

1 Ответ

3 голосов
/ 26 января 2011

После преодоления всех препятствий с помощью индексированных представлений (самосоединение, cte, доступ к данным в формате udf и т. Д.) Я предлагаю использовать приведенное ниже решение.

Создать функцию поддержки

На основе максимальной глубины 4 от корня (всего 5). Или используйте CTE

CREATE FUNCTION dbo.GetHierPath(@hier_id int) returns varchar(max)
WITH SCHEMABINDING
as
begin
return (
    select FullPath =
               isnull(H5.Name+'/','') + 
               isnull(H4.Name+'/','') +
               isnull(H3.Name+'/','') +
               isnull(H2.Name+'/','') +
               H1.Name
             +
               ':'
             +
               isnull(STUFF(
               isnull(','+A1.abbreviation,'') +
               isnull(','+A2.abbreviation,'') + 
               isnull(','+A3.abbreviation,'') +
               isnull(','+A4.abbreviation,'') +
               isnull(','+A5.abbreviation,''),1,1,''),'')
    from dbo.HIER H1
    left join dbo.ABBR A1 on A1.hier_id = H1.Id
    left join dbo.HIER H2 on H1.ParentId = H2.Id
    left join dbo.ABBR A2 on A2.hier_id = H2.Id
    left join dbo.HIER H3 on H2.ParentId = H3.Id
    left join dbo.ABBR A3 on A3.hier_id = H3.Id
    left join dbo.HIER H4 on H3.ParentId = H4.Id
    left join dbo.ABBR A4 on A4.hier_id = H4.Id
    left join dbo.HIER H5 on H4.ParentId = H5.Id
    left join dbo.ABBR A5 on A5.hier_id = H5.Id
    where H1.id = @hier_id)
end
GO

Добавление столбцов в саму таблицу

Например, столбец полного пути, если вам нужно, добавьте два других столбца в CTE, разделив результат dbo.GetHierPath на ':' (left=>path, right=>abbreviations)

-- index maximum key length is 900, based on your data, 400 is enough
ALTER TABLE HIER ADD FullPath VARCHAR(400)

Поддерживать столбцы

Из-за иерархической природы запись X может быть удалена, что влияет на потомок Y и Z предка, что довольно трудно идентифицировать в триггерах INSTEAD OF или AFTER. Таким образом, альтернативный подход основан на условиях

  • если данные изменяются в любой из таблиц, к которой присоединяется представление, то представление должно обновляться само.
  • Неиндексированное представление с использованием CTE занимает 3-5 секунд, что слишком долго для моих нужд

Мы обслуживаем данные просто, снова просматривая всю таблицу, занимая 3-5 секунд на обновление (или быстрее, если запрос с 5 объединениями работает лучше).

CREATE TRIGGER TG_HIER
ON HIER
AFTER INSERT, UPDATE, DELETE
AS
UPDATE HIER
SET FullPath = dbo.GetHierPath(HIER.Id)

Наконец, индексируйте новый столбец (столбцы) на самой таблице

create index ix_hier_fullpath on HIER(FullPath)

Если вы намеревались получить доступ к данным пути через идентификатор, то они уже находятся в самой таблице без добавления дополнительного индекса.

Приведенный выше TSQL ссылается на эти объекты

Измените имена таблиц и столбцов в соответствии с вашей схемой.

CREATE TABLE dbo.HIER (Id INT Primary Key Clustered, [Name] VARCHAR(20) ,ParentId INT)
;
INSERT dbo.HIER( 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)
    ,(10, 'Antartica', NULL) 
; 
CREATE TABLE dbo.ABBR (id int primary key clustered, abbreviation varchar(10), hier_id int)
;
INSERT dbo.ABBR( Id, Abbreviation, hier_id ) VALUES 
     (100, 'EU', 1) 
    ,(101, 'AS', 2) 
    ,(102, 'DE', 3) 
    ,(103, 'CN', 5)
GO

РЕДАКТИРОВАТЬ - Возможно, более быстрая альтернатива

Учитывая, что все записи пересчитываются каждый раз, нет реальной необходимости в функции, которая возвращает FullPath для одного HIER.ID. Запрос в support function можно использовать без фильтра where H1.id = @hier_id в конце. Кроме того, выражение для FullPath может быть разбито на PathOnly и Abbreviation легко посередине. Или просто используйте оригинальный CTE, в зависимости от того, что быстрее.

...