Vista del servidor SQL:cómo agregar filas faltantes usando interpolación
-
06-09-2019 - |
Pregunta
Encontrarse con un problema.
Tengo una tabla definida para contener los valores del tesoro diario. curva de rendimiento.
Es una tabla bastante simple que se utiliza para la búsqueda histórica de valores.
Se observan notablemente algunas lagunas en la tabla por año. 4
, 6
, 8
, 9
, 11-19
y 21-29
.
La fórmula es bastante simple para calcular el año. 4
es 0.5*Year3Value + 0.5*Year5Value
.
El problema es ¿cómo puedo escribir un VIEW
¿Que puede devolver los años perdidos?
Probablemente podría hacerlo en un procedimiento almacenado pero el resultado final debe ser una vista.
Solución
Tomando la suposición por Tom H. que lo que realmente quieres es una interpolación lineal y el hecho de que no solo faltan años, sino también meses, debes basar cada cálculo en MES, no en AÑO.
Para el código siguiente, supongo que tiene 2 tablas (una de las cuales se puede calcular como parte de la vista):
- Producir:contiene datos reales y almacenados PeriodoM en número de mes en lugar de nombre.si almacenas Nombre del período allí, sólo necesitarías unirte en la mesa:
- Período (se puede calcular en la vista como se muestra):almacena el nombre del período y el número de meses que representa
El siguiente código debe funcionar (solo necesita crear una vista basada en él):
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'
Otros consejos
Usted podría tratar de conseguir UNPIVOT los años y los valores de una lista.
A continuación, esta unión de los años faltantes seleccione YearNo , (Seleccione YearValue donde YearNo = YearNo-1) * 0,5 + (seleccione YearValue donde YearNo = YearNo + 1) * 0.5 AS YearValue de unpivotedlist donde YearNo en (nuestra lista de desaparecidos del año)
A continuación, girar de nuevo otra vez para obtener el formato que desee y el pop en una vista?
Voy a hacer la suposición de que desea que la curva se mueva suavemente entre dos años si hay un hueco, por lo que si más de un año no se encuentra no sólo quiere promediar los dos años próximos. Esto es lo que probablemente usaría:
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
Se puede reescribir esta usando un CTE en lugar de la tabla Números (sólo una tabla de números consecutivos). También es posible usar NO EXISTE o subconsultas con MIN y MAX en lugar de la IZQUIERDA combinaciones externas en YC_BOT2 y YC_TOP2 si se quería hacer eso. Algunas personas encuentran que este método confuso.
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
Desde hace años que faltan esta consulta toma el promedio de los años más cercanos se encuentran.