TSQL用窗口和CTE替换“奇怪的更新”计算

时间:2016-02-19 16:04:18

标签: sql-server replace common-table-expression windowing

我正在尝试使用窗口和CTE来使用“Quirky Update”来替代“运行性能”的复杂计算。运行性能的快速数学运算是((1 +运行)*(1 +每日)) - 1.需要为当前行更新此运行,然后在下一行计算中使用。当AssetID更改时,它重置为0。我发现的唯一替代方法是使用exp(sum(log))(在代码示例中显示),尽管数字在现实世界中是正确的,但它使用起来太慢,使用Quirky Update几乎是瞬间的。 在我的示例中,我有窗口替换每日计算,但由于运行需要更新当前行并将其反映在下一行计算中,我想不出使用窗口和CTE来处理运行计算的方法。所以我的尝试是更新语句中的变量类似于Quirky Update,它们是错误的。也许有一种类似于我每天计算的方式? 提前致谢, 罗布

if OBJECT_ID('tempdb..#DMVRunningPerformance') is not null
    drop table #DMVRunningPerformance

create table #DMVRunningPerformance (
    LongPosition_bt bit null,
    AssetID_in int null,
    Symbol_vc varchar(255),
    DayID_in int,
    Status_vc varchar(255) null,
    MV float,
    DailyPerf float null,
    RunningPerf float null)

    CREATE CLUSTERED INDEX [indexdown] ON #DMVRunningPerformance 
        (
            [LongPosition_bt] ASC,
            [AssetID_in] ASC,
            [DayID_in] DESC
        )
    WITH (SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]

insert into #DMVRunningPerformance  
 values (1,100,'IBM',75006,NULL,201048.8987,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,100,'IBM',75005,NULL,200841.5658,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,100,'IBM',75004,NULL,200321.7043,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,100,'IBM',75003,NULL,201120.0467,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,100,'IBM',75002,NULL,201779.8805,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,100,'IBM',75001,NULL,201651.3917,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,100,'IBM',75000,NULL,201101.0320,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,200,'MSFT',75006,NULL,805048.8987,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,200,'MSFT',75005,NULL,801841.5658,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,200,'MSFT',75004,NULL,804321.7043,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,200,'MSFT',75003,NULL,809120.0467,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,200,'MSFT',75002,NULL,801779.8805,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,200,'MSFT',75001,NULL,801151.3917,NULL,NULL)
insert into #DMVRunningPerformance  
 values (1,200,'MSFT',75000,NULL,801901.0320,NULL,NULL)

-------------------  BEGIN Quirky Update   -------------------------------------    
declare @RunningPerformance as float=0
declare @AssetID_in int=-1,
        @DayID_in int=0,
        @LongPosition_bt bit=0,
        @Status_vc varchar(255)=NULL,
        @NewGroup_bt bit=1,
        @DailyPerformance float,
        @NDMarketValue float,
        @PDMarketValue float=0,
        @NDSymbol_vc varchar(255)='XXXXXXXXXX',
        @PDSymbol_vc varchar(255)='XXXXXXXXXX',
        @NDDayID_in int,
        @PDDayID_in int,
        @NDAssetID_in int=-1,
        @PDAssetID_in int=-1

update #DMVRunningPerformance
    set 
        @LongPosition_bt=LongPosition_bt,
        @NewGroup_bt=CASE WHEN @AssetID_in<>AssetID_in or @LongPosition_bt<>LongPosition_bt THEN 1 ELSE 0 END,
        @DailyPerformance=CASE WHEN @NewGroup_bt=1 THEN 0 ELSE
                            (@NDMarketValue-MV)/MV END,
        @RunningPerformance=CASE WHEN @NewGroup_bt=1 THEN 0 ELSE
                            ((1 + @RunningPerformance) * (1 + @DailyPerformance)) - 1 END
        ,DailyPerf=@DailyPerformance
        ,RunningPerf=@RunningPerformance
        ,@AssetID_in=AssetID_in
        ,@NDAssetID_in=AssetID_in
        ,@NDSymbol_vc=Symbol_vc
        ,@NDDayID_in=DayID_in
        ,@NDMarketValue=MV
FROM #DMVRunningPerformance OPTION (MAXDOP 1)

select 'this data is correct'
select * from #DMVRunningPerformance
-------------------  END Quirky Update   -------------------------------------  

-- reset performance
update #DMVRunningPerformance set DailyPerf=null, RunningPerf=NULL

-------------------  BEGIN windowing and CTE   -------------------------------------    
-- update daily perf with CTE
;WITH dailyCTE as
    (
    select LongPosition_bt,AssetID_in,Symbol_vc,DayID_in,PreviousAssetID,
        MV,
        DailyPerformance = CASE WHEN YesterdayMV IS NULL THEN 0 
                            ELSE (YesterdayMV - MV) / MV END
    FROM
        (
            select p.LongPosition_bt,p.AssetID_in,p.Symbol_vc,p.DayID_in,
                p.MV,
                lag(p.MV,1) over (Partition by LongPosition_bt,AssetID_in
                                    order by DayID_in desc) as YesterdayMV,
                lag(p.AssetID_in,1) over (Partition by LongPosition_bt,AssetID_in
                                    order by DayID_in desc) as PreviousAssetID
                from #DMVRunningPerformance p
        ) p1
    )
    Update #DMVRunningPerformance 
        set DailyPerf=dailyCTE.DailyPerformance,
        @NewGroup_bt=CASE WHEN PreviousAssetID is null THEN 1 ELSE 0 END,
        @RunningPerformance=CASE WHEN PreviousAssetID is null THEN DailyPerformance ELSE
                            ((1 + @RunningPerformance) * (1 + DailyPerformance)) - 1 END,
        RunningPerf=@RunningPerformance
        from dailyCTE join #DMVRunningPerformance d
            on dailyCTE.LongPosition_bt=d.LongPosition_bt and dailyCTE.AssetID_in=d.AssetID_in and dailyCTE.DayID_in=d.DayID_in

select 'this data daily is correct, running is incorrect'

select * from #DMVRunningPerformance

-- alternate method for running perf
-- works but for realworld data takes orders of magnitude longer than quirky update
if 1=2
select 
    dr.AssetID_in,dr.LongPosition_bt,dr.DayID_in,dr.MV,dr.DailyPerf-1 as DailyPerformance,
    RunningPerformance_fl = exp((
                    select
                        sum(log(DailyPerf))
                    from
                        #DMVRunningPerformance
                    where
                        AssetID_in=dr.AssetID_in and LongPosition_bt=dr.LongPosition_bt and
                        DayID_in >= DR.DayID_in)) - 1
 from #DMVRunningPerformance dr
    order by
        dr.assetid_in,DR.DayID_in desc

-------------------  END windowing and CTE   -------------------------------------  



drop table #DMVRunningPerformance

1 个答案:

答案 0 :(得分:0)

使用递归CTE非常简单。下面的实现是一个快速和脏的版本,因此您可以删除一些额外/冗余列。 (DailyPerf2和RunningPerf2是新结果。)

;with orderedRows as
(
    select *, row_number() over (partition by AssetId_in order by DayId_in desc) as rn
    from #DMVRunningPerformance
), grouped as
(
    select *, cast(0.0 as float) as DailyPerf2, cast(0.0 as float) as RunningPerf2
    from orderedRows
    where rn = 1

    union all

    select r.*
        , cast((prevR.MV - r.MV) / r.MV as float)
        , cast(((1 + prevR.RunningPerf2) * (1 + (prevR.MV - r.MV) / r.MV)) - 1 as float)
    from orderedRows r
    inner join grouped prevR on r.AssetId_in = prevR.AssetID_in and r.rn = prevR.rn + 1
)
select * 
from grouped
order by AssetID_in, DayID_in desc