Date
ディメンションとTime
ディメンションが他のいくつかのディメンションと一緒にあるシナリオがあります。分または第2レベルでデータを保存する必要がなかったため、時間ディメンションにはHours
のみが含まれます。
状況によっては、特定の時間帯の値のAverage
のMax
を計算する必要があります。たとえば、01:00から04:00までの値のMax
と、指定された期間(この例では2015年1月1日から2015年1月3日)のこれらの最大値の平均が必要です。
Date
を使用する代わりにTime
およびSubselect
ディメンションを(他のディメンションとともに)From
Where
句としてフィルタリングしています。
Time
ディメンションのメンバーがDate
ディメンションの選択された値ごとに同じままである場合、Max
、Avg
、およびStDev
は正しい結果を示します、ただし、Date
ディメンションメンバーの1つから数時間を除外する必要がある場合、Max
はエラーになり、Avg
とStDev
は誤った値を示します。
この質問で使用する例を作成するには、以下のSQLコードを参照してください。
_create database TestDateTimeDimensions
GO
USE [TestDateTimeDimensions]
GO
create table DimDate (
DateId int not null PRIMARY KEY,
Date [Date] not null
)
insert into DimDate
select 20150101, CONVERT(DATE, '2015-01-01')
UNION ALL
select 20150102, CONVERT(DATE, '2015-01-02')
UNION ALL
select 20150103, CONVERT(DATE, '2015-01-03')
UNION ALL
select 20150104, CONVERT(DATE, '2015-01-04')
create table DimTime(
TimeId int not null PRIMARY KEY,
[Time] Time not null
)
insert into DimTime
select 0, CONVERT(TIME, '00:00:00')
UNION ALL
select 1, CONVERT(TIME, '01:00:00')
UNION ALL
select 2, CONVERT(TIME, '02:00:00')
UNION ALL
select 3, CONVERT(TIME, '03:00:00')
UNION ALL
select 4, CONVERT(TIME, '04:00:00')
UNION ALL
select 5, CONVERT(TIME, '05:00:00')
UNION ALL
select 6, CONVERT(TIME, '06:00:00')
UNION ALL
select 7, CONVERT(TIME, '07:00:00')
UNION ALL
select 8, CONVERT(TIME, '08:00:00')
UNION ALL
select 9, CONVERT(TIME, '09:00:00')
UNION ALL
select 10, CONVERT(TIME, '10:00:00')
UNION ALL
select 11, CONVERT(TIME, '11:00:00')
UNION ALL
select 12, CONVERT(TIME, '12:00:00')
UNION ALL
select 13, CONVERT(TIME, '13:00:00')
UNION ALL
select 14, CONVERT(TIME, '14:00:00')
UNION ALL
select 15, CONVERT(TIME, '15:00:00')
UNION ALL
select 16, CONVERT(TIME, '16:00:00')
UNION ALL
select 17, CONVERT(TIME, '17:00:00')
UNION ALL
select 18, CONVERT(TIME, '18:00:00')
UNION ALL
select 19, CONVERT(TIME, '19:00:00')
UNION ALL
select 20, CONVERT(TIME, '20:00:00')
UNION ALL
select 21, CONVERT(TIME, '21:00:00')
UNION ALL
select 22, CONVERT(TIME, '22:00:00')
UNION ALL
select 23, CONVERT(TIME, '23:00:00')
CREATE TABLE Fact (
Id int identity not null PRIMARY KEY,
DateId int not null REFERENCES DimDate(DateId),
TimeId int not null REFERENCES DimTime(TimeId),
value int not null
)
insert into Fact
select DateId, TimeId, ROUND(Rand(CONVERT(varbinary, NEWID())) * 100, 2)
FROM DimDate, DimTime
_
正しく機能するMDX
クエリは次のとおりです。
_WITH
MEMBER MaxMember as MAX(TimeSet, [Measures].[Value])
MEMBER AvgOfMax as Avg(DateSet, MAX(TimeSet, [Measures].[Value]))
MEMBER StDevOfMax as StDev(DateSet, MAX(TimeSet, [Measures].[Value]))
SET DateSet as EXISTING [Dim Date].[Date Id].[Date Id]
SET TimeSet as EXISTING [Dim Time].[Time Id].[Time Id]
select
{
[Measures].[Value], MaxMember, AvgOfMax, StDevOfMax
}
on 0,
{
DateSet * TimeSet
} on 1
FROM
(
SELECT ({[Dim Date].[Date Id].&[20150101] : [Dim Date].[Date Id].&[20150103]}, {[Dim Time].[Time Id].&[1] : [Dim Time].[Time Id].&[4]})
on 0
FROM [Test Date Time Dimensions]
)
_
結果は次のように正しく表示されます。
軸1に含まれるアイテムは使用されず、実際のクエリは1行のみを返すことに注意してください。これらはこの例のためだけのものです。
期待どおりに機能しないクエリは次のとおりです。
_WITH
MEMBER MaxMember as MAX(TimeSet, [Measures].[Value])
MEMBER AvgOfMax as Avg(DateSet, MAX(TimeSet, [Measures].[Value]))
MEMBER StDevOfMax as StDev(DateSet, MAX(TimeSet, [Measures].[Value]))
SET DateSet as EXISTING [Dim Date].[Date Id].[Date Id]
SET TimeSet as EXISTING [Dim Time].[Time Id].[Time Id]
select
{
[Measures].[Value], MaxMember, AvgOfMax, StDevOfMax
}
on 0
,
{
DateSet * TimeSet
} on 1
FROM
(
SELECT {({[Dim Date].[Date Id].&[20150101] : [Dim Date].[Date Id].&[20150102]} * {[Dim Time].[Time Id].&[1] : [Dim Time].[Time Id].&[4]})
, ([Dim Date].[Date Id].&[20150103] * [Dim Time].[Time Id].&[3] : [Dim Time].[Time Id].&[4])}
on 0
FROM [Test Date Time Dimensions]
)
_
ここでは、時間_01:00
_と'02:00 _from Date
_ 3 2015年1月. The maximum value (70) is at hour
01:00 of
3 2015年1月_but is still being used to calculate Avg and
_ StDev _, and
_ Max`列でエラーが発生していることを除外しました、画像に示されているように:
ここでは、平均偏差と標準偏差の両方が正しくありません。
この2番目のケースで結果を正しく計算する方法を見つけるのを手伝ってください。また、Maxがエラーを出し、Avg
関数とStDev
関数がまだ計算している理由を説明してください。フィルタリングされたSubselect
句。
私が思いついた理想的とは言えない回避策は、DateTime
とDate
の両方を持つ新しいTime
ディメンションを追加し、このDateTime
ディメンションを使用することです。以下のコードのように、Subselect
句で、既存のTime
ディメンションを使用してMax
およびDate
ディメンションを計算し、平均および標準偏差を取得します。
DateTime
ディメンションを作成して入力します。
_create table DimDateTime
(
DateTimeId int not null PRIMARY KEY,
[Date] Date not null,
[Time] Time not null
)
insert into DimDateTime
select ((DateId * 100) + TimeId) DateTimeId, DimDate.Date, DimTime.Time
from DimDate, DimTime
alter table Fact add DateTimeId int References DimDateTime(DateTimeId)
update Fact set DateTimeId = ((DateId * 100) + TimeId)
_
正しい結果を返すMDX
クエリ:
_WITH
MEMBER MaxMember as MAX(TimeSet, [Measures].[Value])
MEMBER AvgOfMax as Avg(DateSet, MAX(TimeSet, [Measures].[Value]))
MEMBER StDevOfMax as StDev(DateSet, MAX(TimeSet, [Measures].[Value]))
SET DateSet as NONEMPTY ([Dim Date].[Date Id].[Date Id])
SET TimeSet as NONEMPTY ([Dim Time].[Time Id].[Time Id])
select
{
[Measures].[Value], MaxMember, AvgOfMax, StDevOfMax
}
on 0,
{
NONEMPTY(DateSet * TimeSet)
} on 1
FROM
(
SELECT ({[Dim Date Time].[Date Time Id].&[2015010101] : [Dim Date Time].[Date Time Id].&[2015010104],
[Dim Date Time].[Date Time Id].&[2015010201] : [Dim Date Time].[Date Time Id].&[2015010204],
[Dim Date Time].[Date Time Id].&[2015010303] : [Dim Date Time].[Date Time Id].&[2015010304]})
on 0
FROM [Test Date Time Dimensions]
)
_
ただし、同じデータを含む既存の2つのディメンションに加えて、新しいディメンションを作成することは避けたいと思います。次に、データをアプリケーションに取り込み、APIメソッドで結果を計算することを避けたいと思います。したがって、Max
、Avg
、およびStDev
関数が正しく機能するようにすることをお勧めします。
SQL Server2012の使用。
MDXで試してみましたが、役に立たなかったため、SSASユーザー定義関数を使用して最大の平均を計算する必要がありました。全体でAvg
(DateSet
)を計算するためのセット、(Max
)全体でTimeSet
を計算するためのセット、および除外されるレコードを含むための3番目のセットMax
操作は、パラメーターとしてUDFに渡されます。
基本的なコードは次のようなものです(エラーチェックを除く、コード化されたAvg of Max機能を表示します)。
public static class AvgOfMaxMethods
{
public static double AvgOfMax(Set maxAcrossSet, Set avgAcrossSet, Set setToExclude, Expression measureExpression)
{
var setToExcludeQueryable = setToExclude.Tuples.OfType<Microsoft.AnalysisServices.AdomdServer.Tuple>();
IList<double> maxMembers = new List<double>();
foreach(var avgAcrossTuple in avgAcrossSet.Tuples)
{
var max = double.MinValue;
foreach(var maxAcrossTuple in maxAcrossSet.Tuples)
{
if (!setToExcludeQueryable.Any(Tuple => Tuple.Members[0].UniqueName.Equals(avgAcrossTuple.Members[0].UniqueName) && Tuple.Members[1].UniqueName.Equals(maxAcrossTuple.Members[0].UniqueName)))
{
TupleBuilder tb = new TupleBuilder(avgAcrossTuple.Members[0]);
tb.Add(maxAcrossTuple.Members[0]);
var calculatedVal = measureExpression.Calculate(tb.ToTuple()).ToDouble();
max = calculatedVal > max ? calculatedVal : max;
}
}
if (!max.Equals(double.MinValue))
{
maxMembers.Add(max);
}
}
return maxMembers.Average();
}
}
これにより、クエリは次のようになります(比較のために元のMax
とStDev
を保持します)。
WITH
MEMBER MaxMember as MAX(TimeSet, [Measures].[Value])
MEMBER AvgOfMax as Avg(DateSet, MAX(TimeSet, [Measures].[Value]))
MEMBER StDevOfMax as StDev(DateSet, MAX(TimeSet, [Measures].[Value]))
Member MaxFromUDF as UDFPoc.UDFPoc.AvgOfMaxMethods.AvgOfMax(TimeSet, DateSet, SetToExclude, [Measures].[Value])
SET DateSet as EXISTING [Dim Date].[Date Id].[Date Id]
SET TimeSet as EXISTING [Dim Time].[Time Id].[Time Id]
SET SetToExclude as {[Dim Date].[Date Id].&[20150103] * [Dim Time].[Time Id].&[1] : [Dim Time].[Time Id].&[2]}
select
{
[Measures].[Value], MaxMember, AvgOfMax, StDevOfMax, MaxFromUDF
}
on 0,
{
DateSet * TimeSet
} on 1
FROM
(
SELECT ({[Dim Date].[Date Id].&[20150101] : [Dim Date].[Date Id].&[20150103]}, {[Dim Time].[Time Id].&[1] : [Dim Time].[Time Id].&[4]})
on 0
FROM [Test Date Time Dimensions]
)
結果は次のようになります(ここでは、除外されたセットのタプルが赤で強調表示されています)。
ご覧のとおり、UDFは、SettoExclude
およびMax
を計算するときに、Average
セット内の各タプルのメンバーを除外します。画像に見られるように、除外されたセットを考慮した正しい平均が計算されています。同様に、StDev
はこの方法で計算できます。
SSASユーザー定義関数を作成するためのガイドとして this を参照してください。