SQL Server:come aggiungere righe mancanti utilizzando l'interpolazione
-
06-09-2019 - |
Domanda
Esegue in un problema.
Ho una tabella definita per contenere i valori del quotidiano tesoro la curva dei rendimenti.
È piuttosto semplice tabella utilizzata per la storica ricerca di valori.
Ci sono notibly alcune lacune nella tabella anno 4
, 6
, 8
, 9
, 11-19
e 21-29
.
La formula è piuttosto semplice, in quanto, per calcolare anno 4
è 0.5*Year3Value + 0.5*Year5Value
.
Il problema è come faccio a scrivere un VIEW
che può tornare mancante anni?
Probabilmente potrei farlo in una stored procedure, ma il risultato finale deve essere una visione.
Soluzione
Assunzione l'assunzione da parte di Tom H. che cosa si vuole veramente è una interpolazione lineare e il fatto che non solo anni, ma anche mesi sono mancanti, è necessario di base di ogni calcolo di MESE, non all'ANNO.
Per il codice riportato di seguito si presuppone che 2 tabelle (una delle quali può essere calcolata come parte della vista):
- Resa:contiene i dati reali e memorizzati PeriodM in numero di mesi, quindi, più che il nome.Se si memorizzano PeriodName non c'è bisogno di unire sul tavolo:
- Periodo (può essere calcolata in vista, come mostrato in figura):negozi di periodo nome e il numero di mesi rappresenta
Seguente codice deve funzionare (basta creare una visualizzazione basata su di esso):
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'
Altri suggerimenti
Si potrebbe provare UNPIVOT di ottenere gli anni e valori in un elenco.
Poi unione questo per gli anni mancanti selezionare YearNo , (Seleziona yearValue dove YearNo = YearNo-1) * 0.5 + (selezionare yearValue dove YearNo = YearNo + 1) * 0.5 AS yearValue da unpivotedlist dove YearNo nella (nostra lista mancante anni)
Poi ruotare su nuovamente per ottenere il formato desiderato e pop in una vista?
Io vado a fare l'ipotesi che si desidera la curva per passare agevolmente tra due anni, se v'è una lacuna, quindi se più di un anno non è presente non si vuole semplicemente media i due più vicini anni. Ecco quello che probabilmente usare:
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
Si potrebbe riscrivere questo utilizzando una CTE invece della tabella di Numbers (solo una tabella di numeri consecutivi). Si potrebbe anche usare NON ESISTE o sottoquery con MIN MAX e al posto del ESTERNO SINISTRO unisce a YC_BOT2 e YC_TOP2 se si voleva farlo. Alcune persone trovano questo metodo di confusione.
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
Per anni mancanti questa query prende la media dei più vicini anni trovati.