很多做数据仓库或者是报表开发的同学估计都有过这样的经历业务方一句“要一份今年截止到昨天的日报”听起来很简单结果打开SQL编辑器一看日期条件的处理就把人折腾得够呛。尤其是做月度、季度、年度的同比环比分析时如果底层没有一张完整的日期维度表仅仅靠DATE_ADD、DATE_SUB这种函数在SQL里临时拼日期不仅代码冗长而且遇到跨年、跨月、周数计算的时候特别容易出错。我之前在搭建某套离线数仓的时候就被这个日期维度的问题困扰过一阵子。最初想过直接从网上找个SQL脚本生成但Hive里的实现方式跟传统关系型数据库不太一样而且字段设计未必符合自己业务的需求也想过用Python写个脚本直接生成CSV然后load进Hive但当时跑批任务的调度平台对Python脚本的依赖管理比较麻烦最后还是回归到了公司里最常用的ETL工具——KettlePentaho Data Integration简称PDI。用Kettle的好处是它可以被调度平台直接托管数据加工过程可视化后续如果需要调整字段或者补数据改起来也直观。这篇就记录一下我用Kettle生成2019年全年日期维度数据并最终落到Hive表里的完整过程。内容上我会从日期字段的设计思路讲起再到Kettle转换的具体配置最后是写入Hive的一些坑和解决方式。如果你也在做数仓建模或者手头刚好需要一张日期维度表这篇应该能帮你省下不少时间。1. 日期维度表为什么要自己生成字段设计想清楚再动手日期维度表在数仓里属于最基础的维度表之一它本身不承载任何业务事实但几乎所有的事实表分析都离不开它。没有这张表你面对“本月环比上月”“今年累计比去年同期”这种需求时就只能把日期逻辑硬编码在SQL里日后再维护那就是一场灾难。反过来有了这张表分析脚本就可以通过JOIN维度表拿到所有的日期属性SQL简洁人也轻松。但日期维度表不是随便拉个日期列表就完事了。字段怎么设计取决于你的业务要怎么做分析。我这次做的是2019年全年的数据业务侧固定的分析口径包括按自然日统计、按周统计、按月统计、按季度统计还涉及到节假日主要是周末的标记。所以我设计的字段大致有下面几类字段分类字段名示例说明日期主键date_id20190101格式为yyyymmdd的整型方便直接做等值关联日期描述date_desc2019-01-01标准日期格式方便人读时间属性year, month, day, quarter2019, 1, 1, 1年、月、日、季度分开存储周相关week_of_year, week_of_month1, 1年中第几周、月中第几周星期属性day_of_week, day_name2, 星期二ISO标准周一为一周开始同时输出中文名特殊标记is_weekend0或1是否周末日常统计里用得非常多这里要特别说一句date_id我故意用整数而不是字符串有个很重要的原因——Hive里如果用字符串直接作为分区字段分区裁剪效率没问题但如果你的日期主键要参与大量JOIN整数类型的比较在底层执行效率上通常会比字符串好那么一点而且很多前端报表工具对整型日期支持得也更好。当然如果你的团队习惯用字符串2019-01-01也完全可以这个没有绝对的对错关键是全公司口径要统一。我之所以要写这篇文章分享是因为当时我在网上找了很多现成的Kettle日期生成方案发现大多数文章只讲了“怎么用Kettle生成连续日期”但“生成的日期数据如何落到Hive表”这个关键环节基本是一笔带过或者给的方案还是传统JDBC插入的方式。实际上Kettle写Hive尤其是通过文件方式加载细节比想象中要多。所以这篇文章我会把从字段设计到Kettle转换配置再到Hive写入的完整链路讲透。2. Kettle生成日期序列的两条技术路线为什么我选了“生成记录JavaScript”用Kettle生成连续日期其实有几种思路。我大致梳理一下供你根据自身情况选择。2.1 方案一数据库源生成你可以用一个“表输入”控件去连接一个传统的MySQL或者PostgreSQL数据库然后利用数据库自己的日期函数生成连续日期。比如MySQL里可以这样写SELECT DATE_ADD(2019-01-01, INTERVAL xc.day_num DAY) AS date_value FROM ( SELECT rn : rn 1 AS day_num FROM information_schema.COLUMNS CROSS JOIN (SELECT rn : -1) r LIMIT 365 ) xc这种方法的特点是如果你公司数据库资源比较空闲而且你已经有了一个现成的数据源连接那么它的执行效率非常高代码也简洁。但问题在于它强依赖数据库语法不同数据库的写法千差万别而且在离线数仓的场景下为了生成个日期还去查一次业务库总觉得有点小题大做。更重要的是后续如果要扩展字段比如动态计算某一天是当月第几个工作日SQL写起来会很痛苦。2.2 方案二Kettle本地生成生成记录JavaScript这是我自己最终采用的方案。核心就是用一个“生成记录”控件配合一个JavaScript代码段在Kettle内部完成从1到365的循环动态构造出每一天的日期。之所以选这个方案首先是它不依赖任何外部数据库完全在Kettle的JVM里完成计算跑批时哪怕业务库连接出问题也不会影响这个转换其次JavaScript控件里你可以写任意逻辑日期加一天、算季度、算星期几都非常灵活最后也是最重要的整个过程是可视化配置的后面的人接手维护时打开转换就能看懂它到底做了什么比看一段晦涩的SQL要友好很多。2.3 两个方案的核心区别对比为了让你更直观地看到差异我做了一个简单的对比表对比项数据库源生成生成记录JavaScript外部依赖依赖可用数据库连接无本地运行灵活性低SQL扩展字段较麻烦高可写任意逻辑跨数据库迁移差不同数据库语法不同好Kettle通用维护成本中SQL里藏着业务逻辑低逻辑一目了然性能快数据库内部计算足够用365行数据毫秒级说句实话生成365行日期数据任何方案性能都不是瓶颈真正影响体验的是“扩展字段”的便利性和“无外部依赖”的健壮性。Kettle生成记录JavaScript正是因为在这两点上胜出才成了我最常用的方案。下面我就把完整的实操步骤一步步拆开来讲。3. 实操第一步用“生成记录”控件配合JavaScript跑出2019年每一天这部分是这篇博文的核心我会尽量把Kettle里每一个控件、每一个关键配置都写清楚。你跟着操作基本不会卡壳。3.1 新建转换并拉入“生成记录”控件打开Kettle我用的版本是8.39.x的界面也基本一样新建一个转换名字就叫dim_date_2019_generate。在左侧“核心对象”树中找到“输入”分类拖一个“生成记录”控件到画布上。双击打开配置界面。“生成记录”这个控件的原理很简单它会在运行时根据你设置的“限制”数量产生指定行数的数据。每一行默认会带有一个自增的序号字段字段名默认叫valuenr从0开始递增。我们这里要把2019年全年的日期都生成出来所以“限制”设置为365valuenr的值在运行时就是0到364。这里有个小细节如果你正好赶上闰年生成数据记得把“限制”改成366因为2月份多了一天。虽然这次生成2019年用不到但建议你把这个值设计成一个可供修改的“命名参数”比如叫total_days这样以后生成其他年份就不用改转换结构了。3.2 插入JavaScript代码控件写日期循环逻辑画布上继续拖一个“脚本”分类下的“JavaScript代码”控件用一条“跳”连接把“生成记录”和它连起来生成记录 - JavaScript代码。双击JavaScript控件在“JavaScript代码”编辑区里写如下代码// 定义起始日期2019-01-01 var startDate new Date(2019, 0, 1); var currentDate new Date(startDate); // 利用生成记录产生的序号valuenr作为偏移量 var offset parseInt(valuenr); currentDate.setDate(startDate.getDate() offset); // 输出标准日期格式 yyyy-MM-dd var year currentDate.getFullYear(); var month currentDate.getMonth() 1; var day currentDate.getDate(); // 补零函数 function pad(n) { return n 10 ? 0 n : n; } var dateDesc year - pad(month) - pad(day); var dateId parseInt(year * 10000 month * 100 day); // 赋值给输出字段类型注意在后面步骤指定 var out_date_id dateId; var out_date_desc dateDesc; var out_year year; var out_month month; var out_day day; var out_quarter Math.floor((month - 1) / 3) 1;写完代码之后切到“字段”选项卡你需要手动添加这些输出字段因为Kettle不会自动识别JavaScript里新定义的变量。我这里的经验是字段名用带前缀的方式避免跟内部的字段冲突。我当时添加的字段列表如下字段名类型长度说明out_date_idInteger8日期主键如20190101out_date_descString10日期描述如2019-01-01out_yearInteger4年份out_monthInteger2月份out_dayInteger2日out_quarterInteger1季度这里有个常见的坑JavaScript里计算出来的out_date_id我用parseInt强行转成了整数但如果JavaScript端没有正确地做类型转换Kettle里仍然可能把字段当成字符串处理。所以最稳妥的做法是在JavaScript控件的“字段”选项卡里直接把类型指定为Integer让Kettle自己完成类型强制转换。3.3 运行预览检查输出数据配置完毕之后点击控件上的“运行”按钮选择“预览”可以看到输出的100行数据。重点检查以下几项out_date_id是整数且第一行是20190101out_date_desc是标准的2019-01-01格式out_quarter前三个月是14到6月是2以此类推我第一版跑出来的时候发现月份和日期没有补零导致out_date_desc在1月出现了2019-1-1这种格式虽然人看没问题但后续如果想用它跟其他系统的字符串日期做关联就会出乱子。后来加了pad函数这个坑才算填平。3.4 为什么不用“增加序列”控件直接生成日期可能你会问Kettle不是有个“增加序列”的功能吗有个“日期范围”控件好像也能生成日期是的Kettle有一个“生成记录”配合“计算器”控件的玩法先用生成记录跑出1到365再用计算器的DateAdd函数把2019-01-01加上N天。理论上这也是一条路。但我当时试过之后发现计算器控件做日期加减虽然简单可一旦需要在同一行里同时计算“星期几”“季度”“是否周末”你就得串联一连串的计算器步骤画布上全是节点看着就头疼。而JavaScript虽然写起来像“硬编码”但逻辑集中在一处想加字段就加代码想改逻辑就改代码维护的时候打开一个窗口就全搞定了。所以我的建议是如果你的日期逻辑比较简单用计算器无可厚非如果像我一样要做十几个衍生字段还是JavaScript更合适。4. 丰富日期属性季度、星期、周数、周末标记的计算与配置基础的日期跑出来之后还远远不够。从分析角度看季度、星期、周数、周末标记这些字段才是真正被高频使用的。这一节我重点讲怎么用JavaScript把这些“附加属性”一并算出来以及有些字段为什么必须在这里算好而不是等落到Hive里再用SQL处理。4.1 在同一个JavaScript里把衍生字段算齐我建议生成日期和衍生字段计算都放在同一个JavaScript控件里完成没必要拆成多个步骤。由于currentDate对象本身包含了完整的日期信息我们从它身上取星期、周数都非常方便。下面是我在原有代码基础上补充的完整字段计算逻辑// 星期几ISO标准1代表周一7代表周日 var dayOfWeek currentDate.getDay(); if (dayOfWeek 0) { dayOfWeek 7; // JS中getDay()返回0是周日 } // 中文星期名 var weekNames [周一, 周二, 周三, 周四, 周五, 周六, 周日]; var dayName weekNames[dayOfWeek - 1]; // 周末标记 var isWeekend (dayOfWeek 6 || dayOfWeek 7) ? 1 : 0; // 年中第几周简单算法以1月1日所在的周为第1周 var firstDayOfYear new Date(year, 0, 1); var dayOfYear Math.floor((currentDate - firstDayOfYear) / 86400000) 1; var weekOfYear Math.ceil(dayOfYear / 7); // 月中第几周以每月1日所在的周为第1周 var firstDayOfMonth new Date(year, month - 1, 1); var dayOfMonth currentDate.getDate(); var weekOfMonth Math.ceil((dayOfMonth firstDayOfMonth.getDay() - 1) / 7); var out_day_of_week dayOfWeek; var out_day_name dayName; var out_is_weekend isWeekend; var out_week_of_year weekOfYear; var out_week_of_month weekOfMonth;对应的字段选项卡里继续追加这些输出字段字段名类型长度说明out_day_of_weekInteger1星期几1-7周一为1out_day_nameString10中文星期名out_is_weekendInteger1是否周末1是0否out_week_of_yearInteger2年中第几周简化算法out_week_of_monthInteger2月中第几周简化算法4.2 关于周数的口径提醒这里最容易埋雷这里我想多啰嗦一句周数的问题因为它在实际报表中非常容易产生口径冲突。我上面的算法属于“自然周”的简化算法即1月1日所在的那一周就算第1周直接把dayOfYear / 7向上取整。这种算法的好处是简单、直观代码一眼能看懂坏处是它跟财务上常用的“ISO周”算法不完全一致。ISO 8601标准对“一年的第几周”有严格定义每周从周一开始每年的第1周必须包含该年的第一个周四。如果你所在的业务团队明确要求按ISO周统计那么上面的算法是不够的需要引入更复杂的判断逻辑比如计算当前周是否跨年。当时我们公司内部对“周”的口径相对宽松所以用了简化版。我的建议是在你写进代码之前先跟业务方确认清楚否则等报表上线后才发现两边周数对不上那才叫真正的返工。4.3 预览检查衍生字段配置好新字段之后再次点击预览重点看几个边界日期比如2019-01-01这天day_of_week应该是2周二day_name是“周二”week_of_year按简化算法是12019-12-31这天week_of_year是53。如果这些边界值符合预期那这一阶段就算调通了。5. 写入Hive为什么优先用“Hadoop File Output”而不是JDBC插入日期数据在Kettle里生成好之后最后一个关键步骤就是写入Hive。这一步的选型几乎是决定整个作业稳定性的分水岭。我见过不少同学在Kettle里拖一个“表输出”控件然后配一个Hive的连接试图直接通过JDBC往Hive表里逐条插入数据。这种方式不是不行只是在真实的生产环境里它有几个明显的劣势。5.1 直接JDBC插入为什么不够好性能差JDBC往Hive里写数据本质上是逐条提交INSERT语句Hive处理OLTP式的单条插入非常吃力跑365行数据可能还能忍但如果你以后要生成10年的日期数据3650行或者还要扩展成“日期小时”级别的维度表8760行/年单条插入会让整个任务跑得极其漫长。版本兼容坑Hive的JDBC驱动版本和Kettle自带的驱动版本经常对不上经常报Could not establish connection to host或者Required field client_protocol is unset!这类错误。排查起来非常头大。事务限制Hive本身对单条INSERT的事务支持就很弱如果中途失败了你根本不知道哪些数据已经写入、哪些没有补数的时候很容易重复。5.2 我推荐的方案本地生成CSV再用“Hadoop File Output”写入实际操作上我是先在Kettle里把日期数据输出成CSV文件或者直接输出到HDFS指定目录然后在Hive里通过外部表或者LOAD DATA的方式把文件导入目标表。Kettle里承担这个任务的核心控件有三种文本文件输出、Hadoop File Output、Hive表输出。我当时采用的是“文本文件输出”生成CSV文件然后用Hive的LOAD DATA INPATH命令加载。为什么不用Hadoop File Output因为当时Kettle里配HDFS连接还需要额外的插件配置而且一旦HDFS集群启用了Kerberos认证Kettle端配置起来会很麻烦。反而是先用文本文件输出到本地再把文件手动上传到HDFS上或者用调度平台执行一条hdfs dfs -put命令来得更稳当。我知道这个做法听起来“技术含量不高”但我要说的是ETL工具的核心使命是稳定、可控、可维护不是炫技。当时生产环境的Kettle跑批节点和HDFS之间的网络策略很严格Kettle直接访问HDFS经常超时反而是先把CSV落到本地再由后续的Hive加载脚本处理跑了一个多月没出过问题。5.3 目标Hive表结构设计在输出文件之前先设计好Hive目标表的结构。因为我们要做的是一张通用的日期维度表为了简化查询我建议直接用外部表并且把日期作为分区字段来管理。下面是我当时使用的建表语句注意字段类型和文件里字段顺序保持一致CREATE EXTERNAL TABLE IF NOT EXISTS dwd.dim_date ( date_id INT COMMENT 日期IDyyyymmdd, date_desc STRING COMMENT 日期描述yyyy-MM-dd, year INT COMMENT 年, month INT COMMENT 月, day INT COMMENT 日, quarter INT COMMENT 季度, day_of_week INT COMMENT 星期几 1-7, day_name STRING COMMENT 星期名称, week_of_year INT COMMENT 年中第几周, week_of_month INT COMMENT 月中第几周, is_weekend INT COMMENT 是否周末 1是0否 ) COMMENT 日期维度表 PARTITIONED BY (dt STRING COMMENT 分区格式yyyy-mm) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t STORED AS TEXTFILE;这里我对表做了一个非常关键的分区设计用dt字段按月分区。为什么要按月分区因为后面如果某一年的数据出错了我不想重修整个表的全量数据只需要ALTER TABLE ... DROP PARTITION (dt2019-01)然后重新加载1月份那几十行CSV就行。这种按月度分区在数仓里维护起来性价比是最高的。5.4 Kettle里“文本文件输出”控件的配置要点Kettle画布上继续拖一个“输出”分类下的“文本文件输出”控件接在JavaScript控件后面。双击打开配置我当初的设置如下文件名称/data/etl/dim_date_2019.txt扩展名留空因为我直接指定了带后缀的文件名分隔符\tTab键与建表语句里的FIELDS TERMINATED BY \t对应编码UTF-8格式默认为“空”即可Kettle会按系统默认来追加或覆盖我选择“截断文件”保证每次任务跑出的是全新文件不会跟上次的残留数据混在一起切到“字段”选项卡这里是最容易出错的地方。文本文件输出控件不会自动识别上游的字段你必须手动添加。而且添加的顺序必须跟Hive表里的字段顺序完全一致。我当时添加的顺序是out_date_id, out_date_desc, out_year, out_month, out_day, out_quarter, out_day_of_week, out_day_name, out_week_of_year, out_week_of_month, out_is_weekend。同时字段选项卡里每个字段的下方都可以设置“类型”“长度”“精度”。建议全部显式指定一下。我有一次偷懒没指定类型结果输出的CSV里整数都变成了1.0这种带小数的格式加载进Hive后通过接口查询出来的数据全都是浮点数又返工了一轮。这种低级错误一次就长记性了。5.5 头尾处理不要输出字段名加一行末尾空行无害文本文件输出控件的“内容”选项卡里有个“头部”选项默认是“无”。千万要保持“无”因为Hive的LOAD DATA不会帮你跳过CSV的表头如果你输出了字段名那第一行就会被当成真实数据加载进表里后面查询的时候看到第一行是date_id整个数据就脏了。“尾部”选项同样保持默认即可。Kettle会在文件末尾加一个空行这个空行加载进Hive的时候是会被自动忽略的所以不用管它。6. 加载进Hive及验证一条LOAD命令搞定但不能少了排查步骤CSV文件生成好了接下来的工作就是把它弄到HDFS上再用Hive命令加载。这个过程说简单也简单但里面有几个操作顺序和细节不按顺序来的话很容易出现“看似成功实则数据错位”的情况。6.1 把CSV文件上传到HDFS如果你是在Kettle所在的机器上直接生成的CSV那么可以通过命令行工具上传。假设文件路径是/data/etl/dim_date_2019.txt目标HDFS目录是/user/hive/warehouse/dwd.db/dim_date/dt2019命令如下hdfs dfs -mkdir -p /user/hive/warehouse/dwd.db/dim_date/dt2019 hdfs dfs -put -f /data/etl/dim_date_2019.txt /user/hive/warehouse/dwd.db/dim_date/dt2019/这里我特别说明一下如果你建的表是外部表并且指定了LOCATION指向某个HDFS目录那么在执行LOAD DATA之前其实可以直接把文件放入分区目录。不过我更推荐的方式是先把文件放到一个临时路径比如/tmp/dim_date_2019/再用LOAD DATA INPATH让Hive自己完成从临时目录到目标表的移动。这样可以避免因为手工操作目录结构不当而导致的表数据不可见问题。6.2 在Hive里执行LOAD DATA文件放到临时路径后进入Hive命令行执行LOAD DATA INPATH /tmp/dim_date_2019/dim_date_2019.txt INTO TABLE dwd.dim_date PARTITION (dt2019);这里要特别强调分区字段dt2019在LOAD DATA命令里必须指定Hive会根据这个分区值把数据放到对应的HDFS目录下。如果你的文件路径是中文或者含特殊字符建议先重命名成纯英文路径否则部分Hive版本对路径解析会有问题。6.3 验证逻辑不只是COUNT(*)那么简单很多同学加载完数据后习惯性地执行一个SELECT COUNT(*) FROM dim_date WHERE dt2019看到返回365行就觉得大功告成了。但说实话这个验证远远不够。我总结了一套自己的验证SQL供你参考-- 1.总数校验 SELECT COUNT(*) FROM dwd.dim_date WHERE dt 2019; -- 期望 365 -- 2.首尾日期校验 SELECT MIN(date_id), MAX(date_id) FROM dwd.dim_date WHERE dt 2019; -- 期望 20190101, 20191231 -- 3.星期分布校验全年周末应有104天或105天2019年约104天 SELECT is_weekend, COUNT(*) FROM dwd.dim_date WHERE dt 2019 GROUP BY is_weekend; -- 4.月度分布校验 SELECT month, COUNT(*) FROM dwd.dim_date WHERE dt 2019 GROUP BY month ORDER BY month; -- 期望1,3,5,7,8,10,12月各31天4,6,9,11月各30天2月28天 -- 5.类型校验确认date_id没有被转成浮点型 SELECT date_id, date_desc FROM dwd.dim_date WHERE dt 2019 AND date_id 20190101;第5条尤其关键。我之前碰过一次很奇葩的问题Kettle输出CSV默认把整数写成了带小数点的格式比如20190101.0文本文件在Hive里加载后因为Hive的隐式转换date_id变成了20190101.0的浮点显示下游用date_id 20190101去关联的时候怎么都关联不上。后来加了第5条这种等值查询测试才揪出这个隐性类型问题。所以请一定按照上面的SQL去逐条验证别偷懒。7. 跑批过程中遇到的三个经典问题与排查思路写到最后我把自己做这个Kettle转换真实踩过的、有代表性的三个问题列出来。这些问题在网上问Kettle相关群也经常有人遇到干脆一次性说清楚。7.1 问题一输出文件里多了很多空行现象生成的CSV文件里每两行真实数据之间就夹着一个空行加载进Hive后COUNT(*) 变成了730行。排查最终发现Kettle的“文本文件输出”控件默认会为每一行数据在末尾追加一个行结束符如果你的输入流里本身带了换行符就会出现重复换行。解决办法是在JavaScript代码的末尾不要给out_date_desc等字符串字段额外拼\n然后文本文件输出的“结束行”留空。7.2 问题二Hive查询出来中文星期名为乱码或NULL现象CSV在本地打开正常但在Hive里查day_name全部是NULL或者?。排查问题本质是字符集不一致。Kettle里文本文件输出控件设置UTF-8编码但Hive表创建时用的ROW FORMAT DELIMITED FIELDS TERMINATED BY \t没有显式指定字符集。Hive默认会在加载时按照你Session的字符集去解码如果你的Hive环境默认字符集不是UTF-8就会乱码。解决办法是建表时显式加上ROW FORMAT SERDE org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe WITH SERDEPROPERTIES (field.delim\t, serialization.encodingUTF-8)同时保证你在Hive CLI或Beeline里执行SET hive.exec.reducers.bytes.per.reducer之类的参数时终端编码也是UTF-8。大多数情况下加上serialization.encoding就能解决。7.3 问题三Kettle任务跑完但HDFS上没有文件现象Kettle转换成功结束日志也没有报错但指定的HDFS目录里没有新文件。排查这种情况多半发生在“文本文件输出”控件里配置的路径是本地文件系统路径而不是HDFS路径。Kettle的文本文件输出默认写本地如果你勾选了“Hadoop File Output”但这个控件没配好HDFS连接它会静默地失败。我当时的做法是确认Kettle的运行节点和HDFS NameNode之间的网络连通性并直接把Hadoop File Output控件里的hdfs://namenode:8020/路径写法替换成hdfs:///路径用本地NameNode配置这个问题就消失了。8. 扩展这个转换还能怎么改造成通用模板日期维度表这种任务最大的特点就是“一次性建设、重复使用”。但如果你下次需要生成2020年、2021年甚至未来5年的数据难道还要重新打开转换去改JavaScript里的硬编码日期吗那也太傻了。所以最后这部分我分享一下怎么把这个转换改造成可复用的“模板”。8.1 用命名参数控制日期范围Kettle的转换和作业都支持定义命名参数。你可以在转换属性里定义start_date和total_days两个参数然后在JavaScript里通过getVariable函数读取var startDateStr getVariable(start_date, 2019-01-01); var totalDays parseInt(getVariable(total_days, 365));这样一来每次调度时只要传入不同的参数值就能生成任意年份的数据不需要改动任何代码。调度平台在调用Kettle作业时也可以通过命令行传入参数pan.sh -file/path/to/dim_date_generate.ktr -param:start_date2020-01-01 -param:total_days3668.2 扩展“小时级别”日期维度如果你的业务需要做小时级分析在这个转换的基础上加一个“复制记录”和“JavaScript”步骤把每一天复制成24行然后依次给每行赋上hour字段0到23就能得到一张“日期小时”的维度表。这种做法比重新写一套逻辑省事得多。8.3 节假日数据的补充思路日期维度表里如果需要更完整的节假日春节、国庆、调休上班日等Kettle本身算不出来这块最好是由运营或行政部门提供一份节假日配置表然后通过Kettle的“记录关联合并记录”控件把节假日标志JOIN到日期表上。这样做的好处是日期主表和节假日配置表解耦以后每年更新节假日配置就行日期主表代码完全不用动。在这篇文章里我尽量把从字段设计、Kettle控件配置、写入Hive、验证数据到问题排查的完整链路都讲了一遍。说穿了用Kettle生成日期维度数据这件事本身不难难的是细节——字段类型对齐、分隔符一致、字符集统一、分区策略合理这些才是真正决定任务能不能长期稳定跑下去的关键。如果你在照着做的时候也遇到了什么奇怪的坑欢迎在评论里说出来大家一起排一排。