看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怎樣可以查詢得到,請教一下大家???
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