3

ニーズを満たすために再帰 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
4

1 に答える 1

3

インデックス付きビュー (自己結合、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

テーブル自体に列を追加する

たとえば、フルパス列の場合、必要に応じて、「:」で dbo.GetHierPath の結果を分割して、CTE に他の 2 つの列を追加します。(left=>path, right=>abbreviations)

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

列を維持する

階層的な性質のため、Y の子孫と Z の祖先に影響を与えるレコード X が削除される可能性があります。これは、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)

id を介してパス データにアクセスするつもりだった場合は、追加のインデックスを追加しなくても、既にテーブル自体に含まれています。

上記の 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さらに、 for の式は途中で簡単にFullPath分割できます。または、元の CTE のどちらか早い方を使用してください。PathOnlyAbbreviation

于 2011-01-26T02:50:09.817 に答える