次の方法で共有


レッスン 1:テーブルの階層構造への変換

適用対象: SQL Server Azure SQL Database Azure SQL Managed Instance

階層リレーションシップを表すために自己結合を使っているテーブルがある場合、このレッスンの説明に従って、それらのテーブルを階層構造に変換できます。 現在の表示から、 hierarchyidを使用した表示に移行するのは比較的簡単です。 移行後は、コンパクトで理解しやすい階層表示が可能になるため、ユーザーはさまざまな方法でインデックスを作成して効率的なクエリを実現できます。

このレッスンでは、既存のテーブルを検証した後、 hierarchyid 列を含む新しいテーブルを作成し、そのテーブルにソース テーブルのデータを取り込みます。さらに、3 とおりのインデックス作成方法を紹介します。 このレッスンの内容は次のとおりです。

前提条件

このチュートリアルを実行するには、SQL Server Management Studio、SQL Server を実行しているサーバーへのアクセス、および AdventureWorks データベースが必要です。

SSMS でデータベースを復元する手順については、データベースの復元に関するページをご覧ください。

Employee テーブルの現在の構造を確認する

サンプル AdventureWorks2022 データベースには、 HumanResources スキーマに含まれる Employee テーブルがあります。 元のテーブルを変更しないように、この手順では、 Employee テーブルのコピーを作成して EmployeeDemoという名前を付けます。 例を単純にするために、元のテーブルから 5 列だけをコピーします。 次に、 HumanResources.EmployeeDemo テーブルに対してクエリを実行し、 hierarchyid データ型が使用されていないテーブル内のデータ構造を確認します。

Employee テーブルをコピーする

  1. クエリ エディターのウィンドウで、次のコードを実行し、 Employee テーブルから新しいテーブルの EmployeeDemoにテーブル構造とデータをコピーします。 元のテーブルは既に hierarchyid を使用しているため、このクエリは従業員のマネージャーを取得するために必然的に階層をフラット化します。 この階層はこのレッスンの中で後ほど再構築します。
USE AdventureWorks2022;  
GO  
  if OBJECT_ID('HumanResources.EmployeeDemo') is not null
 drop table HumanResources.EmployeeDemo 

 SELECT emp.BusinessEntityID AS EmployeeID, emp.LoginID, 
  (SELECT  man.BusinessEntityID FROM HumanResources.Employee man 
	    WHERE emp.OrganizationNode.GetAncestor(1)=man.OrganizationNode OR 
		    (emp.OrganizationNode.GetAncestor(1) = 0x AND man.OrganizationNode IS NULL)) AS ManagerID,
       emp.JobTitle, emp.HireDate
INTO HumanResources.EmployeeDemo   
FROM HumanResources.Employee emp ;
GO

EmployeeDemo テーブルの構造とデータを確認する

  • この新しい EmployeeDemo テーブルは、新しい構造に移行可能な、既存のデータベースの一般的なテーブルを表しています。 クエリ エディター ウィンドウで次のコードを実行すると、テーブルで自己結合を使用して従業員と管理者のリレーションシップを示しているようすを確認できます。

    SELECT   
        Mgr.EmployeeID AS MgrID, Mgr.LoginID AS Manager,   
        Emp.EmployeeID AS E_ID, Emp.LoginID, Emp.JobTitle  
    FROM HumanResources.EmployeeDemo AS Emp  
    LEFT JOIN HumanResources.EmployeeDemo AS Mgr  
    ON Emp.ManagerID = Mgr.EmployeeID  
    ORDER BY MgrID, E_ID  
    

    結果セットは次のようになります。

    MgrID Manager                 E_ID LoginID                  JobTitle  
    NULL	NULL	                1	adventure-works\ken0	    Chief Executive Officer
    1	adventure-works\ken0	    2	adventure-works\terri0   	Vice President of Engineering
    1	adventure-works\ken0	   16	adventure-works\david0	  Marketing Manager
    1	adventure-works\ken0	   25	adventure-works\james1	  Vice President of Production
    1	adventure-works\ken0	  234	adventure-works\laura1	  Chief Financial Officer
    1	adventure-works\ken0  	263	adventure-works\jean0	    Information Services Manager
    1	adventure-works\ken0	  273	adventure-works\brian3	  Vice President of Sales
    2	adventure-works\terri0	  3	adventure-works\roberto0	Engineering Manager
    3	adventure-works\roberto0	4	adventure-works\rob0	    Senior Tool Designer
    ...  
    

    結果は合計 290 行にわたります。

ORDER BY 句を使用していることによって、出力では、各管理レベルの直属の部下がまとめて表示されていることに注意してください。 たとえば、MgrID 1 (ken0) の 7 人の直属の部下がすべて左右に並べて表示されています。 実際に MgrID 1 に直属するすべての人をグループ化することは、不可能ではありませんが、かなり難しくなります。

テーブルに既存の階層データを入力する

ここでは、新しいテーブルを作成して、そのテーブルに EmployeeDemo テーブルのデータを設定します。 この作業には、次の手順があります。

  • hierarchyid 列を含む新しいテーブルを作成します。 この列を、既存の EmployeeID 列や ManagerID 列の代わりに使用してもかまいません。 ただし、これらの列は保持する必要があります。 これは、既存のアプリケーションがこれらの列を参照している可能性があるためでもあり、転送後のデータをわかりやすくするためでもあります。 テーブル定義では、 OrgNode が主キーであることを指定します。したがって、この列には一意の値を格納する必要があります。 OrgNode 列のクラスター化インデックスは、 OrgNode シーケンスのデータを格納します。
  • 各管理者に直属する従業員の数を追跡するために使用する一時テーブルを作成します。
  • 新しいテーブルを、 EmployeeDemo テーブルのデータを使用して設定します。

NewOrg という名前の新しいテーブルを作成するには

  • クエリ エディター ウィンドウで、次のコードを実行し、 HumanResources.NewOrgという名前の新しいテーブルを作成します。

    CREATE TABLE HumanResources.NewOrg  
    (  
      OrgNode hierarchyid,  
      EmployeeID int,  
      LoginID nvarchar(50),  
      ManagerID int  
    CONSTRAINT PK_NewOrg_OrgNode  
      PRIMARY KEY CLUSTERED (OrgNode)  
    );  
    GO  
    

#Children という名前の一時テーブルを作成する

  1. Num という名前の列を持つ #Children という名前の一時テーブルを作成します。この列は、各ノードの子の数を格納します。

    CREATE TABLE #Children   
       (  
        EmployeeID int,  
        ManagerID int,  
        Num int  
    );  
    GO  
    
  2. インデックスを作成します。このインデックスによって、 NewOrg テーブルを設定するクエリの速度が大幅に向上します。

    CREATE CLUSTERED INDEX tmpind ON #Children(ManagerID, EmployeeID);  
    GO  
    

NewOrg テーブルを設定する

  1. 再帰クエリでは、集計を含むサブクエリが禁止されています。 代わりに、 ROW_NUMBER() メソッドを使用して Num 列を設定する次のコードによって、 #Children テーブルを設定します。

    INSERT #Children (EmployeeID, ManagerID, Num)  
    SELECT EmployeeID, ManagerID,  
      ROW_NUMBER() OVER (PARTITION BY ManagerID ORDER BY ManagerID)   
    FROM HumanResources.EmployeeDemo  
    GO 
    
  2. #Children テーブルを確認します。 Num 列に、各管理者の連続する番号がどのように格納されているかに注目してください。

    SELECT * FROM #Children ORDER BY ManagerID, Num  
    GO  
    
    

    結果セットは次のようになります。

    EmployeeID	ManagerID	Num
    1  	NULL  	1
    2	     1    1
    16	   1	  2
    25	   1	  3
    234	   1	  4
    263	   1	  5
    273	   1	  6
    3	     2	  1
    4	     3	  1
    5	     3	  2
    6	     3	  3
    7	     3	  4
    
  3. NewOrg テーブルを設定します。 GetRoot メソッドと ToString メソッドを使用して Num 値を hierarchyid 形式に連結し、結果の階層値で OrgNode 列を更新します。

    WITH paths(path, EmployeeID)   
    AS (  
    -- This section provides the value for the root of the hierarchy  
    SELECT hierarchyid::GetRoot() AS OrgNode, EmployeeID   
    FROM #Children AS C   
    WHERE ManagerID IS NULL   
    
    UNION ALL   
    -- This section provides values for all nodes except the root  
    SELECT   
    CAST(p.path.ToString() + CAST(C.Num AS varchar(30)) + '/' AS hierarchyid),   
    C.EmployeeID  
    FROM #Children AS C   
    JOIN paths AS p   
       ON C.ManagerID = P.EmployeeID   
    )  
    INSERT HumanResources.NewOrg (OrgNode, O.EmployeeID, O.LoginID, O.ManagerID)  
    SELECT P.path, O.EmployeeID, O.LoginID, O.ManagerID  
    FROM HumanResources.EmployeeDemo AS O   
    JOIN Paths AS P   
       ON O.EmployeeID = P.EmployeeID  
    GO 
    
  4. hierarchyid 列は、文字形式に変換すると、よりわかりやすくなります。 次のコードを実行して、 NewOrg テーブルのデータを確認します。このコードには、 OrgNode 列の 2 つの表記が含まれています。

    SELECT OrgNode.ToString() AS LogicalNode, *   
    FROM HumanResources.NewOrg   
    ORDER BY LogicalNode;  
    GO  
    

    LogicalNode 列は、階層を表す読みやすいテキスト形式に hierarchyid 列を変換します。 残りの作業では、 ToString() メソッドを使用して、 hierarchyid 列の論理形式を表示します。

  5. 不要になった一時テーブルを削除します。

    DROP TABLE #Children  
    GO  
    

NewOrg テーブルの最適化

既存の階層データを使用したテーブルの設定 」の作業で作成した NewOrd テーブルにはすべての従業員情報が格納されており、その階層構造は hierarchyid データ型によって表されています。 ここでは、 hierarchyid 列の検索をサポートする新しいインデックスを追加します。

hierarchyid 列 (OrgNode) は、 NewOrg テーブルの主キーです。 OrgNode 列に一意性を持たせるため、このテーブルには作成時に PK_NewOrg_OrgNode という名前のクラスター化インデックスが格納されています。 このクラスター化インデックスは、テーブルの深さ優先検索もサポートしています。

効率的な検索のため NewOrg テーブルにインデックスを付ける

  1. 階層の同じレベルでのクエリを容易にするには、 GetLevel メソッドを使用して、階層内のレベルを格納する計算列を作成します。 次に、レベルと Hierarchyidに基づく複合インデックスを作成します。 次のコードを実行すると、計算列と幅優先のインデックスが作成されます。

    ALTER TABLE HumanResources.NewOrg   
       ADD H_Level AS OrgNode.GetLevel() ;  
    CREATE UNIQUE INDEX EmpBFInd   
       ON HumanResources.NewOrg(H_Level, OrgNode) ;  
    GO  
    
  2. EmployeeID 列に一意のインデックスを作成します。 これは、 EmployeeID の番号によって 1 人の従業員を検索する従来の単一参照です。 次のコードを実行すると、 EmployeeIDのインデックスが作成されます。

    CREATE UNIQUE INDEX EmpIDs_unq ON HumanResources.NewOrg(EmployeeID) ;  
    GO
    
  3. 次のコードを実行して、3 種類の各インデックスの順にテーブルからデータを取得します。

    SELECT OrgNode.ToString() AS LogicalNode,  
    OrgNode, H_Level, EmployeeID, LoginID  
    FROM HumanResources.NewOrg   
    ORDER BY OrgNode;  
    
    SELECT OrgNode.ToString() AS LogicalNode,  
    OrgNode, H_Level, EmployeeID, LoginID   
    FROM HumanResources.NewOrg   
    ORDER BY H_Level, OrgNode;  
    
    SELECT OrgNode.ToString() AS LogicalNode,  
    OrgNode, H_Level, EmployeeID, LoginID   
    FROM HumanResources.NewOrg   
    ORDER BY EmployeeID;  
    GO  
    
  4. 結果セットを比較して、順序がどのように格納されているかをインデックスの種類ごとに確認します。 それぞれの出力の最初の 4 行だけを以下に示します。

    結果セットは次のようになります。

    深さ優先のインデックス : 従業員のレコードは、それぞれのマネージャーのレコードに隣接して格納されます。

    LogicalNode	OrgNode	H_Level	EmployeeID	LoginID
    /	0x	0	1	adventure-works\ken0
    /1/	0x58	1	2	adventure-works\terri0
    /1/1/	0x5AC0	2	3	adventure-works\roberto0
    /1/1/1/	0x5AD6	3	4	adventure-works\rob0
    /1/1/2/	0x5ADA	3	5	adventure-works\gail0
    /1/1/3/	0x5ADE	3	6	adventure-works\jossef0
    /1/1/4/	0x5AE1	3	7	adventure-works\dylan0
    /1/1/4/1/	0x5AE158	4	8	adventure-works\diane1
    /1/1/4/2/	0x5AE168	4	9	adventure-works\gigi0
    /1/1/4/3/	0x5AE178	4	10	adventure-works\michael6
    /1/1/5/	0x5AE3	3	11	adventure-works\ovidiu0
    

    EmployeeID優先のインデックス: 行は EmployeeID の順に格納されます。

    LogicalNode	OrgNode	H_Level	EmployeeID	LoginID
    /	0x	0	1	adventure-works\ken0
    /1/	0x58	1	2	adventure-works\terri0
    /1/1/	0x5AC0	2	3	adventure-works\roberto0
    /1/1/1/	0x5AD6	3	4	adventure-works\rob0
    /1/1/2/	0x5ADA	3	5	adventure-works\gail0
    /1/1/3/	0x5ADE	3	6	adventure-works\jossef0
    /1/1/4/	0x5AE1	3	7	adventure-works\dylan0
    /1/1/4/1/	0x5AE158	4	8	adventure-works\diane1
    /1/1/4/2/	0x5AE168	4	9	adventure-works\gigi0
    /1/1/4/3/	0x5AE178	4	10	adventure-works\michael6
    /1/1/5/	0x5AE3	3	11	adventure-works\ovidiu0
    /1/1/5/1/	0x5AE358	4	12	adventure-works\thierry0
    

Note

深さ優先のインデックスと幅優先のインデックスの違いを示す図については、「階層データ (SQL Server)」を参照してください。

不要な列を削除する

  1. ManagerID 列が表す従業員とマネージャーのリレーションシップは、現在は OrgNode 列によって表されるようになっています。 ManagerID 列を必要とするアプリケーションが他にない場合は、次のステートメントを使用してこの列を削除することを検討します。

    ALTER TABLE HumanResources.NewOrg DROP COLUMN ManagerID ;  
    GO  
    
  2. EmployeeID 列も冗長です。 各従業員は、 OrgNode 列によって一意に識別されます。 EmployeeID 列を必要とするアプリケーションが他にない場合は、次のコードを使用して、インデックスを削除した後に列を削除することを検討します。

    DROP INDEX EmpIDs_unq ON HumanResources.NewOrg ;  
    ALTER TABLE HumanResources.NewOrg DROP COLUMN EmployeeID ;  
    GO  
    

元のテーブルを新しいテーブルに置き換える

  1. 元のテーブルに追加のインデックスや制約が格納されている場合は、それらを NewOrg テーブルに追加します。

  2. 古い EmployeeDemo テーブルを新しいテーブルに置き換えます。 次のコードを実行すると、古いテーブルが削除され、新しいテーブルの名前がその古い名前に変更されます。

    DROP TABLE HumanResources.EmployeeDemo ;  
    GO  
    sp_rename 'HumanResources.NewOrg', 'EmployeeDemo' ;  
    GO  
    
  3. 次のコードを実行して、最終的なテーブルを確認します。

    SELECT * FROM HumanResources.EmployeeDemo ;  
    

次のステップ

次の記事では、階層テーブルでデータを作成して管理する方法を説明します。

詳細については、次の記事に進んでください