根据前一个和下一个字段的值计算一个字段值

Posted

技术标签:

【中文标题】根据前一个和下一个字段的值计算一个字段值【英文标题】:Calculating a fields value according to the values of the previous and next fields 【发布时间】:2015-03-31 07:40:22 【问题描述】:

为清楚起见,假设我有一张带有汽车 ID、里程和日期的表格。日期始终为月份(例如 01/02/2015、01/03/2015、...)。每个 carID 每个月都有一行,但不是每行都有里程字段的值,有些是 NULL。

示例表:

carID           mileage           date
-----------------------------------------
1               400            01/01/2015
2               NULL           01/02/2015
3               NULL           01/03/2015
4               1050           01/04/2015

如果这样的字段为 NULL,我需要通过查看前一个值和下一个值来计算它应该具有的值(这些值不一定是下个月或上个月,它们可以相隔几个月)。

我想通过取前一个值和下一个值的差值来做到这一点,然后计算它们之间的时间,并根据时间制作值。但是我不知道如何做到这一点。

之前我已经使用了一点代码来查看下一个值,看起来是这样的:

, carKMcombiDiffList as (
select ml.*,
       (ml.KM - mlprev.KM) as diff
from carKMcombilist ml outer apply
     (select top 1 ml2.*
      from carKMcombilist ml2
      where ml2.FK_CarID = ml.FK_CarID and
            ml2.beginmonth < ml.beginmonth
      order by ml2.beginmonth desc
     ) mlprev
)

它的作用是检查当前值是否大于前一个值。我假设我也可以使用它来检查当前问题中的前一个问题,我只是不知道如何在其中添加下一个问题以及进行计算所需的所有逻辑。

【问题讨论】:

【参考方案1】:

假设:CarID 和日期始终是唯一的组合

这是我想出的:

select with_dates.*,
       prev_mileage.mileage as prev_mileage,
       next_mileage.mileage as next_mileage,
       next_mileage.mileage - prev_mileage.mileage as mileage_delta,
       datediff(month,prev_d,next_d) as month_delta,
       (next_mileage.mileage - prev_mileage.mileage)/datediff(month,prev_d,next_d)*datediff(month,prev_d,with_dates.d) + prev_mileage.mileage as estimated_mileage
  from (select *,
          (select top 1 d
             from mileage as prev
            where carid = c.carid
              and prev.d < c.d
              and prev.mileage is not null
         order by d desc ) as prev_d,
           (select top 1 d
             from mileage as next_rec
            where carid = c.carid
              and next_rec.d > c.d
              and next_rec.mileage is not null
         order by d asc) as next_d
          from mileage as c
         where mileage is null) as with_dates
  join mileage as prev_mileage
    on     prev_mileage.carid = with_dates.carid
       and prev_mileage.d = with_dates.prev_d
  join mileage as next_mileage
    on     next_mileage.carid = with_dates.carid
       and next_mileage.d = with_dates.next_d

逻辑: 首先,对于每个mileage is nullrecord,我选择mileage is not null 的上一个和下一个日期。在此之后,我只是加入基于 carid 和 date 的行并做一些简单的数学来近似。

希望这会有所帮助,这很有趣。

【讨论】:

谢谢,我现在去测试一下:) 当我测试它返回 0 行时,我正在通过代码试图理解它。 我理解你的代码,虽然 datediff 部分很难,但它仍然返回 0 行。 是否可能因为没有下一个或上一个值而无法工作? 是的,这是有道理的。如果您只有一辆车的 NULL 记录......或者只有以前的记录但没有后续里程的汽车,那么就没有真正的方法来近似空记录的里程【参考方案2】:

以下查询获取记录的上一个和下一个可用里程。

with data as --test data
(
    select * from (VALUES
        (0, null, getdate()),
        (1, 400, '20150101'),
        (1, null, '20150201'),
        (1, null, '20150301'),
        (1, 1050, '20150401'),
        (2, 300, '20150101'),
        (2, null, '20150201'),
        (2, null, '20150301'),
        (2, 1235, '20150401'),
        (2, null, '20150501'),
        (2, 1450, '20150601'),
        (3, 200, '20150101'),
        (3, null, '20150201')
    ) as v(carId, mileage, [date])
    where v.carId != 0
)
-- replace 'data' with your table name
select  d.*, 
        (select top 1 mileage from data dprev where dprev.mileage is not null and dprev.carId = d.carId and dprev.[date] <= d.date order by dprev.[date] desc) as 'Prev available mileage',
        (select top 1 mileage from data dnext where dnext.mileage is not null and dnext.carId = d.carId and dnext.[date] >= d.date order by dnext.[date] asc) as 'Next available mileage'
from    data d

请注意,如果在特定日期之前/之后没有可用数据,这些列仍然可以是 null

如何使用这些值由您决定。可能您想为缺少 mileage 的记录插入值。

编辑

为了插入缺失里程的值,我必须计算三个辅助列:

ri - 缺少里程的连续组中的记录索引gi - 每辆车缺少里程的连续组的索引gc - 每个连续组的记录数里程丢失

上述查询中的限制列重命名为 pa(上一个可用)和 na(下一个可用)。

查询并不紧凑,我相信它可以改进,但级联 CTE 的好处是您可以轻松检查中间结果并了解每个步骤。

SQL 小提琴:SO 29363187

with data as --test data
(
    select * from (VALUES
        (0, null, getdate()),
        (1, 400, '20150101'),
        (1, null, '20150201'),
        (1, null, '20150301'),
        (1, 1050, '20150401'),
        (2, 300, '20150101'),
        (2, null, '20150201'),
        (2, null, '20150301'),
        (2, 1235, '20150401'),
        (2, null, '20150501'),
        (2, 1450, '20150601'),
        (3, 200, '20150101'),
        (3, null, '20150201')
    ) as v(carId, mileage, [date])
    where v.carId != 0
),
-- replace 'data' with your table name
limits AS
(
    select  d.*, 
            (select top 1 mileage from data dprev where dprev.mileage is not null and dprev.carId = d.carId and dprev.[date] <= d.date order by dprev.[date] desc) as pa,
            (select top 1 mileage from data dnext where dnext.mileage is not null and dnext.carId = d.carId and dnext.[date] >= d.date order by dnext.[date] asc) as na
    from    data d
),
t1 as
(
    SELECT l.*,
           case when mileage is not null 
                then null 
                else row_number() over (partition by l.carId, l.pa, l.na  order by  l.carId, l.[date])
           end as ri,   -- index of record in a continuous group where mileage is missing
           case when mileage is not null 
                then null 
                else dense_rank() over (partition by carId order by  l.carId, l.pa, l.na)
           end as gi    -- index of  a continuous group where mileage is missing per car
    from limits l
),
t2 as
(
    select  *,
            (select count(*) from t1 tm where tm.carId = t.carId and tm.gi = t.gi)  gc  --count of records per continuous group where mileage is missing
    FROM    t1 t
)
select  *,
        case when mileage is NULL
            then pa + (na - pa) / (gc + 1.0) * ri   -- also converts from integer to decimal
            else NULL
        end as 'Interpolated value' 
from    t2
order by carId, [date]

【讨论】:

谢谢,是的,这正是我打算做的。 它可以工作,但我从上一行中删除了“=”符号,否则它将始终将当前值作为上一个值。否则,这很完美,谢谢!我会再等一会儿,如果没有其他提示,我会将其评为好答案。 还删除了另一行中的“=”符号。 您是否还需要帮助确定中间值? 是的,我现在正在看。他们建议我计算下一个日期和前一个值的日期之间的天数,然后将差异分布在上面并计算它。只是还不知道如何做到这一点。

以上是关于根据前一个和下一个字段的值计算一个字段值的主要内容,如果未能解决你的问题,请参考以下文章

MySQL,怎么根据一个字段的值,计算另外一个字段的数量?

在 Django 中,根据模型中其他字段中选择的值删除选择字段下拉列表中的选项

如何根据oracle中另一个表中的值更新一个表中的字段[重复]

根据同一个表中另一个字段的值获取Mysql编号字段的净值

我可以使用指令根据其他字段值计算字段值吗?

根据另一个字段的值设置文本字段的值