在我做的大气污染报表系统中,由于原始数据缺失,经常出现一些负数或者0的大气浓度,导致最后生成的曲线很丑,会画到水平轴以下。将这些错误的数据当错缺失数据处理,需要采取一定的手段填充。缺失的数据采取插值法填充,这一点早就确定下来,但在如何实现上却困扰很久。

     将原始问题简化一下。比如有这样一组数据。

ID  so    co1
1  0.1  0.1
2  0    0.2
3  0.2  0
4  0    0
5  0    0.4
6  0.1  0.5

插值法计算方法如下:(也可以不使用这两个步骤,只要最后的结果一致就行)
步骤一:计算缺失值上下的已知值间的斜率:
k = (b2 - b1)/(n + 1)  n 为缺失数据的个数
步骤二:计算对应的缺失值
a(i) = b1 + k * i
经过处理后,得到的数据是这样的:

1    0.10    0.10
2    0.15    0.20
3    0.20    0.27
4    0.17    0.33
5    0.13    0.40
6    0.10    0.50
        我最初的想法是:在sql语句中用for循环来做。逐条地检查每个数值,如果是0,那么获取它的前一个记录的值b1,然后再继续向后遍历,获取后面一个非0的值b2,计算这两个非0数据之间的距离n,之后再用插值法将缺失的数据计算出来,并update到b1和b2之间的每一个值。按照这个思路,很麻烦,比如遍历过程中如何获取前一个数值?出现0的时候,如何记录出现多少个0?for循环经过后,再如何update之前的数值? 被这些问题困扰很久!

在论坛上发帖解决,解决的办法很受启发。

1. 创建一个函数

ALTER  FUNCTION FUN_CO(@ID INT)
RETURNS DECIMAL(18, 3)
AS
BEGIN

DECLARE @NUM1 NUMERIC(19,2),@ID1 INT,@NUM2 NUMERIC(19,2),@ID2 INT
SELECT TOP 1 @ID1=ID , @NUM1=CO  FROM APRECORD WHERE ID<=@ID AND CO<>0 ORDER BY ID DESC

SELECT TOP 1 @ID2=ID , @NUM2=CO  FROM APRECORD WHERE ID>=@ID AND CO<>0 ORDER BY ID ASC
IF @ID2<>@ID1
	RETURN @NUM1+(((@NUM2-@NUM1)/(@ID2-@ID1))*(@ID-@ID1))

RETURN @NUM1
END

2. 更新数据库

UPDATE APRECORD
SET CO=DBO.FUN_CO(ID)
WHERE DAYTIME >= @BDT
AND DAYTIME < @EDT

       在这个解决方案中,首先查找到缺失的数据,也就是值为0的数据,然后向前查找非0数据@NUM1,以及它的编号@ID1,向后查找非0的数据@NUM2. 以及编号@ID2。也就是步骤一。然后用公式计算出填充的数据。将上述过程保存在一个函数中,在存储过程中调用。甚至不用for循环之类。

---------------------------------------------------------------------------------------

启示:

1. 明确问题,记录下来,逐步地寻求解决方案。而不是全凭脑袋空想。脑袋很容易遗漏一些因素,而且大多数时候没什么条理,跳跃性太强。解决问题需要方法学。

2. 在sql下思考。用for循环什么的,还处于静态语言的思维模式之下。sql是一门艺术!思维转换,才能发挥出语言的最大功能。当然这需要长时间的锻炼。