【问题标题】:SQL Server view: how to add missing rows using interpolationSQL Server 视图:如何使用插值添加缺失的行
【发布时间】:2010-10-29 13:52:45
【问题描述】:

遇到问题。

我定义了一个表来保存每日资金yield curve 的值。

这是一个用于历史查找值的非常简单的表。

表中的年份468911-1921-29 明显存在一些差距。

公式很简单,计算年份40.5*Year3Value + 0.5*Year5Value

问题是我如何写一个VIEW可以返回丢失的年份?

我可能可以在存储过程中执行此操作,但最终结果需要是一个视图。

【问题讨论】:

  • 缺一年的时候你想返回什么?最近年份的简单平均值?
  • 好问题!虽然标题具有误导性:请将其更改为“MSSQL 视图:如何使用插值添加缺失的行”或类似内容。谢谢。
  • 标题改了,好建议

标签: sql sql-server sql-view linear-interpolation


【解决方案1】:

Tom H. 假设您真正想要的是线性插值,而且不仅缺少年,而且缺少几个月,您需要将每个计算都基于 MONTH,而不是年。

对于下面的代码,我假设您有 2 个表(其中一个可以作为视图的一部分计算):

  • 产量:包含真实数据并以月数而不是名称存储PeriodM。如果您将 PeriodName 存储在那里,您只需要加入表格即可:
  • 期间(可以在如图所示的视图中计算):存储期间名称和它所代表的月数

以下代码必须有效(您只需要基于它创建一个视图):

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'

【讨论】:

  • 所以我正在查看结果集,它看起来更像是一个正弦波而不是曲线......看看人们给我的原始公式,0.5 似乎不太适用1 年的差距,几乎就像 11-19 一样,我应该用 0.5 计算 15,然后使用一个系列来计算上下,所以第 11 年是 0.8*[10yr]+0.2*(0.5*[10yr]+0.5*[20yr ])--() 是第 15 年,依此类推,第 12 年为 0.6 和 .4,依此类推。打算试试看
  • @Christopher Klein:1)原始公式是什么? 2)如果你先做 15',然后按照你描述的使用 10 和 15' 做 11',那么你最终会得到与使用 10 和 20 相同的数字。
  • 15 年; 0.5*[10yr]+0.5*[20yr] Year11; 0.8*[10yr]+0.2*[15yr] Year12; 0.6*[10yr]+0.4*[15yr] Year13; 0.4*[10yr]+0.6*[15yr] Year14; 0.2*[10yr]+0.8*[15yr] Year16; 0.8*[15yr]+0.2*[20yr] 等等 代码在 Year11 的运行方式是看到下一个“周期”是第 20 年,所以 10 和 11 之间的值跳跃从 3.21 到 4.04,然后回到 3.95第 12 年,应该是 3.210、3.303、3.396、3.489 和 3.675(Year15)。有人挖出了他们几年前使用的 Excel 电子表格,结果就是这样。
  • @Christopher Klein:克里斯。您所描述的仍然是“线性插值”并且基于相同的“线性”权重,这将产生相同的拟合线。因此在这两种情况下 [11y] 将是相同的。让我们看看旧的计算:[11y-old] = 9/10*[10y] + 1/10[20y]。现在让我们看看 [11y-new] = 4/5*[10y] + 1/5*[15y],然后用你的公式替换 [15y],我们得到:[11y-new] = 4/5*[10y ] + 1/5*(5/10*[10y] + 5/10*[20y]) = 4/5*[10y] + 1/10*[10y] + 1/10*[20y] = 9/ 10*[10 年] + 1/10[20 年]。正如你所看到的,我们得到了相同的值 :-) 其他数据点也是一样的。
  • @Christopher Klein:...粗略的近似值对您的客户来说还不够好,那么“正确”的做法是使用样条曲线来拟合您的收益率曲线。但我不敢在 SQL 中这样做。您可以做的是创建一种方法来计算整个样条曲线,然后将其存储为该日期的整个函数或仅存储您关心的数据点的值。您将在下载新数据后立即运行此例程,而我会将数据存储在另一个表中。然后你只需创建一个视图来基本上“选择*”。谷歌搜索“三次样条收益率曲线”。
【解决方案2】:

您可以尝试 unpivot 以获取列表中的年份和值。

然后将它与缺失的年份结合起来 选择年份No , (select YearValue where YearNo = YearNo-1) * 0.5 + (select YearValue where YearNo = YearNo+1) * 0.5 AS YearValue 来自未透视表 其中 YearNo 在(我们缺失的年份列表)

然后再次将其转回以获得您需要的格式并将其弹出到视图中?

【讨论】:

    【解决方案3】:

    我会猜测,如果存在差距,您希望曲线在两年之间平稳移动,因此如果缺少一年以上,您不想只平均最接近的两年。以下是我可能会使用的:

    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
    

    您可以使用 CTE 而不是 Numbers 表(只是一个连续数字表)来重写它。如果你想这样做,你也可以使用 NOT EXISTS 或带有 MIN 和 MAX 的子查询,而不是 YC_BOT2 和 YC_TOP2 上的 LEFT OUTER JOIN。有些人觉得这种方法令人困惑。

    【讨论】:

      【解决方案4】:
      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
      

      对于缺少的年份,此查询采用找到的最接近年份的平均值。

      【讨论】:

      • 我相信'cal' CTE 会因为递归限制而中断。
      • @van:当然,忘了限制器。已更正。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-02
      • 2016-08-30
      • 1970-01-01
      • 2019-11-30
      • 1970-01-01
      相关资源
      最近更新 更多