SQL Server-Ansicht: wie fehlende Zeilen hinzufügen Interpolation
-
06-09-2019 - |
Frage
Beim Laufen in ein Problem.
Ich habe eine Tabelle definiert die Werte des Tageskasse zu halten Zinskurve .
Es ist eine ziemlich einfache Tabelle für historische Nachschlagen von Werten verwendet wird.
Es gibt notibly einige Lücken in der Tabelle auf Jahr 4
, 6
, 8
, 9
, 11-19
und 21-29
.
Die Formel ist recht einfach in diesem Jahr zu berechnen 4
es 0.5*Year3Value + 0.5*Year5Value
ist.
Das Problem ist, wie kann ich eine VIEW
schreiben, die die fehlenden Jahre zurückkehren können?
Ich kann es wahrscheinlich in einer gespeicherten Prozedur, aber das Endergebnis muss eine Ansicht sein.
Lösung
Unter der Annahme von Tom H. das, was Sie wirklich wollen, ist eine lineare Interpolation und die Tatsache, dass nicht nur Jahre, sondern auch Monate fehlen, müssen Sie jede Berechnung auf MONAT stützen, nicht JAHR.
Für den Code unten Ich gehe davon aus, dass Sie haben zwei Tabellen (von denen eine als Teil der Ansicht berechnet werden kann):
- Ausbeute: enthält reale Daten und gespeichert PeriodM in Nummer-of-Monat eher dann Namen. Wenn Sie speichern PeriodName gibt, würden Sie einfach auf den Tisch kommen müssen:
- Periode (kann wie gezeigt in der Ansicht berechnet werden) : speichert Zeitraum Name und Anzahl der Monate es stellt
Im Anschluss an Code arbeiten muß (Sie müssen nur eine Sicht erstellen, basierend darauf):
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'
Andere Tipps
Sie könnten versuchen, Entpivotisierung die Jahre & Wert in einer Liste zu erhalten.
Dann ist diese Vereinigung auf die fehlenden Jahre wählen YearNo , (Select YearValue wo YearNo = YearNo-1) * 0,5 + (select YearValue wo YearNo = YearNo + 1) * 0,5 AS YearValue von unpivotedlist wo YearNo in (unserer fehlenden Liste Jahre)
Sie es dann wieder zurückschwenken das Format benötigen Sie zu erhalten und es in einer Ansicht Pop?
Ich werde die Vermutung zu, dass Sie die Kurve wollen zwischen zwei Jahren reibungslos bewegen, wenn es eine Lücke, so dass, wenn mehr als ein Jahr, das Sie fehlen nicht nur die beiden nächsten Jahre im Durchschnitt wollen. Hier ist, was ich würde wahrscheinlich verwenden:
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
Sie können dies umschreiben einen CTE anstelle der Zahlen Tabelle (nur eine Tabelle von aufeinanderfolgenden Zahlen). Sie könnten auch NICHT VORHANDEN oder Subqueries mit MIN und MAX statt der LEFT OUTER JOIN auf YC_BOT2 und YC_TOP2 verwenden, wenn Sie das tun wollte. Einige Leute finden diese Methode verwirrend.
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
Für fehlende Jahre diese Abfrage nimmt den Durchschnitt der nächsten Jahre gefunden.