在本篇文章中,将探讨如何使用SQL Server Integration Services (SSIS)中的脚本组件来构建日期维度。这通常涉及到将一种特殊的日期格式(如朱利安日期)转换为常规日期格式,以便在数据仓库中使用。将通过一个实际的案例来展示这一过程,该案例涉及将JD Edwards事务数据从OLTP系统迁移到SQL Server数据库。
背景介绍:在这个项目中,公司决定关闭几条业务线,因此不再需要使用JD Edwards系统进行新事务处理。他们决定不续订JD Edwards的许可证,因此将失去对系统的访问权限。同时,他们还决定出售运行该系统的IBM iSeries硬件。工作是在SQL Server数据库上创建数据副本,并创建报告,以便他们在未来任何时候都能查看数据。SQL Server被选中,因为剩余的业务部门将继续在该服务器上维护数据库以支持持续运营。因此,有一个新数据仓库项目需要开发。
公司使用JD Edwards进行订单交易,因此需要构建的第一个部分是订单明细事实表和一些维度,如产品、客户和日期。到目前为止,它看起来像是一个相当典型的数据仓库项目。在订单明细表中有三个重要的日期。第一个是交易日期,第二个是发货日期,从会计角度来看,最重要的发票日期,这是收入确认的时间。
遇到的问题在于日期是以朱利安格式表示的。JD Edwards系统在用户界面和报告中很好地展示了正常日期,但系统底层的日期是以这种朱利安格式表示的。例如,1/1/2012表示为112001,2/1/2012表示为112032。前三位数字表示年份。2011年将是111。最后三位是一年中的天数。
决定,由于公司只需要过去四到五年的历史,日期维度不会很大,每年大约有365行,减去周末、节假日和任何没有活动的天数。将使用朱利安日期作为日期维度的键,并将其分配给整型数据类型以提高效率。
接下来是ETL过程中将朱利安日期转换为常规日期的部分,以便用所有现有日期和今年年底之前输入的新日期填充日期维度。
ETL基本转换过程如下:有一个OLE DB数据源,它查看SQL Server上存在的订单明细表,并查询日期的不重复列表。为了简单起见,上面看到的是OrderDetail表中检索的单个列。
接下来,向数据流中添加一个脚本组件。这样做时,可以选择使用脚本组件作为源、目的地或转换。在这种情况下,选择是转换。
现在在数据流上有一个脚本组件,并且要做的第一件事是将其连接到OLE DB源,以便它有输入。如果跳过这一步,将无法完全配置组件,如果尝试,将获得以下警告:
现在右键单击脚本组件并选择编辑。左侧窗格包含要配置的项目列表,主窗格是进行更改和设置的地方。所以对于第一项脚本,可以设置属性。以下属性以粗体显示,可以更改它们。有名称、描述和脚本语言。大多数项目使用C#,因此以下代码示例将使用C#。
现在可以看看下一项,也是需要配置的最后一项,即输入和输出项。在下面的图片中,已经展开了输入和输出。输入是所期望的,InvoiceDate。注意不能设置数据类型,因为组件知道该列的底层数据类型。在JD Edwards源数据库中,朱利安日期字段甚至可能是一个字符串,但在项目中将其作为整数使用,因为它将是日期维度的键,它将是一个索引,整数在索引中更有效。这也有道理,因为将字段作为整数递增正是期望的日期值,它在0-365的域内增加了一天。
还请注意,有一个输出(Output 0),但没有列。还应该知道,可以重命名输入和输出。如果这样做,它们将在脚本中更改。关键是,如果要更改它们,那么最好在编写脚本部分之前这样做。现在在这个例子中,想向输出添加两列,通过使用添加列按钮来实现这一点。点击并突出显示Output Columns文件夹后,此按钮将启用。
在下面的图片中,可以看到添加了两列,分别是JulianDate和RegularDate。希望Julian日期列包含源表中出现的原始朱利安日期,而常规日期将是常规日期格式(或至少可以通过常见内置函数修改的格式)。
现在可以通过选择脚本,左侧的第一项,进行一些编码。这样做时,将再次访问编辑脚本按钮,该按钮出现在上面的一个图片中。下面是一个示例,使用默认的输入和输出名称。
public override void Input0_ProcessInputRow(Input0Buffer Row)
{
String julianDate = Convert.ToString(Row.InvoiceDate);
DateTime dt_date;
if (julianDate.Length == 6)
{
try
{
string sDay = julianDate.Substring(3);
string sYear = "20" + julianDate.Substring(1, 2);
dt_date = DateTime.Now;
int iCurrentDay = Convert.ToInt16(sDay) - dt_date.DayOfYear;
dt_date = dt_date.AddDays(iCurrentDay);
dt_date = dt_date.AddYears(Convert.ToInt16(sYear) - dt_date.Year);
julianDate = Convert.ToString(dt_date.ToShortDateString());
Output0Buffer.AddRow();
Output0Buffer.RegularDate = Convert.ToDateTime(julianDate);
Output0Buffer.JulianDate = Row.InvoiceDate;
}
catch
{
}
}
}
代码的第一部分执行朱利安日期转换,这相当直接。在try块中的最后三行逐行创建输出,无需循环。注意Output0Buffer语句中Output0的名称使用。如果更改输入名称,方法名称将更改。如果更改输出名称,将必须适应这些语句,因此首先重命名。所以这些语句中的第一个为每个输入行添加一个输出行。因为添加了两个输出列,现在可以在第二个语句中输出常规日期,在第三个语句中输出原始朱利安日期。将为dayOfWeek、Year等添加额外的列。