文章一:ETL和Kettle简介

ETL即数据抽取(Extract)、转换(Transform)、装载(Load)的过程。它是构建数据仓库的重要环节。数据仓库是面向主题的、集成的、稳定的且随时间不断变化的数据集合,用以支持经营管理中的决策制定过程。数据仓库系统中有可能存在着大量的噪声数据,引起的主要原因有:滥用缩写词、惯用语、数据输入错误、重复记录、丢失值、拼写变化等。即便是一个设计和规划良好的数据库系统,如果其中存在着大量的噪声数据,那么这个系统也是没有任何意义的,因为垃圾进,垃圾出garbagein, garbage out),系统根本就不可能为决策分析系统提供任何支持。为了清除噪声数据,必须在数据库系统中进行数据清洗。目前有不少数据清洗研究和ETL研究,但是如何在ETL过程中进行有效的数据清洗并使这个过程可视化,此方面研究不多。本文主要从两个方面阐述ETL和数据清洗的实现过程:ETL的处理方式和数据清洗的实现方法。
(1)       ETL的处理方式
本文所采用的ETL方法是数据库段区域中的ETL处理方式,它不使用外部引擎而是使用数据库作为唯一的控制点。由于源系统SQLserver2000是关系数据库,它的段表也是典型的关系型表。成功地将外部未修改数据载入数据库后,再在数据库内部进行转换。数据库段区域中的ETL处理方式执行的步骤是提取、装载、转换,即通常所说的ELT。这种方式的优点是为抽取出的数据首先提供一个缓冲以便于进行复杂的转换,减轻了ETL进程的复杂度。
(2)       ETL过程中实现数据清洗的实现方法
首先,在理解源数据的基础上实现数据表属性一致化。为解决源数据的同义异名和同名异义的问题,可通过元数据管理子系统,在理解源数据的同时,对不同表的属性名根据其含义重新定义其在数据挖掘库中的名字,并以转换规则的形式存放在元数据库中,在数据集成的时候,系统自动根据这些转换规则将源数据中的字段名转换成新定义的字段名,从而实现数据挖掘库中的同名同义。
其次,通过数据缩减,大幅度缩小数据量。由于源数据量很大,处理起来非常耗时,所以可以优先进行数据缩减,以提高后续数据处理分析效率。
最后,通过预先设定数据处理的可视化功能节点,达到可视化的进行数据清洗和数据转换的目的。针对缩减并集成后的数据,通过组合预处理子系统提供各种数据处理功能节点,能够以可视化的方式快速有效完成数据清洗和数据转换过程。

2.   KETTLE简介
    现在是一个Google的时代,而对于开发者,开源已成为最重要的参考书。对于某课题,不管你是深入研究还是初窥门径。估且google一把,勾一勾同行的成就,你必会获益良多。
    说到ETL开源项目,Kettle当属翘首,项目名称很有意思,水壶。按项目负责人Matt的说法:把各种数据放到一个壶里,然后呢,以一种你希望的格式流出。呵呵,外国人都很有联想力。
    看了提供的文档,然后对发布程序的简单试用后,可以很清楚得看到Kettle的四大块:
Chef——工作(job)设计工具 (GUI方式)
Kitchen——工作(job)执行器 (命令行方式)
Spoon——转换(transform)设计工具 (GUI方式)
Span——转换(trasform)执行器 (命令行方式)
2.1.Chef——工作(job)设计器
这是一个GUI工具,操作方式主要通过拖拖拉拉,勿庸多言,一看就会。
何谓工作?多个作业项,按特定的工作流串联起来,开成一项工作。正如:我的工作是软件开发。我的作业项是:设计、编码、测试!先设计,如果成功,则编码,否则继续设计,编码完成则开始设计,周而复始,作业完成。
2.1.1.    Chef中的作业项包括:
转换:指定更细的转换任务,通过Spoon生成。通过Field来输入参数;
SQLsql语句执行;
FTP:下载ftp文件;
邮件:发送邮件;
检查表是否存在
检查文件是否存在
执行shell脚本:如dos命令。
批处理(注意:windows批处理不能有输出到控制台)
Job:作为嵌套作业使用。
JavaScript执行:这个比较有意思,我看了一下源码,如果你有自已的Script引擎,可以很方便的替换成自定义Script,来扩充其功能;
SFTP:安全的Ftp协议传输;
HTTP方式的上/下传
2.1.2.    工作流
如上文所述,工作流是作业项的连接方式。分为三种:无条件,成功,失败,为了方便工作流使用,KETTLE提供了几个辅助结点单元(也可将其作为简单的作业项)
Start单元:任务必须由此开始。设计作业时,以此为起点。
OK单元:可以编制做为中间任务单元,且进行脚本编制,用来控制流程。
ERROR单元:用途同上。
DUMMY单元:什么都不做,主要是用来支持多分支的情况,文档中有例子。
2.1.3.    存储方式
支持XML存储,或存储到指定数据库中。
一些默认的配置(如数据库存储位置……),在系统的用户目录下,单独建立了一个.Kettle目录,用来保存用户的这些设置。
2.1.4.    LogView:
可查看执行日志。
2.2.Kitchen——作业执行器
是一个作业执行引擎,用来执行作业。这是一个命令行执行工具,没啥可讲的,就把它的参数说明列一下。
    -rep      : Repository name   任务包所在存储名
    -user     : Repository username   执行人
    -pass     : Repository password   执行人密码
    -job      : The name of the job to launch 任务包名称
    -dir      : The directory (don't forget the leading / or /)
    -file     : The filename (Job XML) to launch
    -level    : The logging level (Basic, Detailed, Debug, Rowlevel, Error, Nothing) 指定日志级别
    -log      : The logging file to write to 指定日志文件
    -listdir : List the directories in the repository 列出指定存储中的目录结构。
    -listjobs : List the jobs in the specified directory 列出指定目录下的所有任务
    -listrep : List the defined repositories 列出所有的存储
    -norep    : Don't log into the repository 不写日志
    嗯,居然不支持调度。看了一下文档,建议使用操作系统提供的调度器来实现调度,比如:Windows可以使用它的任务计划工具。
2.3.Spoon——转换过程设计器
    GUI工作,用来设计数据转换过程,创建的转换可以由Pan来执行,也可以被Chef所包含,作为作业中的一个作业项。
    下面简单列举一下所有的转换过程。(简单描述,详细的可见Spoon文档)
2.3.1.    Input-Steps:输入步骤
l         Text file input:文本文件输入
可以支持多文件合并,有不少参数,基本一看参数名就能明白其意图。
l         Table input:数据表输入
实际上是视图方式输入,因为输入的是sql语句。当然,需要指定数据源(数据源的定制方式在后面讲一下)
l         Get system info:取系统信息
就是取一些固定的系统环境值,如本月最后一天的时间,本机的IP地址之类。
l         Generate Rows:生成多行。
这个需要匹配使用,主要用于生成多行的数据输入,比如配合Add sequence可以生成一个指定序号的数据列。
l         XBase Input
l         Excel Input
l         XML Input
这三个没啥可讲的,看看参数就明了。
2.3.2.    Output-Steps: 输出步聚
l         Text file output:文本文件输出。这个用来作测试蛮好,呵呵。很方便的看到转换的输出。
l         Table output:输出到目的表。
l         Insert/Update:目的表和输入数据行进行比较,然后有选择的执行增加,更新操作。
l         Update:同上,只是不支持增加操作。
l         XML Output
2.3.3.    Look-up:查找操作
l         Data Base
l         Stream
l         Procedure
l         Database join
2.3.4.    Transform 转换
l         Select values
对输入的行记录数据的字段进行更改 (更改数据类型,更改字段名或删除数据类型变更时,数据的转换有固定规则,可简单定制参数。可用来进行数据表的改装。
l         Filter rows
 对输入的行记录进行指定复杂条件的过滤。用途可扩充sql语句现有的过滤功能。但现有提供逻辑功能超出标准sql的不多。
l         Sort rows
对指定的列以升序或降序排序,当排序的行数超过5000时需要临时表。
l         Add sequence
为数据流增加一个序列,这个配合其它Step(Generate rows, rows join),可以生成序列表,如日期维度表(年、月、日)
l         Dummy
不做任何处理,主要用来作为分支节点。
l         Join Rows
对所有输入流做笛卡儿乘积。
l         Aggregate
聚合,分组处理
l         Group by
分组,用途可扩充sql语句现有的分组,聚合函数。但我想可能会有其它方式的sql语句能实现。
l         Java Script value
使用mozillarhino作为脚本语言,并提供了很多函数,用户可以在脚本中使用这些函数。
l         Row Normaliser
该步骤可以从透视表中还原数据到事实表,通过指定维度字段及其分类值,度量字段,最终还原出事实表数据。
l         Unique rows
去掉输入流中的重复行,在使用该节点前要先排序,否则只能删除连续的重复行。 
l         Calculator
提供了一组函数对列值进行运算,用该方式比用户自定义JAVA SCRIPT脚本速度更快。
l         Merge Rows
用于比较两组输入数据,一般用于更新后的数据重新导入到数据仓库中。
l         Add constants
增加常量值。
l         Row denormaliser
Normaliser过程相反。
l         Row flattener
表扁平化处理,指定需处理的字段和扃平化后的新字段,将其它字段做为组合Key进行扃平化处理。
2.3.5.    除了上述基本节点类型外还定义了扩展节点类型 
l         SPLIT FIELDS
按指定分隔符拆分字段
l         EXECUTE SQL SCRIPT
执行SQL语句
l         CUBE INPUT
l         CUBE OUTPUT
2.3.6.    其它
l         存储方式:Chef相同。
l         数据源(Connection);见后。
l         Hopssetp连接起来,形成Hops
l         Plugin step types等节点:这个没仔细看,不知如何制作Pluginstep
l         LogView:可查看执行日志。
2.4.Pan——转换的执行工具
命令行执行方式,可以执行由Spoon生成的转换任务。同样,不支持调度。参数与Kitchen类似,可参见Pan的文档。
2.5.其它
Connection
可以配置多个数据源,在Job或是Trans中使用,这意味着可以实现跨数据库的任务。支持大多数市面上流行的数据库。
2.6.个人感觉:(本人不成熟的看法)
1、 转换功能全,使用简洁。作业项丰富,流程合理。但缺少调度。
2、 java代码,支持的数据源范围广,所以,跨平台性较好。
3、 从实际项目的角度看,和其它开源项目类似,主要还是程序员的思维,缺少与实际应用项目(专业领域)的更多接轨,当然,项目实施者的专注点可能在于一个平台框架,而非实际应用(实际应用需要二次开发)
4、 看过了大多数源码,发现源码的可重用性不是太好(缺少大粒度封装),有些关键部分好像有Bug。比如:个别class过于臃肿,线程实现的同步有问题。
5、 提供的工具有些小错,如参数的容错处理。
3.   ETL 小结
做数据仓库系统,ETL是关键的一环。说大了,ETL是数据整合解决方案,说小了,就是倒数据的工具。回忆一下工作这么些年来,处理数据迁移、转换的工作倒还真的不少。但是那些工作基本上是一次性工作或者很小数据量,使用access、 DTS或是自己编个小程序搞定。可是在数据仓库系统中,ETL上升到了一定的理论高度,和原来小打小闹的工具使用不同了。究竟什么不同,从名字上就可以看到,人家已经将倒数据的过程分成3个步骤,E、T、L分别代表抽取、转换和装载。
其实ETL过程就是数据流动的过程,从不同的数据源流向不同的目标数据。但在数据仓库中,ETL有几个特点,一是数据同步,它不是一次性倒完数据就拉到,它是经常性的活动,按照固定周期运行的,甚至现在还有人提出了实时ETL的概念。二是数据量,一般都是巨大的,值得你将数据流动的过程拆分成E、T和L。
现在有很多成熟的工具提供ETL功能,例如datastage、powermart 等,且不说他们的好坏。从应用角度来说,ETL的过程其实不是非常复杂,这些工具给数据仓库工程带来和很大的便利性,特别是开发的便利和维护的便利。但另一方面,开发人员容易迷失在这些工具中。举个例子,VB是一种非常简单的语言并且也是非常易用的编程工具,上手特别快,但是真正VB的高手有多少?微软设计的产品通常有个原则是“将使用者当作傻瓜”,在这个原则下,微软的东西确实非常好用,但是对于开发者,如果你自己也将自己当作傻瓜,那就真的傻了。 ETL工具也是一样,这些工具为我们提供图形化界面,让我们将主要的精力放在规则上,以期提高开发效率。从使用效果来说,确实使用这些工具能够非常快速地构建一个job来处理某个数据,不过从整体来看,并不见得他的整体效率会高多少。问题主要不是出在工具上,而是在设计、开发人员上。他们迷失在工具中,没有去探求ETL的本质。
可以说这些工具应用了这么长时间,在这么多项目、环境中应用,它必然有它成功之处,它必定体现了ETL的本质。如果我们不透过表面这些工具的简单使用去看它背后蕴涵的思想,最终我们作出来的东西也就是一个个独立的job,将他们整合起来仍然有巨大的工作量。大家都知道“理论与实践相结合”,如果在一个领域有所超越,必须要在理论水平上达到一定的高度

4.   ETL本质
4.1.ETL 特点
ETL的过程就是数据流动的过程,从不同异构数据源流向统一的目标数据。其间,数据的抽取、清洗、转换和装载形成串行或并行的过程。ETL的核心还是在于T这个过程,也就是转换,而抽取和装载一般可以作为转换的输入和输出,或者,它们作为一个单独的部件,其复杂度没有转换部件高。和OLTP系统中不同,那里充满这单条记录的insert、update和select等操作,ETL过程一般都是批量操作,例如它的装载多采用批量装载工具,一般都是DBMS系统自身附带的工具,例如Oracle SQLLoader和DB2的autoloader等。
ETL本身有一些特点,在一些工具中都有体现,下面以datastage和powermart举例来说。
1、静态的ETL单元和动态的ETL单元实例;一次转换指明了某种格式的数据如何格式化成另一种格式的数据,对于数据源的物理形式在设计时可以不用指定,它可以在运行时,当这个ETL单元创建一个实例时才指定。对于静态和动态的ETL单元,Datastage没有严格区分,它的一个Job就是实现这个功能,在早期版本,一个Job同时不能运行两次,所以一个Job相当于一个实例,在后期版本,它支持multiple instances,而且还不是默认选项。Powermart中将这两个概念加以区分,静态的叫做Mapping,动态运行时叫做Session。
2、ETL元数据;元数据是描述数据的数据,他的含义非常广泛,这里仅指ETL的元数据。主要包括每次转换前后的数据结构和转换的规则。ETL元数据还包括形式参数的管理,形式参数的ETL单元定义的参数,相对还有实参,它是运行时指定的参数,实参不在元数据管理范围之内。
3、数据流程的控制;要有可视化的流程编辑工具,提供流程定义和流程监控功能。流程调度的最小单位是ETL单元实例,ETL单元是不能在细分的ETL过程,当然这由开发者来控制,例如可以将抽取、转换放在一个ETL单元中,那样这个抽取和转换只能同时运行,而如果将他们分作两个单元,可以分别运行,这有利于错误恢复操作。当然,ETL单元究竟应该细分到什么程度应该依据具体应用来看,目前还没有找到很好的细分策略。比如,我们可以规定将装载一个表的功能作为一个ETL单元,但是不可否认,这样的ETL单元之间会有很多共同的操作,例如两个单元共用一个Hash表,要将这个Hash表装入内存两次。
4、转换规则的定义方法;提供函数集提供常用规则方法,提供规则定义语言描述规则。
5、对数据的快速索引;一般都是利用Hash技术,将参照关系表提前装入内存,在转换时查找这个hash表。Datastage中有Hash文件技术,Powermart也有类似的Lookup功能。
4.2.ETL 类型
昨在IT-Director上阅读一篇报告,关于ETL产品分类的。一般来说,我们眼中的ETL工具都是价格昂贵,能够处理海量数据的家伙,但是这是其中的一种。它可以分成4种,针对不同的需求,主要是从转换规则的复杂度和数据量大小来看。它们包括:
1、交互式运行环境,你可以指定数据源、目标数据,指定规则,立马ETL。这种交互式的操作无疑非常方便,但是只能适合小数据量和复杂度不高的ETL过程,因为一旦规则复杂了,可能需要语言级的描述,不能简简单单拖拖拽拽就可以的。还有数据量的问题,这种交互式必然建立在解释型语言基础上,另外他的灵活性必然要牺牲一定的性能为代价。所以如果要处理海量数据的话,每次读取一条记录,每次对规则进行解释执行,每次在写入一条记录,这对性能影响是非常大的。
2、专门编码型的,它提供了一个基于某种语言的程序框架,你可以不必将编程精力放在一些周边的功能上,例如读文件功能、写数据库的功能,而将精力主要放在规则的实现上面。这种近似手工代码的性能肯定是没话说,除非你的编程技巧不过关(这也是不可忽视的因素之一)。对于处理大数据量,处理复杂转换逻辑,这种方式的ETL实现是非常直观的。
3、代码生成器型的,它就像是一个ETL代码生成器,提供简单的图形化界面操作,让你拖拖拽拽将转换规则都设定好,其实他的后台都是生成基于某种语言的程序,要运行这个ETL过程,必须要编译才行。Datastage就是类似这样的产品,设计好的job必须要编译,这避免了每次转换的解释执行,但是不知道它生成的中间语言是什么。以前我设计的ETL工具大挪移其实也是归属于这一类,它提供了界面让用户编写规则,最后生成C++语言,编译后即可运行。这类工具的特点就是要在界面上下狠功夫,必须让用户轻松定义一个ETL过程,提供丰富的插件来完成读、写和转换函数。大挪移在这方面就太弱了,规则必须手写,而且要写成标准c++语法,这未免还是有点难为最终用户了,还不如做成一个专业编码型的产品呢。另外一点,这类工具必须提供面向专家应用的功能,因为它不可能考虑到所有的转换规则和所有的读写,一方面提供插件接口来让第三方编写特定的插件,另一方面还有提供特定语言来实现高级功能。例如Datastage提供一种类Basic的语言,不过他的Job的脚本化实现好像就做的不太好,只能手工绘制job,而不能编程实现Job。
4、最后还有一种类型叫做数据集线器,顾名思义,他就是像Hub一样地工作。将这种类型分出来和上面几种分类在标准上有所差异,上面三种更多指ETL实现的方法,此类主要从数据处理角度。目前有一些产品属于EAI(Enterprise Application Integration),它的数据集成主要是一种准实时性。所以这类产品就像Hub一样,不断接收各种异构数据源来的数据,经过处理,在实施发送到不同的目标数据中去。
虽然,这些类看似各又千秋,特别在BI项目中,面对海量数据的ETL时,中间两种的选择就开始了,在选择过程中,必须要考虑到开发效率、维护方面、性能、学习曲线、人员技能等各方面因素,当然还有最重要也是最现实的因素就是客户的意象。
4.3.ETL 中的转换-Transication
ETL探求之一中提到,ETL过程最复杂的部分就是T,这个转换过程,T过程究竟有哪些类型呢?
4.3.1.    宏观输入输出方面
从对数据源的整个宏观处理分,看看一个ETL过程的输入输出,可以分成下面几类:
1、大小交,这种处理在数据清洗过程是常见了,例如从数据源到ODS阶段,如果数据仓库采用维度建模,而且维度基本采用代理键的话,必然存在代码到此键值的转换。如果用SQL实现,必然需要将一个大表和一堆小表都Join起来,当然如果使用ETL工具的话,一般都是先将小表读入内存中再处理。这种情况,输出数据的粒度和大表一样。
2、大大交,大表和大表之间关联也是一个重要的课题,当然其中要有一个主表,在逻辑上,应当是主表Left Join辅表。大表之间的关联存在最大的问题就是性能和稳定性,对于海量数据来说,必须有优化的方法来处理他们的关联,另外,对于大数据的处理无疑会占用太多的系统资源,出错的几率非常大,如何做到有效错误恢复也是个问题。对于这种情况,我们建议还是尽量将大表拆分成适度的稍小一点的表,形成大小交的类型。这类情况的输出数据粒度和主表一样。
3、站着进来,躺着出去。事务系统中为了提高系统灵活性和扩展性,很多信息放在代码表中维护,所以它的“事实表”就是一种窄表,而在数据仓库中,通常要进行宽化,从行变成列,所以称这种处理情况叫做“站着进来,躺着出去”。大家对 Decode肯定不陌生,这是进行宽表化常见的手段之一。窄表变宽表的过程主要体现在对窄表中那个代码字段的操作。这种情况,窄表是输入,宽表是输出,宽表的粒度必定要比窄表粗一些,就粗在那个代码字段上。
4、聚集。数据仓库中重要的任务就是沉淀数据,聚集是必不可少的操作,它是粗化数据粒度的过程。聚集本身其实很简单,就是类似SQL中Group by的操作,选取特定字段(维度),对度量字段再使用某种聚集函数。但是对于大数据量情况下,聚集算法的优化仍是探究的一个课题。例如是直接使用SQL的 Group by,还是先排序,在处理。
4.3.2.    微观规则
从数据的转换的微观细节分,可以分成下面的几个基本类型,当然还有一些复杂的组合情况,例如先运算,在参照转换的规则,这种基于基本类型组合的情况就不在此列了。ETL的规则是依赖目标数据的,目标数据有多少字段,就有多少条规则。
1、直接映射,原来是什么就是什么,原封不动照搬过来,对这样的规则,如果数据源字段和目标字段长度或精度不符,需要特别注意看是否真的可以直接映射还是需要做一些简单运算;
2、字段运算,数据源的一个或多个字段进行数学运算得到的目标字段,这种规则一般对数值型字段而言;
3、参照转换,在转换中通常要用数据源的一个或多个字段作为Key。
4.4.ETL中数据质量
“不要绝对的数据准确,但要知道为什么不准确。”这是我们在构建BI系统是对数据准确性的要求。确实,对绝对的数据准确谁也没有把握,不仅是系统集成商,包括客户也是无法确定。准确的东西需要一个标准,但首先要保证这个标准是准确的,至少现在还没有这样一个标准。客户会提出一个相对标准,例如将你的OLAP数据结果和报表结果对比。虽然这是一种不太公平的比较,你也只好认了吧。
首先在数据源那里,已经很难保证数据质量了,这一点也是事实。在这一层有哪些可能原因导致数据质量问题?可以分为下面几类:
1、数据格式错误,例如缺失数据、数据值超出范围或是数据格式非法等。要知道对于同样处理大数据量的数据源系统,他们通常会舍弃一些数据库自身的检查机制,例如字段约束等。他们尽可能将数据检查在入库前保证,但是这一点是很难确保的。这类情况诸如身份证号码、手机号、非日期类型的日期字段等。
2、数据一致性,同样,数据源系统为了性能的考虑,会在一定程度上舍弃外键约束,这通常会导致数据不一致。例如在帐务表中会出现一个用户表中没有的用户ID,在例如有些代码在代码表中找不到等。
3、业务逻辑的合理性,这一点很难说对与错。通常,数据源系统的设计并不是非常严谨,例如让用户开户日期晚于用户销户日期都是有可能发生的,一个用户表中存在多个用户ID也是有可能发生的。对这种情况,有什么办法吗?
构建一个BI系统,要做到完全理解数据源系统根本就是不可能的。特别是数据源系统在交付后,有更多维护人员的即兴发挥,那更是要花大量的时间去寻找原因。以前曾经争辩过设计人员对规则描述的问题,有人提出要在ETL开始之前务必将所有的规则弄得一清二楚。我并不同意这样的意见,倒是认为在ETL过程要有处理这些质量有问题数据的保证。一定要正面这些脏数据,是丢弃还是处理,无法逃避。如果没有质量保证,那么在这个过程中,错误会逐渐放大,抛开数据源质量问题,我们再来看看ETL过程中哪些因素对数据准确性产生重大影响。
1、规则描述错误。上面提到对设计人员对数据源系统理解的不充分,导致规则理解错误,这是一方面。另一方面,是规则的描述,如果无二义性地描述规则也是要探求的一个课题。规则是依附于目标字段的,在探求之三中,提到规则的分类。但是规则总不能总是用文字描述,必须有严格的数学表达方式。我甚至想过,如果设计人员能够使用某种规则语言来描述,那么我们的ETL单元就可以自动生成、同步,省去很多手工操作了。
2、ETL开发错误。即时规则很明确,ETL开发的过程中也会发生一些错误,例如逻辑错误、书写错误等。例如对于一个分段值,开区间闭区间是需要指定的,但是常常开发人员没注意,一个大于等于号写成大于号就导致数据错误。
3、人为处理错误。在整体ETL流程没有完成之前,为了图省事,通常会手工运行ETL过程,这其中一个重大的问题就是你不会按照正常流程去运行了,而是按照自己的理解去运行,发生的错误可能是误删了数据、重复装载数据等。
4.5.ETL数据质量保证
上回提到ETL数据质量问题,这是无法根治的,只能采取特定的手段去尽量避免,而且必须要定义出度量方法来衡量数据的质量是好还是坏。对于数据源的质量,客户对此应该更加关心,如果在这个源头不能保证比较干净的数据,那么后面的分析功能的可信度也都成问题。数据源系统也在不断进化过程中,客户的操作也在逐渐规范中,BI系统也同样如此。本文探讨一下对数据源质量和ETL处理质量的应对方法。
如何应对数据源的质量问题?记得在onteldatastage列表中也讨论过一个话题-"-1的处理",在数据仓库模型维表中,通常有一条-1记录,表示“未知”,这个未知含义可广了,任何可能出错的数据,NULL数据甚至是规则没有涵盖到的数据,都转成-1。这是一种处理脏数据的方法,但这也是一种掩盖事实的方法。就好像写一个函数FileOpen(filename),返回一个错误码,当然,你可以只返回一种错误码,如-1,但这是一种不好的设计,对于调用者来说,他需要依据这个错误码进行某些判断,例如是文件不存在,还是读取权限不够,都有相应的处理逻辑。数据仓库中也是一样,所以,建议将不同的数据质量类型处理结果分别转换成不同的值,譬如,在转换后,-1表示参照不上,-2表示NULL数据等。不过这仅仅对付了上回提到的第一类错误,数据格式错误。对于数据一致性和业务逻辑合理性问题,这仍有待探求。但这里有一个原则就是“必须在数据仓库中反应数据源的质量”。
对于ETL过程中产生的质量问题,必须有保障手段。从以往的经验看,没有保障手段给实施人员带来麻烦重重。实施人员对于反复装载数据一定不会陌生,甚至是最后数据留到最后的Cube,才发现了第一步ETL其实已经错了。这个保障手段就是数据验证机制,当然,它的目的是能够在ETL过程中监控数据质量,产生报警。这个模块要将实施人员当作是最终用户,可以说他们是数据验证机制的直接收益者。
首先,必须有一个对质量的度量方法,什么是高质什么是低质,不能靠感官感觉,但这却是在没有度量方法条件下通常的做法。那经营分析系统来说,联通总部曾提出测试规范,这其实就是一种度量方法,例如指标的误差范围不能高于5%等,对系统本身来说其实必须要有这样的度量方法,先不要说这个度量方法是否科学。对于ETL数据处理质量,他的度量方法应该比联通总部测试规范定义的方法更要严格,因为他更多将BI系统看作一个黑盒子,从数据源到展现的数据误差允许一定的误差。而ETL数据处理质量度量是一种白盒的度量,要注重每一步过程。因此理论上,要求输入输出的指标应该完全一致。但是我们必须正面完全一致只是理想,对于有误差的数据,必须找到原因。
在质量度量方法的前提下,就可以建立一个数据验证框架。此框架依据总量、分量数据稽核方法,该方法在高的《数据仓库中的数据稽核技术》一文中已经指出。作为补充,下面提出几点功能上的建议:
1、提供前端。将开发实施人员当作用户,同样也要为之提供友好的用户界面。《稽核技术》一文中指出测试报告的形式,这种形式还是要依赖人为判断,在一堆数据中去找规律。到不如用OLAP的方式提供界面,不光是加上测试统计出来的指标结果,并且配合度量方法的计算。例如误差率,对于误差率为大于0的指标,就要好好查一下原因了。
2、提供框架。数据验证不是一次性工作,而是每次ETL过程中都必须做的。因此,必须有一个框架,自动化验证过程,并提供扩展手段,让实施人员能够增加验证范围。有了这样一个框架,其实它起到规范化操作的作用,开发实施人员可以将主要精力放在验证脚本的编写上,而不必过多关注验证如何融合到流程中,如何展现等工作。为此,要设计一套表,类似于DM表,每次验证结果数据都记录其中,并且自动触发多维分析的数据装载、发布等。这样,实施人员可以在每次装载,甚至在流程过程中就可以观察数据的误差率。特别是,如果数据仓库的模型能够统一起来,甚至数据验证脚本都可以确定下来,剩下的就是规范流程了。
3、规范流程。上回提到有一种ETL数据质量问题是由于人工处理导致的,其中最主要原因还是流程不规范。开发实施人员运行单独一个ETL单元是很方便的,虽然以前曾建议一个ETL单元必须是“可重入”的,这能够解决误删数据,重复装载数据问题。但要记住数据验证也是在流程当中,要让数据验证能够日常运作,就不要让实施者感觉到他的存在。总的来说,规范流程是提高实施效率的关键工作,这也是以后要继续探求的。
4.6.关于元数据
对于元数据(Metadata)的定义到目前为止没有什么特别精彩的,这个概念非常广,一般都是这样定义,“元数据是描述数据的数据(Data about Data)”,这造成一种递归定义,就像问小强住在哪里,答,在旺财隔壁。按照这样的定义,元数据所描述的数据是什么呢?还是元数据。这样就可能有元元元...元数据。我还听说过一种对元数据,如果说数据是一抽屉档案,那么元数据就是分类标签。那它和索引有什么区别?
元数据体现是一种抽象,哲学家从古至今都在抽象这个世界,力图找到世界的本质。抽象不是一层关系,它是一种逐步由具体到一般的过程。例如我->男人->人->哺乳动物->生物这就是一个抽象过程,你要是在软件业混会发现这个例子很常见,面向对象方法就是这样一种抽象过程。它对世界中的事物、过程进行抽象,使用面向对象方法,构建一套对象模型。同样在面向对象方法中,类是对象的抽象,接口又是对类的抽象。因此,我认为可以将“元”和“抽象”换一下,叫抽象数据是不是好理解一些。
常听到这样的话,“xx领导的讲话高屋建瓴,给我们后面的工作指引的清晰的方向”,这个成语“高屋建瓴”,站在10楼往下到水,居高临下,能砸死人,这是指站在一定的高度看待事物,这个一定的高度就是指他有够“元”。在设计模式中,强调要对接口编程,就是说你不要处理这类对象和那类对象的交互,而要处理这个接口和那个接口的交互,先别管他们内部是怎么干的。
元数据存在的意义也在于此,虽然上面说了一通都撤到哲学上去,但这个词必须还是要结合软件设计中看,我不知道在别的领域是不是存在Metadata这样的叫法,虽然我相信别的领域必然有类似的东东。元数据的存在就是要做到在更高抽象一层设计软件。这肯定有好处,什么灵活性啊,扩展性啊,可维护性啊,都能得到提高,而且架构清晰,只是弯弯太多,要是从下往上看,太复杂了。很早以前,我曾看过 backorifice的代码,我靠,一个简单的功能,从这个类转到父类,又转到父类,很不理解,为什么一个简单的功能不在一个类的方法中实现就拉到了呢?现在想想,还真不能这样,这虽然使代码容易看懂了,但是结构确实混乱的,那他只能干现在的事,如果有什么功能扩展,这些代码就废了。
我从98年刚工作时就开始接触元数据的概念,当时叫做元数据驱动的系统架构,后来在 QiDSS中也用到这个概念构建QiNavigator,但是现在觉得元数据也没啥,不就是建一堆表描述界面的元素,再利用这些数据自动生成界面吗。到了数据仓库系统中,这个概念更强了,是数据仓库中一个重要的部分。但是至今,我还是认为这个概念过于玄乎,看不到实际的东西,市面上有一些元数据管理的东西,但是从应用情况就得知,用的不多。之所以玄乎,就是因为抽象层次没有分清楚,关键就是对于元数据的分类(这种分类就是一种抽象过程)和元数据的使用。你可以将元数据抽象成0和1,但是那样对你的业务有用吗?必须还得抽象到适合的程度,最后问题还是“度”。
数据仓库系统的元数据作用如何?还不就是使系统自动运转,易于管理吗?要做到这一步,可没必要将系统抽象到太极、两仪、八卦之类的,业界也曾定义过一些元数据规范,向CWM、XMI等等,可以借鉴,不过俺对此也是不精通的说,以后再说。


文章二:Kattle API 实战
前言:

为什么要用Kettle和KETTLE JAVA API?
Kettle是什么?kettle:是一个开源ETL工具。kettle提供了基于java的图形化界面,使用很方便,kettle的ETL工具集合也比较多,常用的ETL工具都包含了。

为什么使用KETTLE JAVA API:就像kettle文档所说:KETTLE JAVA API : Program your own Kettle transformation,kettle提供了基于 JAVA的脚步编写功能,可以灵活地自定义ETL过程,使自行定制、批量处理等成为可能,这才是一个程序员需要做的工作,而不仅是象使用word一样操作kettle用户界面。

KETTLE JAVA API 实战操作记录:

、         搭建环境 :到http://www.kettle.be网站下载kettle的源码包,加压缩,例如解压缩到d:/kettle目录

、         打开eclipse,新建一个项目,要使用jdk1.5.0,因为kettle的要使用System.getenv(),只有在jdk1.5.0才被支持。提起getenv(),好像有一段几起几落的记录,曾一度被抛弃,现在又被jdk1.5支持了.

、         建一个class : TransBuilder.java,可以把d:/kettle/ extra/TransBuilder.java的内容原样拷贝到你的TransBuilder.java里。

、         根据需要编辑源码。并需要对原程序进行如下修改,在头部增加:

import org.eclipse.swt.dnd.Transfer;

//这个包被遗漏了,原始位置kettle根目录/libswt/win32/swt.jar

//add by chq(www.chq.name) on  2006.07.20

(后来发现,不必加这个引用,因为编译时不需要)

、         编译准备,在eclipse中增加jar包,主要包括(主要依据extra/TransBuilder.bat):

/lib/kettle.jar
/libext/CacheDB.jar
/libext/SQLBaseJDBC.jar
/libext/activation.jar
/libext/db2jcc.jar
/libext/db2jcc_license_c.jar
/libext/edtftpj-1.4.5.jar
/libext/firebirdsql-full.jar
/libext/firebirdsql.jar
/libext/gis-shape.jar
/libext/hsqldb.jar
/libext/ifxjdbc.jar
/libext/javadbf.jar
/libext/jconn2.jar
/libext/js.jar
/libext/jt400.jar
/libext/jtds-1.1.jar
/libext/jxl.jar
/libext/ktable.jar
/libext/log4j-1.2.8.jar
/libext/mail.jar
/libext/mysql-connector-java-3.1.7-bin.jar
/libext/ojdbc14.jar
/libext/orai18n.jar
/libext/pg74.215.jdbc3.jar
/libext/edbc.jar

(注意 :下面这个包被遗漏了,要加上。原始位置kettle根目录/libswt/win32/swt.jar)
/libswt/win32/swt.jar 

、         编译成功后,准备运行

为使程序不必登陆就可以运行,需要设置环境署文件:kettle.properties,位置在用户目录里,一般在 /Documents and Settings/用户/.kettle/,主要内容如下:

KETTLE_REPOSITORY=kettle@m80

KETTLE_USER=admin

KETTLE_PASSWORD=passwd

、         好了,现在可以运行一下了,看看数据是不是已经拷贝到目标表了。

以下是运行时的控制台信息输出:



下面是自动生成的Transformation :



以下为修改后的程序源码:


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

  1. package name.chq.test;

  2.  

  3. import java.io.DataOutputStream;

  4. import java.io.File;

  5. import java.io.FileOutputStream;

  6.  

  7. import be.ibridge.kettle.core.Const;

  8. import be.ibridge.kettle.core.LogWriter;

  9. import be.ibridge.kettle.core.NotePadMeta;

  10. import be.ibridge.kettle.core.database.Database;

  11. import be.ibridge.kettle.core.database.DatabaseMeta;

  12. import be.ibridge.kettle.core.exception.KettleException;

  13. import be.ibridge.kettle.core.util.EnvUtil;

  14. import be.ibridge.kettle.trans.StepLoader;

  15. import be.ibridge.kettle.trans.Trans;

  16. import be.ibridge.kettle.trans.TransHopMeta;

  17. import be.ibridge.kettle.trans.TransMeta;

  18. import be.ibridge.kettle.trans.step.StepMeta;

  19. import be.ibridge.kettle.trans.step.StepMetaInterface;

  20. import be.ibridge.kettle.trans.step.selectvalues.SelectValuesMeta;

  21. import be.ibridge.kettle.trans.step.tableinput.TableInputMeta;

  22. import be.ibridge.kettle.trans.step.tableoutput.TableOutputMeta;

  23.  

  24.  

  25. //这个包被遗漏了,原始位置kettle根目录/libswt/win32/swt.jar

  26. //add by chq([link=http://www.chq.name]www.chq.name[/link]) on  2006.07.20

  27. //import org.eclipse.swt.dnd.Transfer; 

  28.  

  29. /**

  30.  * Class created to demonstrate the creation of transformations on-the-fly.

  31.  * 

  32.  * @author Matt

  33.  * 

  34.  */

  35. public class TransBuilder

  36. {

  37.     public static final String[] databasesXML = {

  38.         "<?xml version=/"1.0/" encoding=/"UTF-8/"?>" +

  39.         "<connection>" +

  40.           "<name>target</name>" +

  41.           "<server>192.168.17.35</server>" +

  42.           "<type>ORACLE</type>" +

  43.                      "<access>Native</access>" +

  44.                      "<database>test1</database>" +

  45.                      "<port>1521</port>" +

  46.                      "<username>testuser</username>" +

  47.                      "<password>pwd</password>" +

  48.                      "<servername/>" +

  49.                      "<data_tablespace/>" +

  50.                      "<index_tablespace/>" +

  51.                      "<attributes>" +

  52.                        "<attribute><code>EXTRA_OPTION_MYSQL.defaultFetchSize</code><attribute>500</attribute></attribute>" +

  53.                        "<attribute><code>EXTRA_OPTION_MYSQL.useCursorFetch</code><attribute>true</attribute></attribute>" +

  54.                           "<attribute><code>PORT_NUMBER</code><attribute>1521</attribute></attribute>" +

  55.                             "</attributes>" +

  56.                        "</connection>" ,

  57.          

  58.         "<?xml version=/"1.0/" encoding=/"UTF-8/"?>" +

  59.                          "<connection>" +

  60.                                 "<name>source</name>" +

  61.                                 "<server>192.168.16.12</server>" +

  62.                                 "<type>ORACLE</type>" +

  63.                                 "<access>Native</access>" +

  64.                                 "<database>test2</database>" +

  65.                                 "<port>1521</port>" +

  66.                                 "<username>testuser</username>" +

  67.                                 "<password>pwd2</password>" +

  68.                                 "<servername/>" +

  69.                                 "<data_tablespace/>" +

  70.                                 "<index_tablespace/>" +

  71.                                 "<attributes>" +

  72.                                     "<attribute><code>EXTRA_OPTION_MYSQL.defaultFetchSize</code><attribute>500</attribute></attribute>" +

  73.                                     "<attribute><code>EXTRA_OPTION_MYSQL.useCursorFetch</code><attribute>true</attribute></attribute>" +

  74.                                        "<attribute><code>PORT_NUMBER</code><attribute>1521</attribute></attribute>" +

  75.                                 "</attributes>" +

  76.                          "</connection>" 

  77.     };

  78.  

  79.     /**

  80.      * Creates a new Transformation using input parameters such as the tablename to read from.

  81.      * @param transformationName The name of the transformation

  82.      * @param sourceDatabaseName The name of the database to read from

  83.      * @param sourceTableName The name of the table to read from

  84.      * @param sourceFields The field names we want to read from the source table

  85.      * @param targetDatabaseName The name of the target database

  86.      * @param targetTableName The name of the target table we want to write to

  87.      * @param targetFields The names of the fields in the target table (same number of fields as sourceFields)

  88.      * @return A new transformation

  89.      * @throws KettleException In the rare case something goes wrong

  90.      */

  91.     public static final TransMeta buildCopyTable(

  92.        String transformationName,String sourceDatabaseName, String sourceTableName, 

  93.        String[] sourceFields, String targetDatabaseName, String targetTableName, 

  94.        String[] targetFields)

  95.       throws KettleException

  96.     {

  97.         LogWriter log = LogWriter.getInstance();

  98.         EnvUtil.environmentInit();

  99.         try

  100.         {

  101.             //

  102.             // Create a new transformation...

  103.             //

  104.             TransMeta transMeta = new TransMeta();

  105.             transMeta.setName(transformationName);

  106.             

  107.             // Add the database connections

  108.  

  109.             for (int i=0;i<databasesXML.length;i++)

  110.             {

  111.                 DatabaseMeta databaseMeta = new DatabaseMeta(databasesXML[i]);

  112.                 transMeta.addDatabase(databaseMeta);

  113.             }

  114.             

  115.             DatabaseMeta sourceDBInfo = transMeta.findDatabase(sourceDatabaseName);

  116.             DatabaseMeta targetDBInfo = transMeta.findDatabase(targetDatabaseName);

  117.  

  118.             

  119.             //

  120.             // Add a note

  121.             //

  122.             String note = "Reads information from table [" + sourceTableName+ "] on database [" 

  123.                             + sourceDBInfo + "]" + Const.CR;

  124.             note += "After that, it writes the information to table [" + targetTableName + "] on database [" 

  125.                             + targetDBInfo + "]";

  126.             NotePadMeta ni = new NotePadMeta(note, 150, 10, -1, -1);

  127.             transMeta.addNote(ni);

  128.  

  129.             // 

  130.             // create the source step...

  131.             //

  132.             String fromstepname = "read from [" + sourceTableName + "]";

  133.             TableInputMeta tii = new TableInputMeta();

  134.             tii.setDatabaseMeta(sourceDBInfo);

  135.             String selectSQL = "SELECT "+Const.CR;

  136.             for (int i=0;i<sourceFields.length;i++)

  137.             {

  138.             /* modi by chq(www.chq.name): use * to replace the fields,经分析,以下语句可以处理‘*‘ */

  139.                 if (i>0) 

  140.                      selectSQL+=", "

  141.                 else selectSQL+="  ";

  142.                

  143.                 selectSQL+=sourceFields[i]+Const.CR;

  144.             }

  145.             selectSQL+="FROM "+sourceTableName;

  146.             tii.setSQL(selectSQL);

  147.  

  148.             StepLoader steploader = StepLoader.getInstance();

  149.  

  150.             String fromstepid = steploader.getStepPluginID(tii);

  151.             StepMeta fromstep = new StepMeta(log, fromstepid, fromstepname, (StepMetaInterface) tii);

  152.             fromstep.setLocation(150, 100);

  153.             fromstep.setDraw(true);

  154.             fromstep.setDescription("Reads information from table [" + sourceTableName 

  155.                                      + "] on database [" + sourceDBInfo + "]");

  156.             transMeta.addStep(fromstep);

  157.  

  158.             //

  159.             // add logic to rename fields

  160.             // Use metadata logic in SelectValues, use SelectValueInfo...

  161.             //

  162.             /* 不必改名或映射 add by chq(www.chq.name) on 2006.07.20

  163.             SelectValuesMeta svi = new SelectValuesMeta();

  164.             svi.allocate(0, 0, sourceFields.length);

  165.             for (int i = 0; i < sourceFields.length; i++)

  166.             {

  167.                 svi.getMetaName()[i] = sourceFields[i];

  168.                 svi.getMetaRename()[i] = targetFields[i];

  169.             }

  170.  

  171.             String selstepname = "Rename field names";

  172.             String selstepid = steploader.getStepPluginID(svi);

  173.             StepMeta selstep = new StepMeta(log, selstepid, selstepname, (StepMetaInterface) svi);

  174.             selstep.setLocation(350, 100);

  175.             selstep.setDraw(true);

  176.             selstep.setDescription("Rename field names");

  177.             transMeta.addStep(selstep);

  178.  

  179.             TransHopMeta shi = new TransHopMeta(fromstep, selstep);

  180.             transMeta.addTransHop(shi);

  181.             fromstep = selstep; //设定了新的起点 by chq([link=http://www.chq.name]www.chq.name[/link]) on 2006.07.20

  182.             */

  183.             // 

  184.             // Create the target step...

  185.             //

  186.             //

  187.             // Add the TableOutputMeta step...

  188.             //

  189.             String tostepname = "write to [" + targetTableName + "]";

  190.             TableOutputMeta toi = new TableOutputMeta();

  191.             toi.setDatabase(targetDBInfo);

  192.             toi.setTablename(targetTableName);

  193.             toi.setCommitSize(200);

  194.             toi.setTruncateTable(true);

  195.  

  196.             String tostepid = steploader.getStepPluginID(toi);

  197.             StepMeta tostep = new StepMeta(log, tostepid, tostepname, (StepMetaInterface) toi);

  198.             tostep.setLocation(550, 100);

  199.             tostep.setDraw(true);

  200.             tostep.setDescription("Write information to table [" + targetTableName + "] on database [" + targetDBInfo + "]");

  201.             transMeta.addStep(tostep);

  202.  

  203.             //

  204.             // Add a hop between the two steps...

  205.             //

  206.             TransHopMeta hi = new TransHopMeta(fromstep, tostep);

  207.             transMeta.addTransHop(hi);

  208.  

  209.             // OK, if we're still here: overwrite the current transformation...

  210.             return transMeta;

  211.         }

  212.         catch (Exception e)

  213.         {

  214.             throw new KettleException("An unexpected error occurred creating the new transformation", e);

  215.         }

  216.     }

  217.  

  218.     /**

  219.      * 1) create a new transformation

  220.      * 2) save the transformation as XML file

  221.      * 3) generate the SQL for the target table

  222.      * 4) Execute the transformation

  223.      * 5) drop the target table to make this program repeatable

  224.      * 

  225.      * @param args

  226.      */

  227.     public static void main(String[] args) throws Exception

  228.     {

  229.        EnvUtil.environmentInit();

  230.         // Init the logging...

  231.         LogWriter log = LogWriter.getInstance("TransBuilder.log"true, LogWriter.LOG_LEVEL_DETAILED);

  232.         

  233.         // Load the Kettle steps & plugins 

  234.         StepLoader stloader = StepLoader.getInstance();

  235.         if (!stloader.read())

  236.         {

  237.             log.logError("TransBuilder",  "Error loading Kettle steps & plugins... stopping now!");

  238.             return;

  239.         }

  240.         

  241.         // The parameters we want, optionally this can be 

  242.         String fileName = "NewTrans.xml";

  243.         String transformationName = "Test Transformation";

  244.         String sourceDatabaseName = "source";

  245.         String sourceTableName = "testuser.source_table";

  246.         String sourceFields[] = { 

  247.                "*" 

  248.                };

  249.  

  250.         String targetDatabaseName = "target";

  251.         String targetTableName = "testuser.target_table";

  252.         String targetFields[] = { 

  253.                "*"

  254.                };

  255.  

  256.         

  257.         // Generate the transformation.

  258.         TransMeta transMeta = TransBuilder.buildCopyTable(

  259.                 transformationName,

  260.                 sourceDatabaseName,

  261.                 sourceTableName,

  262.                 sourceFields,

  263.                 targetDatabaseName,

  264.                 targetTableName,

  265.                 targetFields

  266.                 );

  267.         

  268.         // Save it as a file:

  269.         String xml = transMeta.getXML();

  270.         DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File(fileName)));

  271.         dos.write(xml.getBytes("UTF-8"));

  272.         dos.close();

  273.         System.out.println("Saved transformation to file: "+fileName);

  274.  

  275.         // OK, What's the SQL we need to execute to generate the target table?

  276.         String sql = transMeta.getSQLStatementsString();

  277.         

  278.         // Execute the SQL on the target table:

  279.         Database targetDatabase = new Database(transMeta.findDatabase(targetDatabaseName));

  280.         targetDatabase.connect();

  281.         targetDatabase.execStatements(sql);

  282.         

  283.         // Now execute the transformation...

  284.         Trans trans = new Trans(log, transMeta);

  285.         trans.execute(null);

  286.         trans.waitUntilFinished();

  287.         

  288.         // For testing/repeatability, we drop the target table again

  289.         /* modi by chq([link=http://www.chq.name]www.chq.name[/link]) on  2006.07.20 不必删表

  290.         //targetDatabase.execStatement("drop table "+targetTableName);

  291.         targetDatabase.disconnect();

  292.     }
  293.  

文章三:Kattle JAVA API

http://wiki.pentaho.com/display/EAI/Pentaho+Data+Integration+-+Java+API+Examples

KETTLE JAVA API   http://kettle.pentaho.org/downloads/api.php

Program your own Kettle transformation

The example described below performs the following actions:

  1. create a new transformation
  2. save the transformation as XML file
  3. generate the SQL for the target table
  4. Execute the transformation
  5. drop the target table to make this program repeatable

The complete source code for the example is distributed in the distribution zip file. You can find this file in the downloads section. (Kettle version 2.1.3 or higher)

After unzipping this file, you can find the source code in the 揟ransBuilder.java� file in the 揺xtra� directory.

The Kettle Java API for Kettle is found here: Kettle Java API

// Generate the transformation.
TransMeta transMeta = TransBuilder.buildCopyTable(
transformationName,
sourceDatabaseName,
sourceTableName,
sourceFields,
targetDatabaseName,
targetTableName,
targetFields
);

// Save it as a file:
String xml = transMeta.getXML();
DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File(fileName)));
dos.write(xml.getBytes("UTF-8"));
dos.close();
System.out.println("Saved transformation to file: "+fileName);

// OK, What's the SQL we need to execute to generate the target table?
String sql = transMeta.getSQLStatementsString();

// Execute the SQL on the target table:
Database targetDatabase = new Database(transMeta.findDatabase(targetDatabaseName));
targetDatabase.connect();
targetDatabase.execStatements(sql);

// Now execute the transformation...
Trans trans = new Trans(log, transMeta);
trans.execute(null);
trans.waitUntilFinished();

// For testing/repeatability, we drop the target table again
targetDatabase.execStatement("drop table "+targetTableName);
targetDatabase.disconnect();

Below is the source code for the method that creates the transformation:

/**
* Creates a new Transformation using input parameters such as the tablename to read from.
* @param transformationName The name of the transformation
* @param sourceDatabaseName The name of the database to read from
* @param sourceTableName The name of the table to read from
* @param sourceFields The field names we want to read from the source table
* @param targetDatabaseName The name of the target database
* @param targetTableName The name of the target table we want to write to
* @param targetFields The names of the fields in the target table (same number of fields as sourceFields)
* @return A new transformation metadata object
* @throws KettleException In the rare case something goes wrong
*/

public static final TransMeta buildCopyTable(
String transformationName, 
String sourceDatabaseName, 
String sourceTableName, 
String[] sourceFields, 
String targetDatabaseName, 
String targetTableName, 
String[] targetFields) throws KettleException
{

LogWriter log = LogWriter.getInstance();

try
{

//
// Create a new transformation...
//
TransMeta transMeta = new TransMeta();
transMeta.setName(transformationName);

// Add the database connections
for (int i=0;i<databasesXML.length;i++)
{
DatabaseMeta databaseMeta = new DatabaseMeta(databasesXML[i]);
transMeta.addDatabase(databaseMeta);
}

DatabaseMeta sourceDBInfo = transMeta.findDatabase(sourceDatabaseName);
DatabaseMeta targetDBInfo = transMeta.findDatabase(targetDatabaseName);

//
// Add a note
//

String note = "Reads information from table [" + sourceTableName+ "] on database [" + sourceDBInfo + "]" + Const.CR;
note += "After that, it writes the information to table [" + targetTableName + "] on database [" + targetDBInfo + "]";
NotePadMeta ni = new NotePadMeta(note, 150, 10, -1, -1);
transMeta.addNote(ni);

// 
// create the source step...
//

String fromstepname = "read from [" + sourceTableName + "]";
TableInputMeta tii = new TableInputMeta();
tii.setDatabaseMeta(sourceDBInfo);
String selectSQL = "SELECT "+Const.CR;
for (int i=0;i<sourceFields.length;i++)
{
if (i>0) selectSQL+=", "; else selectSQL+=" ";
selectSQL+=sourceFields[i]+Const.CR;
}
selectSQL+="FROM "+sourceTableName;
tii.setSQL(selectSQL);

StepLoader steploader = StepLoader.getInstance();

String fromstepid = steploader.getStepPluginID(tii);
StepMeta fromstep = new StepMeta(log, fromstepid, fromstepname, (StepMetaInterface) tii);
fromstep.setLocation(150, 100);
fromstep.setDraw(true);
fromstep.setDescription("Reads information from table [" + sourceTableName + "] on database [" + sourceDBInfo + "]");
transMeta.addStep(fromstep);

//
// add logic to rename fields
// Use metadata logic in SelectValues, use SelectValueInfo...
//

SelectValuesMeta svi = new SelectValuesMeta();
svi.allocate(0, 0, sourceFields.length);
for (int i = 0; i < sourceFields.length; i++)
{

svi.getMetaName()[i] = sourceFields[i];
svi.getMetaRename()[i] = targetFields[i];

}

String selstepname = "Rename field names";
String selstepid = steploader.getStepPluginID(svi);
StepMeta selstep = new StepMeta(log, selstepid, selstepname, (StepMetaInterface) svi);
selstep.setLocation(350, 100);
selstep.setDraw(true);
selstep.setDescription("Rename field names");
transMeta.addStep(selstep);

TransHopMeta shi = new TransHopMeta(fromstep, selstep);
transMeta.addTransHop(shi);
fromstep = selstep;

// 
// Create the target step...
//

//
// Add the TableOutputMeta step...
//

String tostepname = "write to [" + targetTableName + "]";
TableOutputMeta toi = new TableOutputMeta();
toi.setDatabase(targetDBInfo);
toi.setTablename(targetTableName);
toi.setCommitSize(200);
toi.setTruncateTable(true);

String tostepid = steploader.getStepPluginID(toi);
StepMeta tostep = new StepMeta(log, tostepid, tostepname, (StepMetaInterface) toi);
tostep.setLocation(550, 100);

tostep.setDraw(true);
tostep.setDescription("Write information to table [" + targetTableName + "] on database [" + targetDBInfo + "]");
transMeta.addStep(tostep);

//
// Add a hop between the two steps...
//

TransHopMeta hi = new TransHopMeta(fromstep, tostep);
transMeta.addTransHop(hi);

// The transformation is complete, return it...
return transMeta;
}
catch (Exception e)
{

throw new KettleException("An unexpected error occurred creating the new transformation", e);

}

}

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 2020年你最需要掌握的11种编程语言

    编程语言是程序员们的老朋友,据统计,世界上一共有600多种编程语言,那么最常用的,我们最应该掌握的都有哪些?根据openPR上发表的《编程语言全球市场调研报告及2018-2023预测》,推荐程序员掌握的编程语言有以下11种(按字母顺序排列)。C/C++根据 TIOBE 2020年5月的报告,C…...

    2024/4/24 9:16:17
  2. 〖教程〗Ladon以指定用户权限运行程序或命令

    前言 Ladon内置Runas允许用户用其他权限运行指定的工具和程序。系统自带Runas命令需要交互式登陆,在webshell或不支持交互式的shell下使用麻烦。而Ladon的Runas则完美解决了以上问题,支持非交互式模拟登陆指定用户运行程序或命令。 应用场景 1.本机用户密码验证(权限不够读不…...

    2024/4/24 9:16:16
  3. 说给男朋友听的源码解析【OkHttp】第一讲

    简介:大三学生党一枚!主攻Android开发,对于Web和后端均有了解。 个人语录:取乎其上,得乎其中,取乎其中,得乎其下,以顶级态度写好一篇的博客。 前言:OkHttp源码是面试中常问的,在腾讯二面中,被面试官追着问Okhttp的原理,当时只是面试前看了几篇Okhttp的分析博客,自…...

    2024/4/24 9:16:15
  4. 10.线程池

    感谢秦疆老师的JUC并发编程视频,更多了解哔哩哔哩搜索【狂神说Java】。 本文内容源于秦疆老师的JUC并发编程视频教程。给狂神推荐,点赞吧!线程池 线程池:三大方法、7大参数、4种拒绝策略池化技术程序的运行,本质:占用系统的资源!优化资源的使用!=》池化技术 线程池、连…...

    2024/4/24 9:16:14
  5. 单链表-创建

    单链表的创建 参考这位大神的文章 #include<stdio.h> #include<stdlib.h> typedef struct LNode{struct LNode *next;int data; }LNode; LNode *HeadCreateList(int len) {LNode *L = (LNode*)malloc(sizeof(LNode)); //创建一个头结点LNode *temp = L;//声明一个…...

    2024/4/24 9:16:13
  6. springboot使用脚本启动

    电脑性能太低,负荷过大,未避免电脑卡顿,可以使用脚本直接运行后端项目 步骤如下: 1.打开项目,点击file选择project structure 2.选中项目模块,点击OK3.点击Build>build-artifacts>弹出一个小标签>>选择当前项目的jar >rebuild成功后,进入当前项目模块的路…...

    2024/4/24 9:16:15
  7. mysql 常用特殊操作

    常用特殊操作--修改已有字段名称alter table 表名 change 老字段名称 新名称 字段类型(需要修改的字段类型,如果字段类型不变,需要写入原类型,不能为空);alter table users_talbe change user_remark remark varchar(5000);--在已有表中添加字段alter table 表名 add 字段…...

    2024/5/2 22:11:14
  8. 递归与递推---------------飞行员兄弟

    飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有16个把手的冰箱。 已知每个把手可以处于以下两种状态之一:打开或关闭。 只有当所有把手都打开时,冰箱才会打开。 把手可以表示为一个4х4的矩阵,您可以改变任何一个位置[i,j]上把手的状态。 但是,这也会使得第i行和第j列上…...

    2024/4/24 9:16:13
  9. java选择排序

    package arrayPro; public class Demo_4 { public static void main(String[] args){ int a[]={2,54,5,78,4}; int replace=0;//交换 int bj=0;//比较 for(int i=0;i<a.length-1;i++){ for(int j=0;j<a.length-1;j++){ if(a[j]>a[j+1]){ int temp=a[j]; a[j]=a[j+1]…...

    2024/5/2 9:38:00
  10. SpringCloud alibaba Nacos简介以及服务注册和服务配置

    Nacos简介Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。 Nacos帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范…...

    2024/4/24 9:16:08
  11. 【C++养成计划】运算符&运算符优先级(Day4)

    写在前面:大家好!我是【AI 菌】,一枚爱弹吉他的程序员。我热爱AI、热爱分享、热爱开源! 这博客是我对学习的一点总结与记录。如果您也对 深度学习、机器视觉、算法、Python、C++ 感兴趣,可以关注我的动态,我们一起学习,一起进步~ 我的博客地址为:【AI 菌】的博客 我的G…...

    2024/4/24 9:16:10
  12. PostgreSQL向量计算插件——vops

    在主流的OLTP数据库中,PostgreSQL可以说是十分优秀了。同时在OLAP领域,pg的表现也是越来越好,例如支持并行计算,JIT编译等等。同时pg还有许多优秀的插件,例如列存插件、LLVM插件。这些使得pg具备了一定的OLAP能力,在OLAP领域,最常用的加速手段可以说就是列存储了,虽然目…...

    2024/4/19 12:09:48
  13. 安装图中要求做一个机房的招标文件,只要提交技术方面的内容,包括按要求选的品牌型号、价格等详细信息

    ...

    2024/5/2 9:19:55
  14. mysql 技巧整理

    基础语法进阶IFNULL(xxx,xx) : 如果XXX字段为null,转换为XX值like ‘_a’ : 模糊查询中只包含一个字符ESCAPE ~ : 转义符,可以直接用 ‘\’ 也可用ESCAPE ‘ ’...

    2024/5/2 19:52:21
  15. 淘宝店铺淡旺季 店铺活动 促销方案 增加销量 淘宝卖家如何因时运用促销策略

    对淘宝店铺来说,一年四季不可能天天都是旺销,总会有淡旺季,那么我们的店铺在参与官方的活动后,自己店铺的一些促销营销方案是十分必要的。如何合理运用促销策略是每个店铺都要面临的问题。 一、店铺活动目的 为什么店铺要做活动?我们的出发点一定是为了提高店铺的转化率,…...

    2024/5/2 21:34:15
  16. 【学习笔记】【计算机网络】第7章——网络安全

    7.1 网络安全问题概述 7.1.1 计算机网络面临的安全性威胁 (1)计算机网络上的通信面临以下两大类威胁:被动攻击和主动攻击。(2)被动攻击 指攻击者从网络上窃听他人的通信内容。通常把这类攻击称为截获。在被动攻击中,攻击者只是观察和分析某一个协议数据单元 PDU,以便了…...

    2024/5/2 13:43:01
  17. TD-LTE原理及其关键技术介绍

    1. 用户感知网络指标1.1 速率C:信道容量 B:带宽:信噪比C是数据速率的极限值,单位bit/s;B为信道带宽,单位Hz;S是信号功率(瓦),N是噪声功率(瓦)。当讨论信噪比时,常以分贝(dB)为单位。公式如下:SNR(信噪比,单位为dB)=10 lg(S/N)。1.2 时延网站/应用的所在机…...

    2024/4/16 22:22:21
  18. Greenplum 分布式数据库内核揭秘(下篇)

    ​点击查看Greenplum 分布式数据库内核揭秘(上篇) 1. 分布式执行器 现在有了分布式数据存储机制,也生成了分布式查询计划,下一步是如何在集群里执行分布式计划,最终返回结果给用户。 Greenplum 执行器相关概念 先看一个 SQL 例子及其计划: test=# CREATE TABLE students (i…...

    2024/5/2 8:41:42
  19. 学习Linux前需要了解的的琐碎知识点

    学习Linux前需要了解的琐碎知识点CMOS:CMOS是Complementary Metal Oxide Semiconductor(互补金属氧化物半导体)的缩写。它是指制造大规模集成电路芯片用的一种技术或用这种技术制造出来的芯片,是电脑主板上的一块可读写的RAM芯片。因为可读写的特性,所以在电脑主板上用来保…...

    2024/4/16 11:55:59
  20. 网关:Gateway是通往异世界的入口

    网关解释一 探索队员:网关(gateway)有各种不同的种类呢。探索队长:是啊。队员:话说,gateway这个词到底是什么意思啊?队长:在问别人之前呢……队员:我知道,我现在就查字典。唔,字典上说是墙上的像门一样的入口。队长:没错,入口的里面是什么呢?队员:里面?是什么呢…...

    2024/4/24 9:16:06

最新文章

  1. 使用 Langchain、Langfuse、Nemo-gaurdrails、RAGAs构建 RAG 管道并进行监控和评估

    原文地址:build-end-to-end-rag-pipeline-with-monitoring-and-evaluation-using-langchain-azure-ai-search 2024 年 4 月 21 日 介绍 使用现代的LLM框架,如Langchain或llamaindex,可以迅速搭建一个用于 RAG 的管道,通常只需编写大约5-6行代码。然而,若要构建一个适用于生…...

    2024/5/2 23:45:48
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. Mybatis--TypeHandler使用手册

    TypeHandler使用手册 场景&#xff1a;想保存user时 teacher自动转String &#xff0c;不想每次保存都要手动去转String&#xff1b;从DB查询出来时&#xff0c;也要自动帮我们转换成Java对象 Teacher Data public class User {private Integer id;private String name;priva…...

    2024/5/1 14:21:06
  4. 【中文视觉语言模型+本地部署 】23.08 阿里Qwen-VL:能对图片理解、定位物体、读取文字的视觉语言模型 (推理最低12G显存+)

    项目主页&#xff1a;https://github.com/QwenLM/Qwen-VL 通义前问网页在线使用——&#xff08;文本问答&#xff0c;图片理解&#xff0c;文档解析&#xff09;&#xff1a;https://tongyi.aliyun.com/qianwen/ 论文v3. : 一个全能的视觉语言模型 23.10 Qwen-VL: A Versatile…...

    2024/5/1 14:22:00
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/2 9:28:15
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57