The road to C# master trapemiya

C#を中心に、.NETの話題を取り上げます。

ホーム 連絡をする 同期する ( RSS 2.0 ) Login
投稿数  256  : 記事  1  : コメント  10763  : トラックバック  30

ニュース

Since 2005年10月26日

わんくま同盟

わんくま同盟

Microsoft MVP


Visual Developer - Visual C#

記事カテゴリ

書庫

日記カテゴリ

2009年10月29日 #

RDBのデータ型
http://blogs.wankuma.com/ognac/archive/2009/10/28/182484.aspx

で、ついhierarchyid型について口走ってしまったので、以下にまとめてみました。どうせまとめようと思っていたので、良いきっかけになって良かったです。

SQL Server 2008でhierarchyid型が追加されました。これによってレコードを階層構造で管理できるようになります。
ここでは詳しく説明しませんので、ご存じない方はまず以下をご覧になると良いと思います。

hierarchyid データ型の使用 (データベース エンジン)
http://msdn.microsoft.com/ja-jp/library/bb677173.aspx

ここではhierarchyid型によって管理された階層構造毎に集計するデモを掲載します。ある会社の営業売上を階層毎に集計してみましょう。以下のような売上があるとします。

全社 支社 売上
全社        
  東京支社      
    統括事業部    
      営業1課 10000
      営業2課 50000
  大阪支社      
    営業部    
      第1営業課 20000
      第2営業課 30000

目標は以下のように階層毎に集計することです。

全社 支社 売上 集計
全社         110000
  東京支社       60000
    統括事業部     60000
      営業1課 10000 10000
      営業2課 50000 50000
  大阪支社       50000
    営業部     50000
      第1営業課 20000 20000
      第2営業課 30000 30000

 

以上を実現するために組織と組織の売上という二つのテーブルを作成します。

まずは組織テーブルからです。hierarchyid型を使用して作成します。

create table OR01Organization
(
    OR01OrganizationID       int            identity (1,1)        not null  primary key,  
    OR01Hierarchyid            hierarchyid                          not null,  
    OR01HierarchyLevel       as OR01Hierarchyid.GetLevel() ,         
    OR01OrganizationName   nvarchar(30)                        not null  
)


ここではOR01Hierarchyidを主キーにしていませんので、インデックスを作成しておきます。


create unique index OR01OrganizationIK01 on OR01Organization (OR01Hierarchyid)


続いて売上テーブルを作成します。組織テーブルとの間に参照整合性制約を作成しています。


create table SA01Sales
(
    SA01SalesID            int  identity (1,1)  not null primary key,  
    SA01OrganizationID   int                  not null,
    SA01Sales               int  
)

alter table SA01Sales
add constraint SA01SalesFK01 foreign key (SA01OrganizationID)
    references OR01Organization(OR01OrganizationID)


データを作成します。

insert into OR01Organization (OR01Hierarchyid, OR01OrganizationName) values
(hierarchyid::GetRoot(), '全社'),
('/1/', '東京支社'),
('/1/1/', '統括事業部'),
('/1/1/1/', '営業1課'),
('/1/1/2/', '営業2課'),
('/2/', '大阪支社'),
('/2/1/', '営業部'),
('/2/1/1/', '第1営業課'),
('/2/1/2/', '第2営業課')

ここまで出来たら一度組織図を表示してみましょう。

select OR01Hierarchyid.ToString() as Path,
       OR01OrganizationName as Name,
       OR01HierarchyLevel as Level
  from OR01Organization order by OR01Hierarchyid


以下のように表示されればOKです。


Path       Name            Level
/            全社               0
/1/         東京支社          1
/1/1/       統括事業部       2
/1/1/1/    営業1課          3
/1/1/2/    営業2課          3
/2/          大阪支社         1
/2/1/       営業部            2
/2/1/1/    第1営業課        3
/2/1/2/    第2営業課        3


Nameにインデントが無くて見難い場合には、以下のようにLevel毎に適当に頭に空白を入れると良いでしょう。

space(OR01HierarchyLevel * 5) + OR01OrganizationName as Name


では次に売上データを作成していきます。

SA01OrganizationIDはOR01OrganizationIDを指定しますが、OR01OrganizationIDは自動インクリメント(オートナンバー)なので、レコードが追加された順に1, 2, 3, ・・・となります。


insert into SA01Sales (SA01OrganizationID, SA01Sales) values
(4, 10000),   --東京支社 営業1課
(5, 50000),   --東京支社 営業2課
(8, 20000),   --大阪支社 第1営業課
(9, 30000)    --大阪支社 第2営業課


さて、いよいよ階層毎の集計をします。階層の集計は、自分にぶら下がっている全ての子供の売上データの合計になります。OR01Hierarchyidというhierarchyid型を辿れば、自分にぶら下がっている全ての子供である組織データが見つかります。
その見つかった組織データにぶら下がっている売上データを合計すれば目的が達せられます。
これを実現した関数が以下になります。
CTE内では外部結合ができませんので、select文を使って売上データを求めています。

create function SumAllSalesUnderMe
(
    @hierarchyid as hierarchyid
)
returns bigint
as
begin

declare @compwk bigint;

with CTE(CTEHierarchyid, CTESales)
AS
(
    select  OR01Hierarchyid, (select SA01Sales from SA01Sales as Sa1 where Sa1.SA01OrganizationID = OR01OrganizationID)
                                    from OR01Organization
                                    where OR01Hierarchyid = @hierarchyid
    union all
    select  OR01Hierarchyid, (select SA01Sales from SA01Sales as Sa2 where Sa2.SA01OrganizationID = Or1.OR01OrganizationID)
                                    from OR01Organization as Or1
                                    inner join CTE as c on c.CTEHierarchyid = Or1.OR01Hierarchyid.GetAncestor(1)
)

select @compwk = sum(CTESales) from CTE

return @compwk

end


これで全ての準備が整いました。以下のようにselect文を発行すれば、階層毎に集計された結果が得られます。


select OR01Hierarchyid.ToString() as Path,
       space(OR01HierarchyLevel * 5) + OR01OrganizationName as Name,
       OR01HierarchyLevel as Level,
       dbo.SumAllSalesUnderMe(OR01Hierarchyid) as SubTotal
  from OR01Organization order by OR01Hierarchyid

 

Path Name Level SubTotal
/ 全社 0 110000
/1/      東京支社 1 60000
/1/1/          統括事業部 2 60000
/1/1/1/              営業1課 3 10000
/1/1/2/              営業2課 3 50000
/2/        大阪支社 1 50000
/2/1/          営業部 2 50000
/2/1/1/              第1営業課 3 20000
/2/1/2/              第2営業課 3 30000
posted @ 15:05 | Feedback (343)