2009-05-28 20 views
7

Występuje problem.Widok serwera SQL: sposób dodawania brakujących wierszy za pomocą interpolacji

Mam tabelę zdefiniowaną do przechowywania wartości skarbca dziennego yield curve.

To całkiem prosta tabela używana do historycznego wyszukiwania wartości.

Istnieje notibly pewne luki w tabeli na rok 4, 6, 8, 9, 11-19 i 21-29.

Formuła jest dość prosta, ponieważ do obliczenia roku 4 jest to 0.5*Year3Value + 0.5*Year5Value.

Problem polega na tym, jak mogę napisać VIEW, który może zwrócić brakujące lata?

Prawdopodobnie mógłbym zrobić to w procedurze przechowywanej, ale wynik końcowy musi być widokiem.

+0

Czego chcesz być zwrócony, gdy brakuje jednego roku? Prosta średnia z najbliższego roku? – ahains

+0

Świetne pytanie! Tytuł wprowadzający w błąd: zmień go na "Widok MSSQL: jak dodać brakujące wiersze za pomocą interpolacji" lub podobny. Dzięki. – van

+0

tytuł zmieniony, dobra sugestia –

Odpowiedz

6

Przyjmując założenie przez Tom H. że to, co naprawdę chcesz to liniowa interpolacja i fakt, że nie tylko lata, ale także miesiąc brakuje, trzeba oprzeć obliczenia na każdy miesiąc, a nie rok.

Dla kodu poniżej Zakładam, że masz 2 tabele (z których jeden może być obliczana jako część zdania):

  • Wydajność: zawiera prawdziwych danych i przechowywane PeriodM w szereg z eksploatacji miesiąc zamiast imienia.Jeśli przechowujesz PeriodName tam, to po prostu trzeba dołączyć na stole:
  • Okres (może być obliczany w widoku jak pokazano): Zapisuje okres nazwa i numer miesiącach reprezentuje

Poniższy kod musi działać (wystarczy utworzyć na nim widok):

WITH "Period" (PeriodM, PeriodName) AS (
    -- // I would store it as another table basically, but having it as part of the view would do 
       SELECT 01, '1 mo' 
    UNION ALL SELECT 02, '2 mo' -- // data not stored 
    UNION ALL SELECT 03, '3 mo' 
    UNION ALL SELECT 06, '6 mo' 
    UNION ALL SELECT 12, '1 yr' 
    UNION ALL SELECT 24, '2 yr' 
    UNION ALL SELECT 36, '3 yr' 
    UNION ALL SELECT 48, '4 yr' -- // data not stored 
    UNION ALL SELECT 60, '5 yr' 
    UNION ALL SELECT 72, '6 yr' -- // data not stored 
    UNION ALL SELECT 84, '7 yr' 
    UNION ALL SELECT 96, '8 yr' -- // data not stored 
    UNION ALL SELECT 108, '9 yr' -- // data not stored 
    UNION ALL SELECT 120, '10 yr' 
    -- ... // add more 
    UNION ALL SELECT 240, '20 yr' 
    -- ... // add more 
    UNION ALL SELECT 360, '30 yr' 
) 
, "Yield" (ID, PeriodM, Date, Value) AS (
    -- // ** This is the TABLE your data is stored in ** 
    -- // 
    -- // value of ID column is not important, but it must be unique (you may have your PK) 
    -- // ... it is used for a Tie-Breaker type of JOIN in the view 
    -- // 
    -- // This is just a test data: 
       SELECT 101, 01 /* '1 mo'*/, '2009-05-01', 0.06 
    UNION ALL SELECT 102, 03 /* '3 mo'*/, '2009-05-01', 0.16 
    UNION ALL SELECT 103, 06 /* '6 mo'*/, '2009-05-01', 0.31 
    UNION ALL SELECT 104, 12 /* '1 yr'*/, '2009-05-01', 0.49 
    UNION ALL SELECT 105, 24 /* '2 yr'*/, '2009-05-01', 0.92 
    UNION ALL SELECT 346, 36 /* '3 yr'*/, '2009-05-01', 1.39 
    UNION ALL SELECT 237, 60 /* '5 yr'*/, '2009-05-01', 2.03 
    UNION ALL SELECT 238, 84 /* '7 yr'*/, '2009-05-01', 2.72 
    UNION ALL SELECT 239,120 /*'10 yr'*/, '2009-05-01', 3.21 
    UNION ALL SELECT 240,240 /*'20 yr'*/, '2009-05-01', 4.14 
    UNION ALL SELECT 250,360 /*'30 yr'*/, '2009-05-01', 4.09 
) 
, "ReportingDate" ("Date") AS (
    -- // this should be a part of the view (or a separate table) 
    SELECT DISTINCT Date FROM "Yield" 
) 

-- // This is the Final VIEW that you want given the data structure as above 
SELECT  d.Date, p.PeriodName, --//p.PeriodM, 
      CAST(
       COALESCE(y_curr.Value, 
        ( (p.PeriodM - y_prev.PeriodM) * y_prev.Value 
        + (y_next.PeriodM - p.PeriodM) * y_next.Value 
        )/(y_next.PeriodM - y_prev.PeriodM) 
       ) AS DECIMAL(9,4) -- // TODO: cast to your type if not FLOAT 
      ) AS Value 
FROM  "Period" p 
CROSS JOIN "ReportingDate" d 
LEFT JOIN "Yield" y_curr 
     ON y_curr.Date = d.Date 
     AND y_curr.PeriodM = p.PeriodM 
LEFT JOIN "Yield" y_prev 
     ON y_prev.ID = (SELECT TOP 1 y.ID FROM Yield y WHERE y.Date = d.Date AND y.PeriodM <= p.PeriodM ORDER BY y.PeriodM DESC) 
LEFT JOIN "Yield" y_next 
     ON y_next.ID = (SELECT TOP 1 y.ID FROM Yield y WHERE y.Date = d.Date AND y.PeriodM >= p.PeriodM ORDER BY y.PeriodM ASC) 

--//WHERE  d.Date = '2009-05-01' 
+1

+1 Myślę, że to zadziała dla mnie, wielkie dzięki! –

+0

Więc patrzyłem na zestaw wyników i wyglądało bardziej na sinusoidę niż na krzywą ... Patrząc na oryginalną formułę dał mi 0.5 nie wydaje się działać dobrze na więcej niż 1yr luki, prawie jak na 11-19 Powinienem obliczyć 15 za pomocą 0,5, a następnie użyć serii do obliczenia powyżej i poniżej, aby 11 rok wynosił 0,8 * [10yr] + 0,2 * (0,5 * [10yr] + 0,5 * [20yr]) -() Rok 15 i tak dalej, z .6 i .4 w roku 12, itp. Going to wypróbować –

+0

@ Christopher Klein: 1) Jaka jest oryginalna formuła? 2) jeśli zrobisz najpierw 15 ', a następnie zrobisz 11' jak opisałeś używając 10 i 15 ', to skończysz z tym samym numerem, jak gdybyś użył 10 i 20 tak czy inaczej. – van

0
WITh cal(year) AS 
     (
     SELECT 1 AS current_year 
     UNION ALL 
     SELECT year + 1 
     FROM cal 
     WHERE year < 100 
     ) 
SELECT CASE WHEN yield_year IS NULL THEN 
      0.5 * 
      (
      SELECT TOP 1 yield_value 
      FROM yield 
      WHERE yield_year < year 
      ORDER BY 
        yield_year DESC 
      ) + 
      0.5 * 
      (
      SELECT TOP 1 yield_value 
      FROM yield 
      WHERE yield_year > year 
      ORDER BY 
        yield_year ASC 
      ) 
     ELSE 
      yield_value 
     END 
FROM  cal 
LEFT JOIN 
     yield 
ON  yield_year = year 

W przypadku brakujących lat to zapytanie pobiera średnią z najbliższych lat.

+0

Wierzę, że CT 'cal' złamie się z powodu limitu rekursji. – van

+0

@van: na pewno, zapomniałem o ograniczniku. Poprawione. – Quassnoi

1

Możesz spróbować rozpiąć, aby uzyskać wartości & na liście.

Następnie związek ten z brakującymi lat wybrać YearNo (wybrać YearValue gdzie YearNo = YearNo-1) * 0,5 + (wybrać YearValue gdzie YearNo = YearNo + 1) * 0,5 jako YearValue z unpivotedlist gdzie YearNo w (nasza brakująca lista lat)

Następnie obróć ją ponownie, aby uzyskać żądany format i wyświetlić go w widoku?

1

Spróbuję zgadnąć, że chcesz, aby krzywa płynnie przechodziła między dwoma latami, jeśli istnieje luka, więc jeśli brakuje więcej niż jednego roku, nie chcesz po prostu przecenić dwóch najbliższych lat. Oto, co byłoby pewnie użyć:

SELECT 
    NUM.number AS year, 
    COALESCE(YC.val, YC_BOT.val + ((NUM.number - YC_BOT.yr) * ((YC_TOP.val - YC_BOT.val)/(YC_TOP.yr - YC_BOT.yr)))) 
FROM 
    dbo.Numbers NUM 
LEFT OUTER JOIN dbo.Yield_Curve YC ON 
    YC.yr = NUM.number 
LEFT OUTER JOIN dbo.Yield_Curve YC_TOP ON 
    YC.yr IS NULL AND  -- Only join if we couldn't find a current year value 
    YC_TOP.yr > NUM.number 
LEFT OUTER JOIN dbo.Yield_Curve YC_TOP2 ON 
    YC_TOP2.yr > NUM.number AND 
    YC_TOP2.yr < YC_TOP.yr 
LEFT OUTER JOIN dbo.Yield_Curve YC_BOT ON 
    YC.yr IS NULL AND  -- Only join if we couldn't find a current year value 
    YC_BOT.yr < NUM.number 
LEFT OUTER JOIN dbo.Yield_Curve YC_BOT2 ON 
    YC_BOT2.yr < NUM.number AND 
    YC_BOT2.yr > YC_BOT.yr 
WHERE 
    YC_TOP2.yr IS NULL AND 
    YC_BOT2.yr IS NULL AND 
    NUM.number BETWEEN @low_yr AND @high_yr 

Można przepisać to za pomocą CTE zamiast tablicy liczb (tylko tabelę z kolejnymi numerami). Możesz również użyć NOT EXISTS lub podkwerendy z MIN i MAX zamiast LEWEGO ZEWNĘTRZNEGO POŁĄCZENIA na YC_BOT2 i YC_TOP2, jeśli chcesz to zrobić. Niektórzy ludzie uważają tę metodę za mylącą.

Powiązane problemy