2009-05-28 8 views
7

Problema.Vista del servidor SQL: cómo agregar filas faltantes mediante la interpolación

Tengo una tabla definida para contener los valores de la tesorería diaria yield curve.

Es una tabla bastante simple utilizada para la búsqueda histórica de valores.

Hay notibly algunas lagunas en la tabla en el año 4, 6, 8, 9, 11-19 y 21-29.

La fórmula es bastante simple en el sentido de que 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.

+0

¿Qué desea que le devuelvan cuando falta un año? ¿Un promedio simple del año más cercano? – ahains

+0

¡Gran pregunta! Sin embargo, el título es engañoso: cámbielo a algo así como "Vista de MSSQL: cómo agregar filas faltantes mediante interpolación" o similar. Gracias. – van

+0

título cambiado, buena sugerencia –

Respuesta

6

Tomando la suposición por Tom H. que lo que realmente quiere es una interpolación lineal y el hecho de que no solo faltan años, sino también meses, necesita basar cada cálculo en MES, no en AÑO.

Para el código de abajo que se supone que tiene 2 mesas (uno de los cuales se pueden calcular como parte de la vista):

  • Rendimiento: contiene los datos reales y se almacena PeriodM en número-de- mes en lugar de nombre.Si almacena PeriodName ahí, sólo tendría que unirse en la mesa:
  • Período (puede ser calculada en la vista como se muestra): tiendas de renombre 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' 
+1

+1 Creo que esto realmente funcionará para mí, muchas gracias! –

+0

Estaba mirando el conjunto de resultados y parecía más una onda sinusoidal que una curva ... Mirando la fórmula original que la gente me dio, el 0.5 no parece funcionar bien durante más de un espacio de 1 año, casi como para 11-19 Debería calcular 15 con 0.5 y luego usar una serie para calcificar arriba y abajo de modo que el año 11 sea 0.8 * [10yr] + 0.2 * (0.5 * [10yr] + 0.5 * [20yr]) -() es Año 15 y así sucesivamente, con .6 y .4 para el año 12, etc. Vamos a probarlo –

+0

@Christopher Klein: 1) ¿Cuál es la fórmula original? 2) si hace 15 'primero, y luego hace 11' como describió usando 10 y 15 ', entonces terminará con el mismo número como si hubiera usado 10 y 20 de todos modos. – 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 

Para los años que faltan, esta consulta toma el promedio de años más cercanos encontrados.

+0

Creo que el CTE 'cal' se romperá debido al límite de recursión. – van

+0

@van: seguro, olvidé el limitador. Corregido – Quassnoi

1

Puede intentar unpivot para obtener los valores de los años & en una lista.

entonces Unión esto a los años que faltan seleccionar YearNo , (seleccione YearValue donde YearNo = YearNo-1) * 0,5 + (seleccione YearValue donde YearNo = YearNo + 1) * 0,5 como YearValue de unpivotedlist donde YearNo en (nuestra lista de años perdidos)

¿Luego vuelve a pivotar para obtener el formato que necesita y abrirlo en una vista?

1

Voy a hacer la suposición de que desea que la curva se mueva sin problemas entre dos años si hay un espacio, por lo que si falta más de un año no desea promediar los dos años más cercanos. 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 podría usar NOT EXISTS o subqueries con MIN y MAX en lugar de LEFT OUTER JOINs en YC_BOT2 y YC_TOP2 si quisiera hacer eso. Algunas personas encuentran este método confuso.

Cuestiones relacionadas