一個有趣的SQL Server 層級匯總數據問題


    看SQL Server 大V宋大俠的博客文章,發現了一個有趣的sql server 層級匯總數據問題
    
    具體的問題如下:
    parent_id emp_id emp_name total_amout 
    NULL 2 Andrew 200 
    2 1 Nancy 100 
    2 3 Janet 120 
    3 4 Michael 80 
    1 5 Robert 50 
    每個員工的總銷售額=自己的銷售額+其下級員工的總銷售額, 
    比如: 
    Andrew = 200_100_120_80_50=550 
    Nancy = 100+50=150 
    Janet = 120+80=200 
    Michael = 80 
    Robert = 50 
    這個用SQL怎樣可以查詢得到,請教一下大家???
 
    從數據表中的數據以及問題闡述來看可以確定該數據表是個父子層級類型數據表,這個在緯度類型中是一種比較常見:父子緯度。 從名字解釋來看就是一種自引用的數據表,大家最熟悉的組織機構就是具有這種層級結構,其中不同級別的機構具有共同的特性。這種層級結構如下圖所示:
以上圖來自百度查詢得到。
         
     看來宋大俠針對該問題的解決方案(CTE遞歸查詢+游標),還有其他的博友的評論(有的支出數據表設計的不完善,還有通過虛擬出層級層級字符串列來實現的,還有獲取當前層級以下所有層級的匯總封裝成存儲等等)。
    
    為了實現該問題,我使用的是CTE遞歸查詢+APPLY,具體的實現思路如下:
    1、通過CTE遞歸查詢虛擬出若干列,其中就有層級索引字符串列(該列表示具有層級的標識ID的字符串格式,便於查找)。
    2、使用APPLY來實現匯總數據(當然也可以使用SELECT + SUBQUERY)。
   
具體演示實現代碼如下:
 1 IF OBJECT_ID(N'dbo.MyEmp', N'U') IS NOT NULL
 2 BEGIN
 3     DROP TABLE dbo.MyEmp;  4 END
 5 GO
 6 
 7 CREATE TABLE dbo.MyEmp (  8     MyEmpID INT NOT NULL,  9     ParentID INT NULL,  10     MyEmpName NVARCHAR(20) NOT NULL,  11     HoursSalary INT NOT NULL
 12 );  13 GO
 14 
 15 IF OBJECT_ID(N'PK_U_CL_MyEmp_MyEmpID', N'PK') IS NULL
 16 BEGIN
 17     ALTER TABLE [dbo].[MyEmp] ADD  CONSTRAINT [PK_U_CL_MyEmp_MyEmpID] PRIMARY KEY CLUSTERED 
 18  (  19         [MyEmpID] ASC
 20     )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90)  21     ON [PRIMARY];  22 END
 23 GO
 24 
 25 IF OBJECT_ID(N'FK_MyEmp_MyEmp_ParentID', N'F') IS NULL
 26 BEGIN
 27     ALTER TABLE dbo.MyEmp ADD CONSTRAINT FK_MyEmp_MyEmp_ParentID FOREIGN KEY (ParentID) REFERENCES dbo.MyEmp (MyEmpID);  28 END
 29 GO
 30 
 31 -- Insert Test Data
 32 INSERT INTO dbo.MyEmp (MyEmpID, ParentID, MyEmpName, HoursSalary) VALUES  
 33 (1, NULL, N'Andrew', 200),  34 (2, 1, N'Nancy', 100),  35 (3, 1, N'Janet', 120),  36 (4, 3, N'Michael', 80),  37 (5, 2, N'Robert', 50)  38 GO
 39 
 40 ;WITH tData (MyEmpID, MyEmpName, ParentID, ParentName, HoursSalary, ParentHierarchyIndex, HierarchyIndex, LevelID, HierarchyName, HierarchyName2) AS (  41     -- 基准點查詢
 42     SELECT MyEmpID                                                                                                /*雇員ID*/
 43         ,MyEmpName                                                                                                /*雇員名稱*/
 44         ,ISNULL(ParentID, 0) AS ParentID                                                                        /*父雇員ID*/
 45         ,CAST(N'' AS NVARCHAR(20)) AS ParentName                                                                /*父雇員名稱*/
 46         ,HoursSalary                                                                                            /*小時薪水*/
 47         ,CAST(CONCAT(',', ISNULL(ParentID, 0), ',') AS VARCHAR(300)) AS ParentHierarchyIndex                    /*父層級索引字符串*/
 48         ,CAST(CONCAT(',', ISNULL(ParentID, 0), ',', MyEmpID, ',') AS VARCHAR(300)) AS HierarchyIndex            /*層級索引字符串串,包含當前層級*/ 
 49         ,CAST(1 AS INT) AS LevelID                                                                                /*層級ID,根層級為1,層級越深則數字越大*/
 50         ,CAST(MyEmpName AS NVARCHAR(800)) AS HierarchyName                                                        /*層級名稱,樹形結構顯示*/
 51         ,CAST(MyEmpName AS NVARCHAR(80)) AS HierarchyName2                                                        /*層級名稱2,水平結構顯示*/
 52     FROM dbo.MyEmp  53     WHERE ParentID IS NULL
 54     -- 遞歸查詢
 55     UNION ALL
 56     SELECT T.MyEmpID  57  ,T.MyEmpName  58  ,T.ParentID  59  ,T2.MyEmpName  60  ,T.HoursSalary  61         ,CAST(CONCAT(T2.ParentHierarchyIndex, T.ParentID, ',') AS VARCHAR(300)) AS ParentHierarchyIndex  62         ,CAST(CONCAT(T2.HierarchyIndex, T.MyEmpID, ',') AS VARCHAR(300)) AS HierarchyIndex  63         ,T2.LevelID + 1 AS LevelID  64         ,CAST(CONCAT(REPLICATE(N'| ', T2.LevelID), T.MyEmpName) AS NVARCHAR(800)) AS HierarchyName  65         ,CAST(CONCAT(T2.HierarchyName2, '->', T.MyEmpName) AS NVARCHAR(80)) AS HierarchyName2  66     FROM dbo.MyEmp AS T  67         INNER JOIN tData AS T2  68             ON T.ParentID = T2.MyEmpID  69 )  70 
 71 -- 使用HierarchyIndex來實現
 72 
 73 -- CROSS APPLY
 74 SELECT T.*, T2.TotalSalary AS TotalSalary  75 FROM tData AS T  76     CROSS APPLY (SELECT SUM(tData.HoursSalary) AS TotalSalary  77     FROM tData  78     WHERE HierarchyIndex LIKE CONCAT(T.HierarchyIndex, '%')) AS T2  79 ORDER BY T.HierarchyIndex ASC;  80 
 81 -- SELECT + 子查詢
 82 --SELECT T.*, TotalSalary = ( SELECT SUM(tData.HoursSalary) FROM tData WHERE tData.HierarchyIndex LIKE CONCAT(T.HierarchyIndex, '%'))
 83 --FROM tData AS T
 84 --ORDER BY T.HierarchyIndex ASC;
 85 
 86 -- 使用ParentHierarchyIndex
 87 -- CROSS APPLY
 88 --SELECT T.*, T.HoursSalary + T2.DownMemberTotalHoursSalary AS TotalSalary
 89 --FROM tData AS T
 90 -- CROSS APPLY (
 91 -- SELECT ISNULL(SUM(tData.HoursSalary), 0) AS DownMemberTotalHoursSalary
 92 -- FROM tData
 93 -- WHERE tData.ParentHierarchyIndex LIKE CONCAT(T.ParentHierarchyIndex, T.MyEmpID, '%')
 94 -- ) AS T2
 95 --ORDER BY T.HierarchyIndex ASC;
 96 
 97 -- SELECT + 子查詢
 98 --SELECT T.*, T.HoursSalary + (SELECT ISNULL(SUM(tData.HoursSalary), 0) AS DownMemberTotalHoursSalary
 99 -- FROM tData
100 -- WHERE tData.ParentHierarchyIndex LIKE CONCAT(T.ParentHierarchyIndex, T.MyEmpID, '%')) AS TotalSalary
101 --FROM tData AS T
102 --ORDER BY T.HierarchyIndex ASC;
103 GO

以上解決方案是在不修改數據結構的情況下來實現的,從以上解決方案中,我們可以從數據表的設計入手,將虛擬出來的父層級索引字符串列增加到數據表中,將該列創建為聚集索引, 便於提高查詢性能。

 增加新列的T-SQL腳本如下:
1 IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE OBJECT_ID = OBJECT_ID(N'dbo.MyEmp', N'U') AND name = N'HierarchyIndex') 2 BEGIN
3     ALTER TABLE dbo.MyEmp ADD HierarchyIndex VARCHAR(800) NOT NULL CONSTRAINT DF_MyEmp_HierarchyIndex DEFAULT ''; 4 END
5 GO

 

如果該列創建為聚集且唯一,則相應的T-SQL腳本如下:

 

 

 1 -- 刪除外鍵
 2 IF OBJECT_ID(N'FK_MyEmp_MyEmp_ParentID', N'F') IS NOT NULL
 3 BEGIN
 4     ALTER TABLE dbo.MyEmp DROP CONSTRAINT FK_MyEmp_MyEmp_ParentID;  5 END
 6 GO
 7 
 8 -- 刪除主鍵
 9 IF OBJECT_ID(N'PK_U_CL_MyEmp_MyEmpID', N'PF') IS NULL
10 BEGIN
11     ALTER TABLE dbo.MyEmp DROP CONSTRAINT PK_U_CL_MyEmp_MyEmpID; 12 END
13 GO
14 
15 -- 創建(唯一:語義分析得到的,沒有使用創建UNIQUE關鍵字)聚集索引
16 IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE OBJECT_ID = OBJECT_ID(N'dbo.MyEmp', N'U') AND name = N'IX_U_CL_MyEmp_HierarchyIndex') 17 BEGIN
18     CREATE CLUSTERED INDEX IX_U_CL_MyEmp_HierarchyIndex ON dbo.MyEmp 19  ( 20         HierarchyIndex ASC
21     ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) 22 END
23 GO
24 
25 -- 創建主鍵且非聚集索引
26 IF OBJECT_ID(N'PK_U_CL_MyEmp_MyEmpID', N'PK') IS NULL
27 BEGIN
28     ALTER TABLE [dbo].[MyEmp] ADD  CONSTRAINT [PK_U_NCL_MyEmp_MyEmpID] PRIMARY KEY NONCLUSTERED 
29  ( 30         [MyEmpID] ASC
31     )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) 32     ON [PRIMARY]; 33 END
34 GO
35 
36 -- 創建外鍵
37 IF OBJECT_ID(N'FK_MyEmp_MyEmp_ParentID', N'F') IS NULL
38 BEGIN
39     ALTER TABLE dbo.MyEmp ADD CONSTRAINT FK_MyEmp_MyEmp_ParentID FOREIGN KEY (ParentID) REFERENCES dbo.MyEmp (MyEmpID); 40 END
41 GO

 

同步HierarchyIndex字段列值的T-SQL如下:

 1 ;WITH tData (MyEmpID, HierarchyIndex) AS (  2     -- 基准點查詢
 3     SELECT MyEmpID, CAST(CONCAT(',', ISNULL(ParentID, 0), ',', MyEmpID, ',') AS VARCHAR(300)) AS HierarchyIndex  4     FROM dbo.MyEmp  5     WHERE ParentID IS NULL
 6     -- 遞歸查詢
 7     UNION ALL
 8     SELECT T.MyEmpID, CAST(CONCAT(T2.HierarchyIndex, T.MyEmpID, ',') AS VARCHAR(300)) AS HierarchyIndex  9     FROM dbo.MyEmp AS T /*子表*/
10         INNER JOIN tData AS T2 /*父表*/
11             ON T.ParentID = T2.MyEmpID 12 ) 13 
14 --SELECT T.*
15 UPDATE T2 16 SET T2.HierarchyIndex = T.HierarchyIndex 17 FROM tData AS T 18     INNER JOIN dbo.MyEmp AS T2 19         ON T.MyEmpID = T2.MyEmpID 20 WHERE T2.HierarchyIndex = ''; 21 GO

使用以下T-SQL驗證數據是否已經修改:

1 SELECT MyEmpID, ParentID, MyEmpName, HoursSalary, HierarchyIndex 2 FROM dbo.MyEmp; 3 GO
解決該問題的解決方案的T-SQL語句如下:
1 SELECT T.MyEmpID, T.ParentID, T.MyEmpName, T.HoursSalary, T.HierarchyIndex, T2.TotalSalary AS TotalSalary 2 FROM dbo.MyEmp AS T 3     CROSS APPLY (SELECT SUM(HoursSalary) AS TotalSalary FROM dbo.MyEmp WHERE HierarchyIndex LIKE CONCAT(T.HierarchyIndex, '%')) AS T2; 4 GO
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM