填补Pentaho数据集成流中的数据空白是否可能?

时间:2014-09-25 13:37:12

标签: pentaho kettle

我有一个CSV文件,货币兑换欧元兑美元。该文件是从Bank of Canada下载的。我从2013年10月10日开始下载CSV数据。

然而,数据存在差距,即。没有转换率的天数。

我一直在与Spoon Kettle战斗(第一天),找出一种简单(但一般)的方法来填补空白,比如,使用最后一个非空值。我设法完成此任务的唯一方法是链接4“获取前一行字段”并使用计算器中的NVL获取第一个非空值。但这只有在间隙不超过流中的4行时才有效。

图像代表转型:

Painfully filling the gaps

我的第一个问题简化为:是否有通用的方法在带间隙的流中进行插值/外推?

我尝试使用“修改过的JavaScript值”,但API仍然让我感到厌烦。此外,似乎这一步只有MapReduce组合的Map部分,我可能需要两者。

所以,我的第二个问题是:有没有办法用非Java语言(Scala,Clojure,Jython或JS)编写MapReduce组合?

2 个答案:

答案 0 :(得分:8)

您可以使用以下三个步骤的组合:

1)分析查询 - 允许您在当前行之前或之后获取字段N行的值;在您的情况下,您将需要提前1行(下一个可用日期)

2)计算器 - 确定了行的上一个日期,用它来计算日期之间的天数;

3)将字段number_of_clones计算为dbd-1(缺少的天数;

4)在Clone Rows步骤中使用该字段,根据需要多次连续多次;添加clone_number字段

5)将clone_number添加为日期的天数,然后获得它所引用的日期。

此外,分析查询步骤允许您通过"将字段指定为"组。字段,这样如果你有美元的x费率,然后你有英镑的x费率,最后的美元x利率日将检索空值作为下一个值。

这是一个示例KTR文件:

Transformation to fill in the gaps

数据网格步骤会生成几行,其中包含一些数据空白:

some days are missing

Analytical查询获取相同货币值的下一个日期

enter image description here

然后计算器步骤计算缺少的行数。请注意,每种货币的最后一天将以null作为值,因此我们需要调整它并使用0代替(如果A为空,则NVL(A,B)返回B,否则返回A) enter image description here

克隆行:占用一行并创建副本。 enter image description here

clone_number字段允许我们计算行引用的实际日期 enter image description here

最后,这是数据。您想要的字段是new_date,currency和exchange_rate。使用选择值重新排序字段列表,并删除您不再需要的字段。 enter image description here

如您所见,现在我们使用之前的已知值获得2014-01-03和2014-01-04的数据。

答案 1 :(得分:1)

虽然这不是您要求的,但您可以通过使用具有通用功能的用户定义的Java类组件来实现您的目标。通过此组件的单个实例替换您的步骤Get previous row fieldsNon-values in row。在此组件的 Classes - Processor 部分中插入以下代码:

Object[] previousRow;

public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException
{
    // First, get a row from the default input hop
    Object[] r = getRow();

    // If the row object is null, we are done processing.
    if (r == null) {
      setOutputDone();
      return false;
    }

    // It is always safest to call createOutputRow() to ensure that your output row's Object[] is large
    // enough to handle any new fields you are creating in this step.
    Object[] outputRow = createOutputRow(r, data.outputRowMeta.size());

    // copy all input fields to the output fields

    for (int i=0; i < getInputRowMeta().size(); i++) {
        logBasic(data.inputRowMeta.getString(r, i));
        if (data.inputRowMeta.getString(r, i) == null && (previousRow != null))  {
            // if the current field is empty take it from the previous row
            outputRow[i] = previousRow[i];
        }   
        else {
            // otherwise use the current row
            outputRow[i] = r[i];
        }

    }

    putRow(data.outputRowMeta, outputRow);
    // store the current row as future previous row
    previousRow = data.outputRowMeta.cloneRow(outputRow);

    return true;
}

Janino类始终保留上一行的副本以填充当前行的空字段。

以下测试设置演示了组件的使用。在最简单的情况下,我们处理从CSV文件读取的流:

simple test transformation

输入文件配置如下:

configuration of CSV input file

并包含以下数据

NUMBER;STRING;DATE;CURRENCY
1;A;01.02.2014;12,5
2;B;;13,5
;;03.12.2001;
4;;;
5;C;;
6;;20.03.2005;18,2
7;D;;

用户定义的Java类组件的配置如下:

configuration of User Defined Java class component

输出文本文件包含增强行“无间隙”:

NUMBER;STRING;DATE;CURRENCY
1;A;01.02.2014; 012,50
2;B;01.02.2014; 013,50
2;B;03.12.2001; 013,50
4;B;03.12.2001; 013,50
5;C;03.12.2001; 013,50
6;C;20.03.2005; 018,20
7;D;20.03.2005; 018,20

注意:

  • 该组件是对这四种数据类型的测试,但原则上它应该适用于所有人。
  • 它与实际的字段数无关。
  • 填充字段后,它永远不会被“未填充”,这对您的设置很好(我猜)但这可能不适用于其他设置。
  • 仅当字段为null时,该机制才有效。只包含空格的字符串可能会破坏它,因此请确保在将所有字符串输送到组件之前修剪它们。

代码是使用http://wiki.pentaho.com/display/EAI/User+Defined+Java+Class作为教程编写的。

<强>附录

@manu提供的链接包含以下代码。它包含对数字格式的特定处理。请注意,它不再是完全通用的。

Object[] previousRow;
RowMetaInterface outputMeta;

public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException
{
    // First, get a row from the default input hop
    Object[] r = getRow();

    // If the row object is null, we are done processing.
    if (r == null) {
        setOutputDone();
        return false;
    }

    if (outputMeta == null) {
        outputMeta = data.outputRowMeta.clone();
    for(int i=0; i < outputMeta.size(); i++) {
        ValueMetaInterface meta = outputMeta.getValueMeta(i);
        if (meta.getType() == ValueMetaInterface.TYPE_NUMBER) {
            meta.setPrecision(4);
            meta.setConversionMask("#.####");
        }
    }
}

// It is always safest to call createOutputRow() to ensure that your output row's Object[] is large
// enough to handle any new fields you are creating in this step.
Object[] outputRow = createOutputRow(r, data.outputRowMeta.size());

// copy all input fields to the output fields

for (int i=0; i < getInputRowMeta().size(); i++) {
    if ((r[i] == null) && (previousRow != null)) {
        // if the current field is empty take it from the previous row
        outputRow[i] = previousRow[i];
    }
    else {
        // otherwise use the current row
        outputRow[i] = r[i];
    }     
}

putRow(outputMeta, outputRow);
// store the current row as future previous row
previousRow = outputMeta.cloneRow(outputRow);

return true;
}