계층적 데이터(SQL Server)

적용 대상:SQL ServerAzure SQL DatabaseAzure SQL Managed Instance

기본 제공 hierarchyid 데이터 형식을 사용하면 계층적 데이터를 더 쉽게 저장하고 쿼리할 수 있습니다. hierarchyid 는 계층적 데이터의 가장 일반적인 유형인 트리를 표시하는 데 최적화되어 있습니다.

계층적 데이터는 계층적 관계에 의해 서로 관련된 데이터 항목 집합으로 정의됩니다. 계층적 관계는 데이터의 한 항목이 다른 항목의 부모인 위치에 있습니다. 데이터베이스에 일반적으로 저장되는 계층적 데이터는 다음과 같습니다.

  • 조직 구조

  • 파일 시스템

  • 프로젝트의 태스크 집합

  • 언어 용어의 분류

  • 웹 페이지 간 링크의 그래프

계층 구조를 사용하여 테이블을 만들거나 다른 위치에 저장된 데이터의 계층 구조를 설명하는 데이터 형식으로 hierarchyid를 사용합니다. Transact-SQL의 hierarchyid 함수를 사용하여 계층적 데이터를 쿼리하고 관리합니다.

hierarchyid의 키 속성

hierarchyid 데이터 형식의 값은 트리 계층에서의 위치를 나타냅니다. hierarchyid 값의 속성은 다음과 같습니다.

  • 높은 압축성

    n 개 노드가 포함된 트리에서 노드를 나타내는 데 필요한 평균 비트 수는 평균 fanout(노드의 평균 자식 수)에 따라 달라집니다. 작은 fanout(0-7)의 경우 크기는 약 6*logAn비트입니다. 여기서 A는 평균 fanout입니다. 평균 fanout 수준이 6인 100,000명으로 구성된 조직 계층의 노드는 약 38비트를 사용합니다. 이는 스토리지를 위해 40비트나 5바이트로 반올림됩니다.

  • 깊이 우선 순서로 비교

    두 개의 hierarchyid 값이 ab인 경우 a<b는 깊이 우선 트리 탐색에서 a가 b 앞에 온다는 의미입니다. hierarchyid 데이터 형식의 인덱스에는 깊이 우선 순서가 사용되며 깊이 우선 탐색에서 서로 가까이 있는 노드는 서로 가깝게 저장됩니다. 예를 들어 레코드의 자식은 해당 레코드에 인접하게 저장됩니다.

  • 임의 삽입 및 삭제 지원

    GetDescendant 메서드를 사용하면 지정한 노드의 오른쪽, 지정한 노드의 왼쪽 또는 두 형제 사이에 형제를 생성할 수 있습니다. 임의 개수의 노드를 계층에서 삽입하거나 삭제할 때 비교 속성이 유지됩니다. 대부분의 삽입 및 삭제 시 압축성 속성이 유지됩니다. 그러나 두 노드 간 삽입 시에는 약간 덜 압축된 표현으로 hierarchyid 값이 생성됩니다.

hierarchyid의 제한 사항

hierarchyid 데이터 형식에는 다음과 같은 제한 사항이 있습니다.

  • hierarchyid 형식의 열은 트리를 자동으로 나타내지 않습니다. 애플리케이션에 따라 원하는 행 간 관계가 값에 반영되도록 hierarchyid 값이 생성되어 할당됩니다. 일부 애플리케이션에는 다른 테이블에 정의된 계층 구조의 위치를 나타내는 hierarchyid 형식의 열이 있을 수 있습니다.

  • 계층 구조 값을 생성하고 할당 할 때 동시성을 관리하는 것은 애플리케이션의 책임입니다. 애플리케이션이 고유 키 제약 조건을 사용하거나 고유한 논리를 통해 고유성을 적용하지 않는 한 열의 hierarchyid 값이 고유하다는 보장은 없습니다.

  • hierarchyid 값으로 표시되는 계층적 관계는 외래 키 관계처럼 적용되지 않습니다. A에 자식 B가 있고 A가 삭제되어 B가 존재하지 않는 레코드에 대한 관계로 남게 되는 계층적 관계를 가질 수 있고 때로는 적절할 수 있습니다. 이 동작이 허용되지 않는 경우 애플리케이션은 부모를 삭제하기 전에 하위 항목을 쿼리해야 합니다.

hierarchyid에 대한 대체 방법을 사용하는 경우

계층적 데이터를 나타내는 hierarchyid의 두 가지 대안은 다음과 같습니다.

  • 부모/자식

  • XML

hierarchyid 는 일반적으로 이러한 대안보다 우수합니다. 그러나 대안이 우수할 가능성이 높은 특정 상황이 아래에 자세히 설명되어 있습니다.

부모/자식

부모/자식 방법을 사용하는 경우 각 행에는 부모에 대한 참조가 포함됩니다. 다음 테이블에서는 하나의 부모/자식 관계에서 부모 및 자식 행을 포함하는 데 사용되는 일반적인 테이블을 정의합니다.

USE AdventureWorks2022;  
GO  
  
CREATE TABLE ParentChildOrg  
   (  
    BusinessEntityID int PRIMARY KEY,  
    ManagerId int REFERENCES ParentChildOrg(BusinessEntityID),  
    EmployeeName nvarchar(50)   
   ) ;  
GO  

공통 작업에 대한 부모/자식 및 계층 구조 비교

  • hierarchyid를 사용하면 하위 트리 쿼리가 훨씬 더 빨라집니다.

  • 직계 하위 쿼리는 hierarchyid약간 느립니다.

  • hierarchyid를 사용하면 리프가 아닌 노드 이동 속도가 느려집니다.

  • 리프가 아닌 노드를 삽입하고 리프 노드를 삽입하거나 이동하는 경우 hierarchyid동일한 복잡성이 있습니다.

다음 경우 부모/자식이 더 우수할 수 있습니다.

  • 키의 크기가 중요합니다. 동일한 수의 노드에 대해 hierarchyid 값은 정수 패밀리(smallint, int, bigint) 값과 같거나 큽니다. hierarchyid 에서는 부모/자식 구조를 사용할 때 필요한 공통 테이블 식보다 I/O 및 CPU 복잡성의 효율이 훨씬 증가하므로 이 경우에 한해 부모/자식을 많이 사용합니다.

  • 쿼리는 계층 구조의 섹션에서 거의 쿼리하지 않습니다. 즉, 쿼리가 일반적으로 계층의 단일 지점만 다루는 경우입니다. 이러한 경우 공동 위치는 중요하지 않습니다. 예를 들어 조직 테이블이 개별 직원에 대한 급여를 처리하는 데에만 사용되는 경우 부모/자식이 더 우수합니다.

  • 리프가 아닌 하위 트리는 자주 이동하며 성능은 매우 중요합니다. 계층 구조에서 행의 위치를 변경하는 부모/자식 표현에서 단일 행에 영향을 줍니다. 그러나 hierarchyid 사용 시 한 행의 위치를 변경하면 n 개의 행에 영향을 줍니다. 여기서 n 은 이동하는 하위 트리의 노드 수입니다.

    리프가 아닌 하위 트리가 자주 이동하고 성능이 중요하지만 대부분의 이동이 계층 구조의 잘 정의된 수준에 있는 경우 상위 수준과 하위 수준을 두 계층으로 분할하는 것이 좋습니다. 이렇게 하면 모든 이동이 더 높은 계층의 리프 수준으로 이동합니다. 예를 들어 서비스에서 호스트하는 웹 사이트의 계층 구조를 고려합니다. 사이트에는 계층으로 정렬된 많은 페이지가 있습니다. 호스트된 사이트는 사이트 계층 구조의 다른 위치로 이동할 수 있지만 하위 페이지는 거의 다시 정렬되지 않습니다. 다음을 통해 나타낼 수 있습니다.

    CREATE TABLE HostedSites   
       (  
        SiteId hierarchyid, PageId hierarchyid  
       ) ;  
    GO  
    

XML

XML 문서는 트리이므로 단일 XML 데이터 형식 인스턴스는 전체 계층 구조를 나타낼 수 있습니다. XML 인덱스가 생성 되면 SQL Server에서 계층 구조의 위치를 나타내기 위해 hierarchyid 값이 내부적으로 사용됩니다.

다음과 같은 경우 XML 데이터 형식을 사용하는 것이 더 우수할 수 있습니다.

  • 전체 계층이 항상 저장되고 검색되는 경우

  • 데이터는 애플리케이션에서 XML 형식으로 사용됩니다.

  • 조건자 검색이 매우 제한되어 있으며 성능이 중요하지 않은 경우

예를 들어 애플리케이션이 여러 조직을 추적하고, 항상 전체 조직 계층을 저장하고 검색하며, 단일 조직으로 쿼리하지 않는 경우 다음 양식의 테이블이 적합할 수 있습니다.

CREATE TABLE XMLOrg   
    (  
    Orgid int,  
    Orgdata xml  
    ) ;  
GO  

계층적 데이터에 대한 인덱싱 전략

계층적 데이터를 인덱싱하기 위한 두 가지 전략이 있습니다.

  • 깊이 우선

    깊이 우선 인덱스에서는 하위 트리의 행이 서로 가깝게 저장됩니다. 예를 들어 한 관리자를 통해 보고하는 모든 직원은 해당 관리자의 레코드에 가깝게 저장됩니다.

    깊이 우선 인덱스의 경우 노드 하위 트리의 모든 노드가 공동 배치됩니다. 따라서 "이 폴더 및 해당 하위 폴더에서 모든 파일 찾기"와 같은 하위 트리에 대한 쿼리에 답하는 데에는 깊이 우선 인덱스가 효율적입니다.

  • 너비 우선

    폭 우선 인덱스는 계층의 각 수준에 행을 함께 저장합니다. 예를 들어 같은 관리자에게 직접 보고하는 직원의 레코드는 서로 가깝게 저장됩니다.

    너비 우선 인덱스에서는 노드의 모든 직접 자식이 공동 배치됩니다. 따라서 "이 관리자에게 직접 보고하는 모든 직원 찾기"와 같은 인접한 자식에 대한 쿼리에 답하는 데에는 너비 우선 인덱스가 효율적입니다.

깊이 우선, 너비 우선 또는 둘 다를 포함할지 여부와 클러스터링 키(있는 경우)를 만들 것인지는 위의 쿼리 형식의 상대적 중요도와 SELECT 및 DML 작업의 상대적 중요도에 따라 달라집니다. 인덱싱 전략의 자세한 예는 자습서: hierarchyid 데이터 형식 사용을 참조 하세요.

인덱스 만들기

GetLevel() 메서드를 사용하여 폭 첫 번째 순서를 만들 수 있습니다. 다음 예제에서는 너비 우선 인덱스와 깊이 우선 인덱스가 모두 생성됩니다.

USE AdventureWorks2022;   -- wmimof
GO  
  
CREATE TABLE Organization  
   (  
    BusinessEntityID hierarchyid,  
    OrgLevel as BusinessEntityID.GetLevel(),   
    EmployeeName nvarchar(50) NOT NULL  
   ) ;  
GO  
  
CREATE CLUSTERED INDEX Org_Breadth_First   
    ON Organization(OrgLevel,BusinessEntityID) ;  
GO  
  
CREATE UNIQUE INDEX Org_Depth_First   
    ON Organization(BusinessEntityID) ;  
GO  

간단한 예

다음 예제는 시작하는 데 도움을 주기 위해 의도적으로 단순화된 것입니다. 먼저 일부 지리 데이터를 저장할 테이블을 만듭니다.

CREATE TABLE SimpleDemo  
(
    Level hierarchyid NOT NULL,  
    Location nvarchar(30) NOT NULL,  
    LocationType nvarchar(9) NULL
);

이제 일부 대륙, 국가/지역, 주 및 도시에 대한 데이터를 삽입합니다.

INSERT SimpleDemo  
    VALUES   
('/1/', 'Europe', 'Continent'),  
('/2/', 'South America', 'Continent'),  
('/1/1/', 'France', 'Country'),  
('/1/1/1/', 'Paris', 'City'),  
('/1/2/1/', 'Madrid', 'City'),  
('/1/2/', 'Spain', 'Country'),  
('/3/', 'Antarctica', 'Continent'),  
('/2/1/', 'Brazil', 'Country'),  
('/2/1/1/', 'Brasilia', 'City'),  
('/2/1/2/', 'Bahia', 'State'),  
('/2/1/2/1/', 'Salvador', 'City'),  
('/3/1/', 'McMurdo Station', 'City');  

데이터를 선택하고 수준 데이터를 이해하기 쉬운 텍스트 값으로 변환하는 열을 추가합니다. 또한 이 쿼리는 hierarchyid 데이터 형식을 기준으로 결과를 정렬합니다.

SELECT CAST(Level AS nvarchar(100)) AS [Converted Level], *   
    FROM SimpleDemo ORDER BY Level;  

결과 집합은 다음과 같습니다.

Converted Level  Level     Location         LocationType  
/1/              0x58      Europe           Continent  
/1/1/            0x5AC0    France           Country  
/1/1/1/          0x5AD6    Paris            City  
/1/2/            0x5B40    Spain            Country  
/1/2/1/          0x5B56    Madrid           City  
/2/              0x68      South America    Continent  
/2/1/            0x6AC0    Brazil           Country  
/2/1/1/          0x6AD6    Brasilia         City  
/2/1/2/          0x6ADA    Bahia            State  
/2/1/2/1/        0x6ADAB0  Salvador         City  
/3/              0x78      Antarctica       Continent  
/3/1/            0x7AC0    McMurdo Station  City  

내부적으로 일관되지 않더라도 계층 구조에는 유효한 구조가 있습니다. 바히아는 유일한 국가입니다. 도시 Brasilia의 피어로 계층에 나타납니다. 마찬가지로, 맥머도 역에는 부모 국가 또는 지역이 없습니다. 사용자는 이 유형의 계층이 해당 계층에 적합한지 결정해야 합니다.

다른 행을 추가하고 결과를 선택합니다.

INSERT SimpleDemo  
    VALUES ('/1/3/1/', 'Kyoto', 'City'), ('/1/3/1/', 'London', 'City');  
SELECT CAST(Level AS nvarchar(100)) AS [Converted Level], * FROM SimpleDemo ORDER BY Level;  

이것은 더 많은 가능한 문제를 보여줍니다. 교토는 부모 수준이 /1/3/없어도 수준으로 /1/3/1/ 삽입할 수 있습니다. 그리고 London과 Kyoto의 hierarchyid값은 동일합니다. 다시 말하지만, 사용자는 이 유형의 계층 구조가 해당 용도에 적합한지 결정하고 사용법이 잘못된 값을 차단해야 합니다.

또한 이 테이블은 계층 '/'구조의 맨 위를 사용하지 않았습니다. 모든 대륙의 공통 부모가 없기 때문에 생략되었습니다. 행성 전체를 추가하여 추가할 수 있습니다.

INSERT SimpleDemo  
    VALUES ('/', 'Earth', 'Planet');  

관련 작업

부모/자식에서 hierarchyid로 마이그레이션

대부분의 트리는 부모/자식으로 표시됩니다. hierarchyid를 사용하여 부모/자식 구조에서 테이블로 마이그레이션하는 가장 쉬운 방법은 임시 열 또는 임시 테이블을 사용하여 계층의 각 수준에서 노드 수를 추적하는 것입니다. 부모/자식 테이블을 마이그레이션하는 예제는 자습서 1 단원: hierarchyid 데이터 형식 사용을 참조하세요.

hierarchyid를 사용하여 트리 관리

hierarchyid 열이 반드시 트리를 나타내는 것은 아니지만 애플리케이션에서 쉽게 트리를 나타낼 수 있습니다.

  • 새 값을 생성할 때 다음 중 하나를 수행합니다.

    • 부모 행의 마지막 자식 번호를 추적합니다.

    • 마지막 자식을 컴퓨팅합니다. 이 작업을 효율적으로 수행하려면 폭 우선 인덱스가 필요합니다.

  • 클러스터링 키의 일부로 열에 고유한 인덱스 만들기를 통해 고유성을 적용합니다. 고유한 값이 삽입되도록 하려면 다음 중 하나를 수행합니다.

    • 고유 키 위반 오류를 검색하고 다시 시도합니다.

    • 각 새 자식 노드의 고유성을 확인하고 직렬화 가능한 트랜잭션의 일부로 삽입합니다.

오류 검색을 사용하는 예제

다음 예제에서 샘플 코드는 새 자식 EmployeeId 값을 계산한 다음 키 위반을 감지하고 INS_EMP 표식으로 반환하여 새 행에 대한 EmployeeId 값을 다시 계산합니다.

USE AdventureWorks ;  
GO  
  
CREATE TABLE Org_T1  
   (  
    EmployeeId hierarchyid PRIMARY KEY,  
    OrgLevel AS EmployeeId.GetLevel(),  
    EmployeeName nvarchar(50)   
   ) ;  
GO  
  
CREATE INDEX Org_BreadthFirst ON Org_T1(OrgLevel, EmployeeId);
GO  
  
CREATE PROCEDURE AddEmp(@mgrid hierarchyid, @EmpName nvarchar(50) )   
AS  
BEGIN  
    DECLARE @last_child hierarchyid;
INS_EMP:   
    SELECT @last_child = MAX(EmployeeId) FROM Org_T1   
        WHERE EmployeeId.GetAncestor(1) = @mgrid;
    INSERT INTO Org_T1 (EmployeeId, EmployeeName)  
        SELECT @mgrid.GetDescendant(@last_child, NULL), @EmpName;
-- On error, return to INS_EMP to recompute @last_child  
IF @@error <> 0 GOTO INS_EMP   
END ;  
GO  

직렬화 가능 트랜잭션 사용 예

Org_BreadthFirst 인덱스는 @last_child 결정하는 데 범위 검색이 사용되도록 합니다. 애플리케이션에서 확인할 수 있는 다른 오류 사례 외에도 삽입 후 중복 키 위반은 ID가 같은 여러 직원을 추가하려는 시도를 나타내므로 @last_child 다시 계산해야 합니다. 다음 코드는 직렬화 가능 트랜잭션 내에서 새 노드 값을 컴퓨팅합니다.

CREATE TABLE Org_T2  
    (  
    EmployeeId hierarchyid PRIMARY KEY,  
    LastChild hierarchyid,   
    EmployeeName nvarchar(50)   
    ) ;  
GO  
  
CREATE PROCEDURE AddEmp(@mgrid hierarchyid, @EmpName nvarchar(50))   
AS  
BEGIN  
DECLARE @last_child hierarchyid  
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE  
BEGIN TRANSACTION   
  
SELECT @last_child  =  EmployeeId.GetDescendant(LastChild,NULL)
FROM Org_T2
WHERE EmployeeId = @mgrid

UPDATE Org_T2 SET LastChild = @last_child  WHERE EmployeeId = @mgrid

INSERT Org_T2 (EmployeeId, EmployeeName)   
    VALUES(@last_child, @EmpName)  
COMMIT  
END ;  

다음 코드에서는 테이블을 3개의 행으로 채우고 결과를 반환합니다.

INSERT Org_T2 (EmployeeId, EmployeeName)   
    VALUES(hierarchyid::GetRoot(), 'David') ;  
GO  
AddEmp 0x , 'Sariya'  
GO  
AddEmp 0x58 , 'Mary'  
GO  
SELECT * FROM Org_T2  

결과 집합은 다음과 같습니다.

EmployeeId LastChild EmployeeName  
---------- --------- ------------  
0x        0x58       David  
0x58      0x5AC0     Sariya  
0x5AC0    NULL       Mary  

트리 적용

위의 예제에서는 애플리케이션이 트리를 유지 관리하는 방법을 보여 줍니다. 제약 조건을 사용하여 트리를 적용하려면 외래 키 제약 조건을 사용하여 각 노드의 부모를 정의하는 계산 열을 기본 키 ID로 다시 만들 수 있습니다.

CREATE TABLE Org_T3  
(  
   EmployeeId hierarchyid PRIMARY KEY,  
   ParentId AS EmployeeId.GetAncestor(1) PERSISTED    
      REFERENCES Org_T3(EmployeeId),  
   LastChild hierarchyid,   
   EmployeeName nvarchar(50)  
)  
GO  

계층 트리를 유지하기 위해 신뢰할 수 없는 코드가 테이블에 대한 직접 DML 액세스 권한이 있는 경우 관계를 적용하는 이 방법을 사용하는 것이 좋습니다. 그러나 이 메서드는 모든 DML 작업에서 제약 조건을 확인해야 하므로 성능이 저하될 수 있습니다.

CLR을 사용하여 상위 항목 찾기

계층 구조의 두 노드와 관련된 일반적인 작업은 가장 낮은 공통 상위 항목을 찾는 것입니다. hierarchyid 형식은 둘 다에서 사용할 수 있으므로 Transact-SQL 또는 CLR로 작성할 수 있습니다. 성능이 더 빨라지므로 CLR을 사용하는 것이 좋습니다.

다음 CLR 코드를 사용하여 상위 항목을 나열하고 수준이 가장 낮은 공통 상위 항목을 찾을 수 있습니다.

using System;  
using System.Collections;  
using System.Text;  
using Microsoft.SqlServer.Server;  // SqlFunction Attribute
using Microsoft.SqlServer.Types;   // SqlHierarchyId
  
public partial class HierarchyId_Operations  
{  
    [SqlFunction(FillRowMethodName = "FillRow_ListAncestors")]
    public static IEnumerable ListAncestors(SqlHierarchyId h)
    {  
        while (!h.IsNull)  
        {  
            yield return (h);  
            h = h.GetAncestor(1);  
        }  
    }  
    public static void FillRow_ListAncestors(
        Object obj,
        out SqlHierarchyId ancestor
        )
    {  
        ancestor = (SqlHierarchyId)obj;  
    }  
  
    public static HierarchyId CommonAncestor(
        SqlHierarchyId h1,
        HierarchyId h2
        )  
    {  
        while (!h1.IsDescendantOf(h2))  
            h1 = h1.GetAncestor(1);  
  
        return h1;  
    }  
}  

다음 Transact-SQL 예제에서 ListAncestor 및 CommonAncestor 메서드를 사용하려면 다음과 유사한 코드를 실행하여 DLL을 빌드하고 SQL Server에서 HierarchyId_Operations 어셈블리를 만듭니다.

CREATE ASSEMBLY HierarchyId_Operations   
    FROM '<path to DLL>\ListAncestors.dll';
GO  

상위 항목 나열

노드의 상위 항목 목록을 만드는 것은 일반적인 작업입니다(예: 조직의 위치 표시). 이 작업을 수행하는 한 가지 방법은 위에서 정의한 HierarchyId_Operations 클래스를 사용하여 테이블 반환 함수를 사용하는 것입니다.

Transact-SQL 사용:

CREATE FUNCTION ListAncestors (@node hierarchyid)  
RETURNS TABLE (node hierarchyid)  
AS  
EXTERNAL NAME HierarchyId_Operations.HierarchyId_Operations.ListAncestors  
GO  

사용 예:

DECLARE @h hierarchyid  
SELECT @h = OrgNode   
FROM HumanResources.EmployeeDemo    
WHERE LoginID = 'adventure-works\janice0' -- /1/1/5/2/  
  
SELECT LoginID, OrgNode.ToString() AS LogicalNode  
FROM HumanResources.EmployeeDemo AS ED  
JOIN ListAncestors(@h) AS A   
   ON ED.OrgNode = A.Node  
GO  

수준이 가장 낮은 공통 상위 항목 찾기

위에 정의된 HierarchyId_Operations 클래스를 사용하여 다음 Transact-SQL 함수를 만들어 계층 구조의 두 노드와 관련된 가장 낮은 공통 상위 항목을 찾습니다.

CREATE FUNCTION CommonAncestor (@node1 hierarchyid, @node2 hierarchyid)  
RETURNS hierarchyid  
AS  
EXTERNAL NAME HierarchyId_Operations.HierarchyId_Operations.CommonAncestor  
GO  

사용 예:

DECLARE @h1 hierarchyid, @h2 hierarchyid;
  
SELECT @h1 = OrgNode   
FROM  HumanResources.EmployeeDemo   
WHERE LoginID = 'adventure-works\jossef0'; -- Node is /1/1/3/  
  
SELECT @h2 = OrgNode   
FROM HumanResources.EmployeeDemo    
WHERE LoginID = 'adventure-works\janice0'; -- Node is /1/1/5/2/  
  
SELECT OrgNode.ToString() AS LogicalNode, LoginID   
FROM HumanResources.EmployeeDemo    
WHERE OrgNode = dbo.CommonAncestor(@h1, @h2) ;  

결과 노드는 /1/1/입니다.

하위 트리 이동

또 다른 일반적인 작업은 하위 트리를 이동하는 것입니다. 아래 절차에서는 @oldMgr 하위 트리를 사용하고(@oldMgr 포함) @newMgr 하위 트리로 만듭니다.

CREATE PROCEDURE MoveOrg(@oldMgr nvarchar(256), @newMgr nvarchar(256) )  
AS  
BEGIN  
DECLARE @nold hierarchyid, @nnew hierarchyid  
SELECT @nold = OrgNode FROM HumanResources.EmployeeDemo WHERE LoginID = @oldMgr ;  
  
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE  
BEGIN TRANSACTION  
SELECT @nnew = OrgNode FROM HumanResources.EmployeeDemo WHERE LoginID = @newMgr ;  
  
SELECT @nnew = @nnew.GetDescendant(max(OrgNode), NULL)   
FROM HumanResources.EmployeeDemo WHERE OrgNode.GetAncestor(1)=@nnew ;  
  
UPDATE HumanResources.EmployeeDemo    
SET OrgNode = OrgNode.GetReparentedValue(@nold, @nnew)  
WHERE OrgNode.IsDescendantOf(@nold) = 1 ;  
  
COMMIT TRANSACTION;
END ;  
GO  

참고 항목

hierarchyid 데이터 형식 메서드 참조
Tutorial: Using the hierarchyid Data Type
hierarchyid(Transact-SQL)