在複雜 OLAP 分析中,常需計算「環比」(本期與上期差值佔上期比例)。MDX 只需 `[維度].CurrentMember.PrevMember` 即可取得上一維度成員值,但 T-SQL 需自行 `JOIN` 歷史記錄,效能與撰寫皆較繁瑣。本文示範以 **CLR 純量函數** 實作緩存機制,模擬 MDX `PrevMember` 行為,大幅簡化查詢並提升效能。
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;
public partial class UserDefinedFunctions
{
private static Dictionary _listValue = new Dictionary();
private static Dictionary _listGroup = new Dictionary();
[SqlFunction]
public static SqlString GetPrevMemberValue(SqlString key, SqlString currentGroup, SqlString currentValue)
{
if (key.IsNull || currentGroup.IsNull) return SqlString.Null;
try
{
SqlString prev = _listValue[key.Value];
if (_listGroup[key.Value] != currentGroup.Value) // 群組變更
{
prev = SqlString.Null;
_listGroup[key.Value] = currentGroup.Value;
}
_listValue[key.Value] = currentValue; // 更新當前值供下一列使用
return prev;
}
catch { return SqlString.Null; }
}
[SqlFunction]
public static SqlBoolean InitKey(SqlString key)
{
try { _listValue.Add(key.Value, SqlString.Null); _listGroup.Add(key.Value, string.Empty); return true; }
catch { return false; }
}
[SqlFunction]
public static SqlBoolean DisposeKey(SqlString key)
{
try { _listValue.Remove(key.Value); _listGroup.Remove(key.Value); return true; }
catch { return false; }
}
}
CREATE ASSEMBLY TestForSQLCLR FROM 'E:/sqlclrdata/TestFun.dll' WITH PERMISSION_SET = UNSAFE;
GO
CREATE FUNCTION dbo.xfn_GetPrevMemberValue (@key NVARCHAR(255), @initByDim NVARCHAR(255), @currentValue NVARCHAR(255))
RETURNS NVARCHAR(255) AS EXTERNAL NAME TestForSQLCLR.[UserDefinedFunctions].GetPrevMemberValue;
GO
CREATE FUNCTION dbo.xfn_initKey (@key NVARCHAR(255)) RETURNS BIT AS EXTERNAL NAME TestForSQLCLR.[UserDefinedFunctions].InitKey;
GO
CREATE FUNCTION dbo.xfn_disposeKey (@key NVARCHAR(255)) RETURNS BIT AS EXTERNAL NAME TestForSQLCLR.[UserDefinedFunctions].DisposeKey;
GO
-- 測試資料
DECLARE @t TABLE (區域 VARCHAR(4), TradeMonth VARCHAR(7), TradePrice FLOAT);
INSERT INTO @t VALUES
('閔行','2007-03',8796.67),('閔行','2007-04',9267.19),('閔行','2007-05',9335.26),
('浦東','2007-01',8976.73),('浦東','2007-02',12568.05),('浦東','2007-03',8023.98),('浦東','2007-04',18337.23),
('普陀','2007-01',11549.02),('普陀','2007-02',13496.24),('普陀','2007-03',7834);
-- 執行查詢
DECLARE @key VARCHAR(40) = NEWID(), @b BIT;
SELECT @b = dbo.xfn_initKey(@key);
SELECT 區域, TradeMonth, TradePrice, LastMonthPrice,
CAST(ROUND((TradePrice - LastMonthPrice) * 100 / LastMonthPrice, 2) AS VARCHAR(10)) + '%' AS 環比
FROM (
SELECT *, CAST(dbo.xfn_GetPrevMemberValue(@key, 區域, CAST(TradePrice AS NVARCHAR(255))) AS FLOAT) AS LastMonthPrice
FROM @t
) t;
SELECT @b = dbo.xfn_disposeKey(@key);
| 區域 | TradeMonth | TradePrice | LastMonthPrice | 環比 |
|---|---|---|---|---|
| 閔行 | 2007-03 | 8796.67 | NULL | NULL |
| 閔行 | 2007-04 | 9267.19 | 8796.67 | 5.35% |
| 閔行 | 2007-05 | 9335.26 | 9267.19 | 0.73% |
| 浦東 | 2007-01 | 8976.73 | NULL | NULL |
| 浦東 | 2007-02 | 12568.05 | 8976.73 | 40.01% |
| 浦東 | 2007-03 | 8023.98 | 12568.05 | -36.16% |
| 浦東 | 2007-04 | 18337.23 | 8023.98 | 128.53% |
| 普陀 | 2007-01 | 11549.02 | NULL | NULL |
| 普陀 | 2007-02 | 13496.24 | 11549.02 | 16.86% |
| 普陀 | 2007-03 | 7834 | 13496.24 | -41.95% |