交叉表 (Cross Tabulation) 將長資料 (Long Format) 轉為寬資料 (Wide Format):**行標題固定、列標題來自資料值、儲存格為聚合值**。SQL Server 透過 PIVOT 操作子 + 動態 SQL 可完整實現。本文提供生產級動態交叉表儲存程序 Pro_CrossTable,支援動態列、自動加總列/行、百分比計算。
#Temp_CrossTable_001 (ColName1, ColName2, Value)。ColName1 值,構建 PIVOT IN (...) 列表、SUM 列表、總和運算式、百分比運算式。PIVOT 產生數值交叉表 #Out_CrossTable_Value,並 UNION ALL 加總行。#Out_CrossTable_Percent。
CREATE PROCEDURE dbo.Pro_CrossTable
@TableName NVARCHAR(255), -- 來源表名
@ColName1 NVARCHAR(255), -- 列標題來源欄位 (將變為 PIVOT 列)
@ColName2 NVARCHAR(255) -- 行標題來源欄位
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- 1. 彙總基礎資料
IF OBJECT_ID(N'tempdb..#Temp_CrossTable_001') IS NOT NULL DROP TABLE #Temp_CrossTable_001;
CREATE TABLE #Temp_CrossTable_001 (
ColName1 NVARCHAR(500) NULL,
ColName2 NVARCHAR(500) NOT NULL,
Value FLOAT NULL
);
DECLARE @SQL NVARCHAR(MAX) = N'
INSERT INTO #Temp_CrossTable_001
SELECT ' + QUOTENAME(@ColName1) + N', ' + QUOTENAME(@ColName2) + N', COUNT(*)
FROM ' + QUOTENAME(@TableName) + N'
WHERE ' + QUOTENAME(@ColName1) + N' IS NOT NULL
GROUP BY ' + QUOTENAME(@ColName1) + N', ' + QUOTENAME(@ColName2);
EXEC sp_executesql @SQL;
-- 2. 動態構建 PIVOT 所需字串
DECLARE @Cols NVARCHAR(MAX), @SumCols NVARCHAR(MAX), @TotalExpr NVARCHAR(MAX), @PctExpr NVARCHAR(MAX);
-- 2.1 列清單: [Val1],[Val2],...
SELECT @Cols = STUFF((
SELECT N', ' + QUOTENAME(ColName1)
FROM (SELECT DISTINCT ColName1 FROM #Temp_CrossTable_001) t
FOR XML PATH(N''), TYPE).value(N'.', N'NVARCHAR(MAX)'), 1, 2, N'');
-- 2.2 SUM 列清單: SUM([Val1]),SUM([Val2]),...
SELECT @SumCols = STUFF((
SELECT N', SUM(' + QUOTENAME(ColName1) + N')'
FROM (SELECT DISTINCT ColName1 FROM #Temp_CrossTable_001) t
FOR XML PATH(N''), TYPE).value(N'.', N'NVARCHAR(MAX)'), 1, 2, N'');
-- 2.3 總和運算式: [Val1]+[Val2]+...
SELECT @TotalExpr = STUFF((
SELECT N'+' + QUOTENAME(ColName1)
FROM (SELECT DISTINCT ColName1 FROM #Temp_CrossTable_001) t
FOR XML PATH(N''), TYPE).value(N'.', N'NVARCHAR(MAX)'), 1, 1, N'');
-- 2.4 百分比運算式: t1.[Val1], t1.[Val1]/t2.[Val1] AS [Val1%], ...
SELECT @PctExpr = STUFF((
SELECT N', ' + QUOTENAME(ColName1) + N', ' + QUOTENAME(ColName1) + N'/CONVERT(FLOAT, t2.' + QUOTENAME(ColName1) + N') AS ' + QUOTENAME(ColName1 + N'%')
FROM (SELECT DISTINCT ColName1 FROM #Temp_CrossTable_001) t
FOR XML PATH(N''), TYPE).value(N'.', N'NVARCHAR(MAX)'), 1, 2, N'');
-- 3. 執行 PIVOT 產生數值表 (含加總行)
SET @SQL = N'
IF OBJECT_ID(N''tempdb..#Out_CrossTable_Value'') IS NOT NULL DROP TABLE #Out_CrossTable_Value;
SELECT * INTO #Out_CrossTable_Value FROM (
SELECT ColName1, ColName2, Value FROM #Temp_CrossTable_001
) P
PIVOT (SUM(Value) FOR ColName1 IN (' + @Cols + N')) AS T
UNION ALL
SELECT N''SumCol'', ' + @SumCols + N', SUM(' + @TotalExpr + N')
FROM (
SELECT ColName1, ColName2, Value FROM #Temp_CrossTable_001
) P
PIVOT (SUM(Value) FOR ColName1 IN (' + @Cols + N')) AS T';
EXEC sp_executesql @SQL;
-- 4. 產生百分比表
SET @SQL = N'
IF OBJECT_ID(N''tempdb..#Out_CrossTable_Percent'') IS NOT NULL DROP TABLE #Out_CrossTable_Percent;
SELECT
t1.ColName2 AS ' + QUOTENAME(@ColName2) + N',
' + @PctExpr + N'
FROM #Out_CrossTable_Value t1
CROSS JOIN (
SELECT ' + @Cols + N' FROM #Out_CrossTable_Value WHERE ColName2 = N''SumCol''
) t2;';
EXEC sp_executesql @SQL;
COMMIT TRANSACTION;
RETURN 0;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
THROW;
RETURN 1;
END CATCH
END
GO
-- 來源表 temp_A063,列標題 ageArrange,行標題 indate
EXEC dbo.Pro_CrossTable
@TableName = N'temp_A063',
@ColName1 = N'ageArrange',
@ColName2 = N'indate';
-- 結果查詢
SELECT * FROM #Out_CrossTable_Value; -- 數值交叉表 (含 SumCol 行)
SELECT * FROM #Out_CrossTable_Percent; -- 百分比交叉表
ColName2 [0-10] [11-20] [21-30] SumCol
2023-01-01 5 12 8 25
2023-01-02 3 9 4 16
SumCol 8 21 12 41
indate [0-10] [0-10%] [11-20] [11-20%] [21-30] [21-30%]
2023-01-01 5 62.5% 12 57.1% 8 66.7%
2023-01-02 3 37.5% 9 42.9% 4 33.3%
| 技術 | 用途 |
|---|---|
| QUOTENAME | 動態列名自動加方括號,防注入、支援特殊字元/保留字。 |
| FOR XML PATH + STUFF | 將多行值串聯為逗號分隔字串 (PIVOT IN 列表、SUM 列表等)。 |
| PIVOT | 行轉列核心操作子:聚合函數 + FOR 列 + IN 列表。 |
| UNION ALL | 將加總行 (SumCol) 附加在數值表底部。 |
| CROSS JOIN 單列總和表 | 高效計算百分比,避免逐列子查詢。 |
| 交易包裹 | 確保中繼暫存表建立/刪除、結果表產出原子性。 |
@AggFunc 參數 (SUM/COUNT/AVG)。@ColName2 改為逗號分隔清單,GROUP BY 多欄位。FORMAT(val, 'P2') (SQL 2012+) 直接輸出 62.50%。(@ColName1, @ColName2);PIVOT 列數超過 100 時考慮分批或改用應用層轉換。