Kettle 使用备忘录 1- 生成xml文件
(2012-06-12 10:36:36)
最近被抓壮丁去弄一个ETL的项目,要求用开源的kettle去替代原来平台上的datastage。
本文主要是记录下在使用kettle过程中碰到的头疼的问题和解决方案。。。
1. 利用 excel中的数据生成xml文件
kettle中的xml文件输出组件的功能其实是很弱的,所以要生成较为复杂的xml文件时需要使用组件:
add xml , xml join,
placeholder, js script 等等
如下是一个例子:
使用merge join主要是为了减少在xml join中需要匹配的结果集合(因为xml join中的匹配功能其实是很弱的)。
在merge join之前记录必须先排序。
placeholder 组件是往记录流中添加一个常量,这个常量在 add xml中通常不被设为属性,而是在xml join中用于放置需要被组装进去的xml元素。
以生成如下简单的xml文件为例:
<?xml version="1.0" encoding="UTF-8"?>
<document>
<section name="foo">
<p>
Hello from section foo
</p>
</section>
<section name="bar">
<p>
Hello from section bar
</p>
</section>
</document>
对应的kettle 转换为:
首先需要从excel文件中读取数据,excel文件中的内容为
利用读取到的内容生成xml元素,如下是add xml中的设置:
这一步主要生成了如下的xml元素:
<section name="foo">
<p>
Hello from section foo
</p>
</section>
<section name="bar">
<p>
Hello from section bar
</p>
</section>
接下来要生成 <document>元素,因为该元素没有对应的数据,所以使用“生成记录”组件生成一条空记录用于与之前的xml元素进行xml join。它的功能有点类似与之前提到的placeholder,不同的是这个可以作为起始输入,而placeholder是一个转换步骤。
然后是利用这条生成的记录生成xml元素document,注意属性选否:
接着就是进行xml join了,source stream中的xml元素会被拼接到target stream的xml元素中。怎么拼接有join condition properties决定,它是使用xpath来定位要拼接的位置,例如//doc 就是把source stream的元素拼接到每个doc元素中。需要注意的是,由于这次xml join之后xml文件就生成好了,所以不能把omit xml header选上。
当然通过xpath也可以做复杂的条件join,如下是个例子:
可以通过xpath指定在所有target stream的area元素中如果area的属性STATIONID的值与source stream中STATIONID的值(这个值不一定要在source的xml元素中,但必须在source stream中,例如可以是一开始从excel读入的流中)相同,那么把source stream中的xml元素放到 orderlinecomments元素中。
这步之后生成的xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<document>
<doc>
<section name="foo">
<p>
Hello from section foo
</p>
</section>
<section name="bar">
<p>
Hello from section bar
</p>
</section>
</doc>
</document>
和我们预期的多了<doc>和</doc>,这是我们使用js脚本进行替换:
js脚本处理好之后,输出流的名字就换成 xmlOderLinesNew了。
最后把这个流输出到文件中,需要注意的是在内容中不要使用“分割符”,“封闭符”,和“头部”:
--关于kettle生成xml文件的性能问题
各个步骤中addxml的效率不错,但是xmljoin在大数据量的情况下效率实在太低。。。,按照我的理解,kettle通过addxml生成一个xml内容的字符串,然后xmljoin其实是对两个大字符串进行重构,这个重构过程还涉及到字符子串的定位,拷贝,比较,甚至是字符串子串级别的join。。。效率很难能高,所以在数据量比较大的时候应该避免使用xmljoin。
其实我们使用xmljoin主要是为了生成层次化的xml文件。作为替代方案,首先在使用addxml生成xml字符串之前,先使用merge join把目标xml文件中所需的数据先生成出来,这个时候生成的xml字符串中的xml是几乎没有层次的:
例如
<?xml version="1.0" encoding="ISO-8859-1"?>
<Users>
<User id="2" name="ABC" Division="HR" country="C"/>
<User id="3" name="xyz" Division="Admin" country="D"/>
<User id="4" name="LMN" Division="Admin" country="D"/>
<User id="5" name="PQR" Division="HR" country="C"/>
</Users>
然后我们可以通过xslt进行xml文件结构的重构,xsl文件的示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="division" match="User" use="concat(@Division, ',', @country)" />
<xsl:template match="Users">
<AllUsers>
<xsl:apply-templates select="User[generate-id()=generate-id(key('division',concat(@Division, ',', @country))[1])]"/>
</AllUsers>
</xsl:template>
<xsl:template match="User">
<Division value="{@Division}" country="{@country}">
<xsl:for-each select="key('division', concat(@Division, ',', @country))">
<User id="{@id}" name="{@name}"/>
</xsl:for-each>
</Division>
</xsl:template>
</xsl:stylesheet>
生成的目标xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<AllUsers>
<Division country="C" value="HR">
<User name="ABC" id="2"/>
<User name="PQR" id="5"/>
</Division>
<Division country="D" value="Admin">
<User name="xyz" id="3"/>
</Division>
<Division country="D" value="Payroll">
<User name="LMN" id="4"/>
</Division>
</AllUsers>
具体到kettle中我们可以使用xsl transformation组件来进行转换:
其中的xsl 转换:
理想的状态是我们直接使用转换中的XSL Transformation组件,在转换级别就解决问题,但事与愿违,这个步骤老是报错。。。
只好使用job级别的XSL Transformation组件,这样会比较麻烦,要先通过一个转换生成中间xml文件保存到磁盘,然后在调用XSL Transformation组件读取这个中间xml文件以及定义好的xsl文件生成目标xml文件。。
还有一种思路是不使用kettle的xsl transformation组件,而是直接在kettle的User Define Java Class组件中通过java代码调用xsl文件进行转换,java代码调用xsl文件和xml文件进行转换的代码可以参考:
http://www.blogjava.net/yangxiang/archive/2009/08/11/290688.html
关于xsl文件的编写可以参考
http://stackoverflow.com/questions/2146648/how-to-apply-group-by-on-xslt-elements
本文主要是记录下在使用kettle过程中碰到的头疼的问题和解决方案。。。
1. 利用 excel中的数据生成xml文件
kettle中的xml文件输出组件的功能其实是很弱的,所以要生成较为复杂的xml文件时需要使用组件:
add xml ,
如下是一个例子:
使用merge join主要是为了减少在xml join中需要匹配的结果集合(因为xml join中的匹配功能其实是很弱的)。
在merge join之前记录必须先排序。
placeholder 组件是往记录流中添加一个常量,这个常量在 add xml中通常不被设为属性,而是在xml join中用于放置需要被组装进去的xml元素。
以生成如下简单的xml文件为例:
<?xml version="1.0" encoding="UTF-8"?>
<document>
</document>
对应的kettle 转换为:
首先需要从excel文件中读取数据,excel文件中的内容为
利用读取到的内容生成xml元素,如下是add xml中的设置:
这一步主要生成了如下的xml元素:
<section name="foo">
</section>
<section name="bar">
接下来要生成 <document>元素,因为该元素没有对应的数据,所以使用“生成记录”组件生成一条空记录用于与之前的xml元素进行xml join。它的功能有点类似与之前提到的placeholder,不同的是这个可以作为起始输入,而placeholder是一个转换步骤。
然后是利用这条生成的记录生成xml元素document,注意属性选否:
接着就是进行xml join了,source stream中的xml元素会被拼接到target stream的xml元素中。怎么拼接有join condition properties决定,它是使用xpath来定位要拼接的位置,例如//doc 就是把source stream的元素拼接到每个doc元素中。需要注意的是,由于这次xml join之后xml文件就生成好了,所以不能把omit xml header选上。
当然通过xpath也可以做复杂的条件join,如下是个例子:
可以通过xpath指定在所有target stream的area元素中如果area的属性STATIONID的值与source stream中STATIONID的值(这个值不一定要在source的xml元素中,但必须在source stream中,例如可以是一开始从excel读入的流中)相同,那么把source stream中的xml元素放到 orderlinecomments元素中。
这步之后生成的xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<document>
</document>
和我们预期的多了<doc>和</doc>,这是我们使用js脚本进行替换:
js脚本处理好之后,输出流的名字就换成 xmlOderLinesNew了。
最后把这个流输出到文件中,需要注意的是在内容中不要使用“分割符”,“封闭符”,和“头部”:
--关于kettle生成xml文件的性能问题
各个步骤中addxml的效率不错,但是xmljoin在大数据量的情况下效率实在太低。。。,按照我的理解,kettle通过addxml生成一个xml内容的字符串,然后xmljoin其实是对两个大字符串进行重构,这个重构过程还涉及到字符子串的定位,拷贝,比较,甚至是字符串子串级别的join。。。效率很难能高,所以在数据量比较大的时候应该避免使用xmljoin。
其实我们使用xmljoin主要是为了生成层次化的xml文件。作为替代方案,首先在使用addxml生成xml字符串之前,先使用merge join把目标xml文件中所需的数据先生成出来,这个时候生成的xml字符串中的xml是几乎没有层次的:
例如
<?xml version="1.0" encoding="ISO-8859-1"?>
<Users>
<User id="2" name="ABC" Division="HR" country="C"/>
<User id="3" name="xyz" Division="Admin" country="D"/>
<User id="4" name="LMN" Division="Admin" country="D"/>
<User id="5" name="PQR" Division="HR" country="C"/>
</Users>
然后我们可以通过xslt进行xml文件结构的重构,xsl文件的示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
</xsl:stylesheet>
生成的目标xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<AllUsers>
<Division country="C" value="HR">
<User name="ABC" id="2"/>
<User name="PQR" id="5"/>
</Division>
<Division country="D" value="Admin">
<User name="xyz" id="3"/>
</Division>
<Division country="D" value="Payroll">
<User name="LMN" id="4"/>
</Division>
</AllUsers>
具体到kettle中我们可以使用xsl transformation组件来进行转换:
其中的xsl 转换:
理想的状态是我们直接使用转换中的XSL Transformation组件,在转换级别就解决问题,但事与愿违,这个步骤老是报错。。。
只好使用job级别的XSL Transformation组件,这样会比较麻烦,要先通过一个转换生成中间xml文件保存到磁盘,然后在调用XSL Transformation组件读取这个中间xml文件以及定义好的xsl文件生成目标xml文件。。
还有一种思路是不使用kettle的xsl transformation组件,而是直接在kettle的User Define Java Class组件中通过java代码调用xsl文件进行转换,java代码调用xsl文件和xml文件进行转换的代码可以参考:
http://www.blogjava.net/yangxiang/archive/2009/08/11/290688.html
关于xsl文件的编写可以参考
http://stackoverflow.com/questions/2146648/how-to-apply-group-by-on-xslt-elements