本章内容
- 将文件暂存以便导入 Snowflake
- 从暂存文件加载数据到暂存表
- 将数据从暂存表合并到目标表
- 使用 SQL 转换数据
- 使用任务自动化数据管道
在本章中,你将学习如何构建你的第一个 Snowflake 数据管道。我们将从 CSV 文件中将数据导入 Snowflake,将原始数据转换成报告所需的目标数据模型,并自动化数据管道。由于我们希望初学者能够理解,所以本章不包括第 1 章中介绍的数据管道底层组件,如安全性、数据治理、软件工程或 DataOps,这些内容将在后续章节中详细讨论。为了让你更好地开始数据工程工作,我们将创建一个简单的数据管道,展示如何将文件数据导入 Snowflake 并进行转换,演示 Snowflake 的核心功能。
在本章结束时,我们将构建并自动化一个数据管道,定时导入和转换 CSV 文件。我们将学习如何使用 Snowflake 内部阶段(internal stage)暂存文件。接着,我们将执行 Snowflake 的 COPY 命令,将数据从内部暂存阶段加载到暂存表。然后,我们将把暂存表的数据合并到目标表中。接下来,我们会使用 SQL 语句将数据转换为适合下游使用者的格式。最后,我们将创建一个任务来每天自动执行数据管道。图 2.1 显示了使用这些步骤构建的数据管道。
在本章中,我们将通过一个示例来说明如何构建数据管道。我们将考虑一个虚构的面包店,该面包店生产面包和糕点,并将这些烘焙食品配送给社区内的小型商店,如杂货店、咖啡馆和餐馆。由于面包店没有在线订购系统,客户通过电子邮件下单。面包店的一名员工会阅读这些电子邮件,并将所有客户订单收集到一个保存在本地文件系统上的 CSV 文件中。
面包店经理每天工作结束时会总结 CSV 文件中的订单,以确定未来几天面包店需要生产多少种类的烘焙食品。这些信息对于面包店来说非常重要,因为它需要根据需求来决定购买多少原材料以及每天需要多少员工来生产这些食品。
面包店希望通过将 CSV 文件中的数据导入 Snowflake,并执行汇总操作来自动化数据管理过程。本章将带领你完成构建支持该面包店场景的数据管道的过程。
参考 Snowflake 文档
在本书中,我们通过示例并根据需要执行 Snowflake 命令,来说明描述各种数据工程任务的示例。我们不会详细描述每个 Snowflake 命令或提供每个命令的所有可能选项和变体。我们重点突出与示例相关的语法和选项,并提供执行最常用命令选项和参数的信息。有关所有命令的详细信息,可以参考 Snowflake 文档,网址为 docs.snowflake.com/。在需要的地方,我们会提供文档中特定章节的链接。
2.1 设置你的 Snowflake 账户
为了跟随本章及后续章节的示例,你需要访问一个 Snowflake 账户。如果你已经有访问权限,并可以在其中操作示例,那么请随时使用它。确保你拥有足够的权限来创建数据库并向其中添加其他对象。
如果你还没有 Snowflake 账户,可以在 signup.snowflake.com/ 创建一个免费试用 Snowflake 账户。附录 A 提供了关于创建免费试用 Snowflake 账户和可用选项的更多信息。
注意:本章的所有代码和示例数据文件可以在随附的 GitHub 仓库的 Chapter_02 文件夹中找到,地址为 mng.bz/dZn1。
在 Snowflake 账户中,第一步是创建一个数据库、一个架构以及一个虚拟数据仓库,我们将使用它来构建数据管道。目前,我们将使用 SYSADMIN 角色创建对象,这是 Snowflake 中的内置管理角色之一。通常,数据工程师会在 Snowflake 账户中使用自定义角色,后续章节中我们将讨论这一点。由于免费试用的 Snowflake 账户中没有自定义角色,因此我们现在将使用内置角色。
提示:即使你使用的是具有 ACCOUNTADMIN 角色的 Snowflake 免费试用账户,请记住,这个角色是 Snowflake 中权限最强大的角色,应该仅用于管理任务。绝不要使用 ACCOUNTADMIN 角色来创建对象;相反,选择一个具有较少权限的角色,这样你就可以执行所需的任务。
为了创建所需的数据库、架构和虚拟数据仓库,我们将在 Snowflake 的 Web 界面(也称为 Snowsight)中打开一个新工作表并执行命令。
提示:如果你不熟悉 Snowsight 用户界面,请参考 Snowflake 文档:mng.bz/r1zj。
我们将使用 SYSADMIN 角色来创建一个名为 BAKERY_DB 的数据库,一个名为 ORDERS 的架构,以及一个名为 BAKERY_WH 的额外小型虚拟数据仓库:
use role SYSADMIN;
create database BAKERY_DB;
create schema ORDERS;
create warehouse BAKERY_WH with warehouse_size = 'XSMALL';
使用 SNOWSIGHT 用户界面与执行命令
在 Snowflake 中,许多对象(如数据库、架构、虚拟数据仓库等)可以通过执行命令或使用 Snowsight 用户界面来创建。在本书中,我们将偏向于执行命令,而非使用用户界面。数据工程管道通常是定时运行的,最小化用户干预,这意味着我们必须使用可以自动化的命令,而不是通过点击用户界面来操作。
2.2 暂存 CSV 文件
为了开始构建面包店所需的数据管道,我们必须准备好一个包含待导入数据的 CSV 文件。面包店每天都在收集订单,并将其保存为 CSV 文件。例如,面包店在 7 月 7 日收集的所有订单都存储在名为 Orders_2023-07-07.csv 的 CSV 文件中。该文件的前几行数据如下表 2.1 所示。
表 2.1 面包店一天内收集的订单
| Customer | Order date | Delivery date | Baked good type | Quantity |
|---|---|---|---|---|
| Coffee Pocket | 2023-07-07 | 2023-07-10 | Baguette | 6 |
| Coffee Pocket | 2023-07-07 | 2023-07-10 | Bagel | 12 |
| Coffee Pocket | 2023-07-07 | 2023-07-10 | English muffin | 16 |
| Coffee Pocket | 2023-07-07 | 2023-07-10 | Croissant | 18 |
| Lily's Coffee | 2023-07-07 | 2023-07-10 | Bagel | 20 |
| Lily's Coffee | 2023-07-07 | 2023-07-10 | White loaf | 4 |
| Lily's Coffee | 2023-07-07 | 2023-07-10 | Croissant | 20 |
| Crave Coffee | 2023-07-07 | 2023-07-10 | Croissant | 50 |
| Best Burgers | 2023-07-07 | 2023-07-10 | Hamburger bun | 75 |
| ... | ... | ... | ... | ... |
该 CSV 文件存储在面包店的本地文件系统中。为了将数据从此文件导入到 Snowflake,我们将把文件上传到 Snowflake 的阶段(stage)中。阶段是一个 Snowflake 对象,用于指向云存储中数据文件的位置。Snowflake 支持两种类型的阶段:
- 外部阶段:数据存储在任何支持的云存储提供商中,包括 Amazon S3、Google Cloud Storage 或 Microsoft Azure。
- 内部阶段:数据存储在托管 Snowflake 账户的云存储提供商中。
外部阶段需要连接到云存储提供商,并将在下一章中详细讨论。为了简化我们的第一个数据管道,我们将使用 Snowflake 内部阶段。我们可以在不同类型的内部阶段中暂存数据文件:
- 分配给每个 Snowflake 用户的用户阶段
- 分配给每个在 Snowflake 中创建的表的表阶段
- 命名的内部阶段
Snowflake 的命名内部阶段是一个在架构中创建的数据库对象。它比用户阶段或表阶段更具灵活性,因为它可以暂存由多个用户管理的文件,并将其加载到多个表中。
让我们创建 Snowflake 的命名阶段来构建管道。在与之前相同的工作表中,使用 BAKERY_DB 数据库和 ORDERS 架构,我们将执行以下命令:
use database BAKERY_DB;
use schema ORDERS;
create stage ORDERS_STAGE;
我们可以通过使用 LIST 命令来查看阶段的内容:
list @ORDERS_STAGE;
由于我们刚刚创建了该阶段,它是空的,因此 LIST 命令会返回一条消息:“查询没有结果。”
现在,我们可以将 CSV 文件上传到我们刚刚创建的阶段。我们将使用 Snowsight 用户界面执行此步骤。为了将文件上传到 Snowflake 内部阶段,我们将在 Snowsight 中导航到主菜单,展开“数据”选项并点击“数据库”。第二个导航窗格出现后,我们可以从 BAKERY_DB 数据库导航到 ORDERS 架构,展开“阶段”文件夹并选择 ORDERS_STAGE 内部阶段。我们将看到类似于图 2.2 的屏幕。
在我们点击导航窗格中的 ORDERS_STAGE 后,主窗口将显示其属性。在这里,我们可以点击 +Files 按钮来上传文件到该阶段。在弹出的窗口中,我们可以上传 Orders_2023-07-07.csv 文件。
让我们再次使用之前相同的 LIST 命令查看阶段的内容:
list @ORDERS_STAGE;
现在,阶段中应该包含我们刚刚上传的 CSV 文件。LIST 命令的输出如下表 2.2 所示。
表 2.2 LIST 命令输出,显示位于内部阶段中的 CSV 文件
| Name | Size | MD5 | Last_modified |
|---|---|---|---|
| orders_stage/Orders_2023-07-07.csv | 3376 | 0fe929d77d8d60772d42dcf7482bb5bc | Fri, 7 Jul 2023 17:02:48 |
通过 Snowflake CLI 将文件加载到 Snowflake 阶段
还有一种替代方法可以将文件从本地文件系统上传到 Snowflake 阶段。我们本可以使用 Snowflake 命令行界面(CLI)来上传文件,但这需要安装和配置 Snowflake CLI,增加了复杂性。对于面包店的员工来说,这种方法可能比较具有挑战性,而使用 Snowsight 用户界面将更加简便。
2.3 从暂存文件加载数据到目标表
现在,CSV 文件已经存储在 Snowflake 内部阶段,我们可以继续执行从文件加载数据到暂存表的步骤。但在此之前,让我们查看数据,以确保它可用且格式符合预期。我们将使用 SQL 命令来查看数据,因为稍后我们也需要使用相同的命令来加载数据。
查看阶段数据的最简单方法是对整个阶段使用 SELECT 命令。需要记住的一点是,在从阶段选择数据时,我们不能像 SQL 查询那样使用 SELECT * 语法来表示所有列,因为 Snowflake 无法知道阶段中数据文件的模式。相反,我们使用 $ 符号来引用文件中的列,例如,$1 表示第一列,$2 表示第二列,依此类推。
对于半结构化格式(如 Parquet 或 JSON),Snowflake 将每条记录视为一列。但是,当数据以 CSV 格式存在时,默认的字段分隔符是逗号,默认的记录分隔符是换行符。由于我们在 CSV 文件中使用了相同的分隔符,Snowflake 会默认识别列。为了从内部阶段选择所有数据,假设上传的 CSV 文件包含五列,我们可以执行以下命令:
select $1, $2, $3, $4, $5 from @ORDERS_STAGE;
此命令的输出显示了面包店收集的订单,如表 2.3 所示(仅显示前几行作为示例)。
表 2.3 从 ORDERS_STAGE 内部阶段选择数据的命令输出
| $1 | $2 | $3 | $4 | $5 |
|---|---|---|---|---|
| Customer | Order date | Delivery date | Baked good type | Quantity |
| Coffee Pocket | 2023-07-07 | 2023-07-10 | Baguette | 6 |
| Coffee Pocket | 2023-07-07 | 2023-07-10 | Bagel | 12 |
| Coffee Pocket | 2023-07-07 | 2023-07-10 | English muffin | 16 |
如表 2.3 所示,Snowflake 不会区分包含列名的头行和数据内容。在从暂存文件加载数据到表时,我们必须告知 Snowflake 排除包含列名的头行。为此,在加载数据时我们将指定文件格式。
Snowflake 支持结构化(CSV、制表符分隔值等)和半结构化(JSON、Avro、ORC、Parquet 和 XML)文件格式。通过文件格式,我们可以提供选项,指定文件中的数据类型以及进一步描述数据格式的其他属性。我们可以将文件格式定义为 Snowflake 中的独立命名数据库对象,具体说明将在第 3 章中提供。为了简单起见,我们将使用单独的格式选项,在加载、卸载或查看暂存文件数据的命令中指定文件格式。
暂存文件包含我们也可以在查询中使用的元数据列。一个有用的元数据列是 metadata$filename,它告诉我们数据来源的文件名。由于目前阶段中只有一个文件,这个信息可能不太重要,但一旦我们开始将更多文件添加到阶段中时,我们将需要知道每条记录的来源文件。
另一个有用的元数据列是 metadata$file_row_number,它包含文件中记录的行号。当我们希望确保目标表中的记录保留其在源数据中的顺序时,这个列非常有用。
2.3.1 从暂存文件加载数据到暂存表
我们可以继续将数据从内部阶段加载到 Snowflake 中的暂存表。首先,我们必须创建暂存表。该表将包含五个列,用于存储来自 CSV 文件的数据(客户、订单日期、交货日期、烘焙商品类型和数量),并为每列定义相应的数据类型(varchar、date 或 number)。
在从文件摄取数据时,最佳实践是添加一些额外的列,用于存储帮助我们跟踪数据摄取时间和来源的额外信息。我们将添加一列用于存储数据来源文件名,另一列用于存储摄取时间戳。我们可以使用以下命令,在之前创建的 BAKERY_DB 数据库和 ORDERS 架构中创建名为 ORDERS_STG 的表:
use database BAKERY_DB;
use schema ORDERS;
create table ORDERS_STG (
customer varchar,
order_date date,
delivery_date date,
baked_good_type varchar,
quantity number,
source_file_name varchar,
load_ts timestamp
);
现在表已创建,我们可以使用 Snowflake 的 COPY 命令,将数据从内部阶段的文件加载到暂存表中。
在后续章节中,会更详细地描述使用 COPY 命令的各种场景。现在,让我们简要回顾一下这个命令。我们使用 COPY INTO <table> 命令,从外部阶段的云存储提供商或从包含在 Snowflake 账户中的内部阶段的云存储加载数据,如本章中的示例所示。
尽管 COPY 命令主要用于加载数据,而不是转换数据,但我们仍然可以进行一些简单的转换。这些包括选择单独的列、重新排序列、转换数据类型或截断文本字符串。
为了将数据从 ORDERS_STAGE 内部阶段加载到 ORDERS_STG 表中,我们执行带有附加选项的 COPY 命令。在本例中,我们将使用以下选项:FILE_FORMAT(跳过头行)、ON_ERROR(如果遇到任何错误,指示 Snowflake 中止操作)和 PURGE(数据加载后从内部阶段删除文件)。
在 COPY 命令的 SELECT 语句中,我们将包括暂存文件中的五列、metadata$filename 列(它告诉我们来源文件的名称)以及 CURRENT_TIMESTAMP() 函数(提供当前时间戳)。将数据从暂存文件加载到 Snowflake 表中的 COPY 命令如下面的代码所示:
Listing 2.1 从暂存文件加载数据到 Snowflake 的 COPY 命令
use database BAKERY_DB;
use schema ORDERS;
copy into ORDERS_STG
from (
select $1, $2, $3, $4, $5, metadata$filename, current_timestamp()
from @ORDERS_STAGE
)
file_format = (type = CSV, skip_header = 1)
on_error = abort_statement
purge = true;
成功执行该命令后,我们可以使用一个简单的 SQL 语句查看加载到 ORDERS_STG 表中的数据:
select * from ORDERS_STG;
如表 2.4 所示,我们应该会看到来自 CSV 文件的 64 行订单数据(这里只展示了前几行作为示例)。
表 2.4 从 ORDERS_STG 暂存表中选择数据的命令输出
| Customer | Order date | Delivery date | Baked good type | Quantity | Source filename | Load timestamp |
|---|---|---|---|---|---|---|
| Coffee Pocket | 2023-07-07 | 2023-07-10 | Baguette | 6 | Orders_2023-07-07.csv | 2023-07-07 09:43:47 |
| Coffee Pocket | 2023-07-07 | 2023-07-10 | Bagel | 12 | Orders_2023-07-07.csv | 2023-07-07 09:43:47 |
| Coffee Pocket | 2023-07-07 | 2023-07-10 | English muffin | 16 | Orders_2023-07-07.csv | 2023-07-07 09:43:47 |
由于我们在 COPY 语句中指定了 PURGE 选项,数据成功摄取后,CSV 文件会从 Snowflake 内部阶段删除。
我们可以通过使用 LIST 命令重新检查阶段的内容:
list @ORDERS_STAGE;
由于 COPY 命令的 PURGE 选项在摄取数据后删除了文件,阶段现在再次为空。执行 LIST 命令时会返回“Query produced no results”消息。
2.3.2 将暂存表中的数据合并到目标表中
暂存表 ORDERS_STG 仅包含在执行 COPY 命令时,内部阶段中 CSV 文件中可用的数据。我们将把这些数据添加到名为 CUSTOMER_ORDERS 的目标表中,该表存储了面包店记录的所有客户订单的历史数据。
CUSTOMER_ORDERS 表的结构与暂存表相同。我们可以通过执行以下命令,使用之前创建的 BAKERY_DB 数据库和 ORDERS 架构来创建目标表:
use database BAKERY_DB;
use schema ORDERS;
create table CUSTOMER_ORDERS (
customer varchar,
order_date date,
delivery_date date,
baked_good_type varchar,
quantity number,
source_file_name varchar,
load_ts timestamp
);
要将数据从暂存表加载到目标表,我们可以使用 INSERT SQL 语句将所有数据从暂存表添加到目标表。然而,作为数据工程师,我们必须时刻关注数据完整性。客户可能会更改订单的数量,并在相同的交货日期为相同的烘焙商品类型重新下订单,从而取代先前的订单。
为了确保每个客户、每种烘焙商品类型和每个交货日期只有一条记录,我们将构建一个查询,将 ORDERS_STG 暂存表中的数据合并到 CUSTOMER_ORDERS 目标表中。一个 MERGE 语句包括以下几个部分:
- MERGE INTO 关键字,后面跟着目标表的名称,目标表将接收语句的结果
- USING 关键字,后面跟着数据来源的源表名称
- ON 子句,我们指定目标表的主键或确保唯一性的列;在我们的示例中,这些列是客户、烘焙商品类型和交货日期
- WHEN MATCHED THEN 子句,当源表和目标表中存在相同的唯一列组合时执行;在这种情况下,源表的值将覆盖目标表中的值
- WHEN NOT MATCHED THEN 子句,当目标表中没有该唯一列组合时执行;在这种情况下,将源表中的记录插入到目标表中
我们将执行的查询如下所示:
Listing 2.2 将暂存表中的订单合并到目标表
-- 目标表
merge into CUSTOMER_ORDERS tgt
-- 源表
using ORDERS_STG as src
-- 确保唯一性的列
on src.customer = tgt.customer
and src.delivery_date = tgt.delivery_date
and src.baked_good_type = tgt.baked_good_type
-- 使用源表中的值更新目标表
when matched then
update set tgt.quantity = src.quantity,
tgt.source_file_name = src.source_file_name,
tgt.load_ts = current_timestamp()
-- 插入源表中的新值到目标表
when not matched then
insert (customer, order_date, delivery_date, baked_good_type,
quantity, source_file_name, load_ts)
values(src.customer, src.order_date, src.delivery_date,
src.baked_good_type, src.quantity, src.source_file_name,
current_timestamp());
通过将暂存表中的数据合并到目标表中,我们将始终确保每个客户、每种烘焙商品类型和每个交货日期在目标表中最多只有一行,并且该行包含最新的数量值。
注意:与其他流行的关系型数据库不同,在 Snowflake 中创建表时不必定义主键。
我们可以通过执行以下简单的 SQL 语句查看加载到 CUSTOMER_ORDERS 目标表中的数据:
select * from CUSTOMER_ORDERS order by delivery_date desc;
我们应该看到 64 行订单数据,正如表 2.4 中所示。
注意:使用 MERGE 语句将数据从暂存表添加到目标表是将新数据添加到存储历史数据的表中的许多方法之一。其他方法也可以使用,例如仅追加的策略或构建 SCD(慢速变化维度)类型 2 历史变化。关于这些方法的更多讨论将在后续章节中进行。
2.4 使用 SQL 命令进行数据转换
CUSTOMER_ORDERS 目标表包含了所有客户的历史订单以及客户已经下的未来订单。用户可以查询此表以检查过去的订单趋势并生成报告。面包店经理的要求是,为了更好的规划,面包店需要知道在未来几天每天每种烘焙商品的订单数量。
由于面包店需要定期获得此信息,我们将构建一个名为 SUMMARY_ORDERS 的汇总表,包含三个列:交货日期、烘焙商品类型和总数量。为了创建此表,我们仍然使用 BAKERY_DB 数据库和 ORDERS 架构,并执行以下命令:
use database BAKERY_DB;
use schema ORDERS;
create table SUMMARY_ORDERS(
delivery_date date,
baked_good_type varchar,
total_quantity number
);
接下来,我们将从之前填充的 CUSTOMER_ORDERS 表中插入汇总数据。为了简化操作,我们每次都会清空 SUMMARY_ORDERS 表,并插入完整的汇总数据。我们之所以这样做,是因为面包店并不会存储大量数据,我们无需考虑每次替换所有数据的计算成本或填充数据所需的时间。在后续章节中,我们将学习不同的数据转换方法,比如物化视图或动态表。但目前,我们将每次都替换数据。
用来汇总 CUSTOMER_ORDERS 数据的查询语句是:
select delivery_date, baked_good_type, sum(quantity) as total_quantity
from CUSTOMER_ORDERS
group by all;
在这个查询中,我们使用了 group by all 的 Snowflake 语法。与其在 group by 子句中指定所有列,Snowflake 允许我们使用 all 关键字,表示所有未参与聚合的列。在执行上述查询后,我们应该看到类似表 2.5 的结果(这里只显示了前几行以作说明)。
表 2.5 汇总 CUSTOMER_ORDERS 数据的查询输出
| Delivery date | Baked good type | Total quantity |
|---|---|---|
| 2023-07-10 | Baguette | 30 |
| 2023-07-10 | Bagel | 70 |
| 2023-07-10 | English muffin | 52 |
| 2023-07-10 | Croissant | 88 |
| 2023-07-10 | White loaf | 44 |
| 2023-07-10 | Hamburger bun | 137 |
| 2023-07-10 | Whole wheat loaf | 25 |
为了准备面包店经理所需的汇总数据以进行未来的规划,我们可以清空目标表并使用之前的查询插入汇总数据。首先,我们使用以下命令清空汇总表:
truncate table SUMMARY_ORDERS;
然后,使用以下查询将汇总数据插入汇总表:
Listing 2.3 向汇总表插入汇总数据
insert into SUMMARY_ORDERS(delivery_date, baked_good_type, total_quantity)
select delivery_date, baked_good_type, sum(quantity) as total_quantity
from CUSTOMER_ORDERS
group by all;
面包店经理随后可以使用以下命令查看汇总数据:
select * from SUMMARY_ORDERS;
该命令的输出与表 2.5 中的结果相同(这里只显示了前几行以作说明)。
2.5 使用任务自动化流程
由于面包店每天都会定期收到来自客户的新订单和更新订单,因此他们希望尽可能自动化数据摄取和转换过程。
面包店仍然需要通过阅读电子邮件并将订单数据存储在 CSV 文件中来手动收集数据,因为他们没有其他系统可用。他们还必须手动将 CSV 文件上传到内部的 Snowflake 阶段。然而,从那时起,数据工程师可以通过将之前的步骤结合起来并安排定期执行(例如,每天晚上),来自动化这个过程。他们可以为此创建 Snowflake 任务。
Snowflake 任务可以执行单个 SQL 语句、存储过程或使用 Snowflake 脚本程序逻辑的 SQL 语句集。任务通常与流(streams)结合使用,流用于检测源数据的变化。本章中,我们将使用没有流的任务来构建数据管道。流将在第 12 章中详细解释。
任务在执行时需要计算资源。计算资源可以通过虚拟仓库(virtual warehouse)或 Snowflake 管理的计算资源提供。在本章中构建的数据管道中,我们将使用之前创建的 BAKERY_WH 虚拟仓库。
我们将创建一个名为 PROCESS_ORDERS 的任务,它将在每天根据我们提供的计划顺序执行所有先前的步骤。任务将执行的步骤如下:
- 清空暂存表。
- 使用 COPY 命令从内部阶段加载数据到暂存表。
- 将数据从暂存表合并到目标表。
- 清空汇总表。
- 将汇总数据插入到汇总表中。
最初为了测试,我们将任务的执行计划设置为每 10 分钟执行一次,以便监控多次执行,确保任务正常工作。稍后,当我们确认一切正常时,我们会将任务安排在每天晚上 11 点执行。
以下代码创建 PROCESS_ORDERS 任务(为简洁起见,某些 SQL 语句已经缩写,使用了前面列表中的代码):
use database BAKERY_DB;
use schema ORDERS;
create task PROCESS_ORDERS
warehouse = BAKERY_WH
schedule = '10 M'
as
begin
truncate table ORDERS_STG;
copy into ORDERS_STG... #1
merge into CUSTOMER_ORDERS... #2
truncate table SUMMARY_ORDERS;
insert into SUMMARY_ORDERS... #3
end;
#1 来自列表 2.1
#2 来自列表 2.3
#3 来自列表 2.4
当我们在 Snowflake 中创建一个任务时,它默认处于挂起状态。为了让任务执行,我们必须恢复它,以便它按计划运行。在允许任务按计划运行之前,我们可以先通过手动执行一次来测试它。
然而,在执行任务之前,我们必须向将要执行任务的角色授予 EXECUTE TASK 权限。尽管 Snowflake 允许所有角色创建任务而无需额外的权限,但默认情况下不会启用任务的执行。因为在本章中我们使用的是 SYSADMIN 角色,所以我们必须将 EXECUTE TASK 权限授予该角色(通常,数据工程师使用自定义角色来创建和执行数据管道,并会将 EXECUTE TASK 权限授予自定义角色)。我们可以使用以下命令将 EXECUTE TASK 权限授予 SYSADMIN 角色:
use role accountadmin;
grant execute task on account to role sysadmin;
use role sysadmin;
我们必须使用 ACCOUNTADMIN 角色来执行授权操作。每当我们使用 ACCOUNTADMIN 角色来授予权限或执行任何其他需要该角色的任务时,必须记得在不再需要时切换回开发时使用的角色。因此,在此我们切换回 SYSADMIN 角色。
授予 EXECUTE TASK 权限后,我们可以使用以下命令执行任务一次:
execute task PROCESS_ORDERS;
执行任务后,我们希望验证它是否成功完成。我们可以通过调用 TASK_HISTORY() 表函数来获取查询执行的信息,该函数位于 INFORMATION_SCHEMA 中。
SNOWFLAKE 信息架构
Snowflake 信息架构包含了有关 Snowflake 帐户中所有对象的元数据视图,以及包含与帐户相关的历史和使用数据的表函数。信息架构存在于每个数据库中的名为 INFORMATION_SCHEMA 的架构中。有关详细信息,请参阅 Snowflake 文档:mng.bz/V2Jx。
我们可以执行以下查询命令来查看之前和计划中的任务执行情况。
Listing 2.4 查看之前和计划中的任务执行情况
select *
from table(information_schema.task_history())
order by scheduled_time desc;
该命令的输出显示任务的执行细节,包括执行的代码、状态(SUCCEEDED 或 FAILED)、如果任务失败时的错误信息、返回值、任务开始和完成的时间戳等。以下是该命令的示例输出,显示在表 2.6 中。
表 2.6 执行 TASK_HISTORY() 表函数后的输出列及其值
| 列名 | 值 |
|---|---|
| QUERY_ID | 01b0c4f9-0102-24a5-0001-6d92000bd6b2 |
| NAME | PROCESS_ORDERS |
| DATABASE_NAME | BAKERY_DB |
| SCHEMA_NAME | ORDERS |
| QUERY_TEXT | begin truncate table ORDERS_STG; copy into ORDERS_STG... |
| STATE | SUCCEEDED |
| ERROR_CODE | NULL |
| ERROR_MESSAGE | NULL |
| SCHEDULED_TIME | 2023-07-07T11:37:05.747-08:00 |
| QUERY_START_TIME | 2023-07-07T11:37:07.371-08:00 |
| NEXT_SCHEDULED_TIME | 2023-07-07T11:47:05.747-08:00 |
| COMPLETED_TIME | 2023-07-07T11:37:10.419-08:00 |
当我们确认任务正确运行后,可以让它按照我们最初为测试设置的每 10 分钟运行一次,以便我们可以监控它的多次执行。为了让任务按定义的计划运行,我们使用以下命令恢复任务:
alter task PROCESS_ORDERS resume;
我们可以使用 GitHub 存储库中找到的额外 CSV 文件(例如 Orders_2023-07-08.csv 和 Orders_2023-07-09.csv)来验证每次任务执行时数据是否正确加载。我们可以手动将每个文件上传到内部 Snowflake 阶段,然后检查这些文件中的数据是否正确加载到暂存表、目标表和汇总表中。我们还可以通过查询 TASK_HISTORY() 表函数来监控任务执行的成功与下一次计划执行,如在 Listing 2.4 中所示。
最后,当我们确认任务能够正确地将数据从暂存文件加载到 Snowflake 表时,可以将任务的执行计划更改为每天晚上 11 点执行。为此,我们将使用 CRON 语法安排任务执行。
在我们对任务进行任何更改之前,必须先暂停它。更改后,我们必须再次恢复任务。我们安排任务在晚上 11 点执行的 CRON 参数是:0(分钟)、23(小时,24 小时制)、 (日期)、 (月份)、*(星期几)。我们还必须提供一个时区,以确保任务执行时间的明确性。在本例中,我们将时区设置为 UTC。
提示
有关如何使用 CRON 语法安排任务执行的更多信息,请参阅 Snowflake 文档:mng.bz/x686。
我们可以通过执行以下命令序列更改任务的执行计划:
alter task PROCESS_ORDERS suspend;
alter task PROCESS_ORDERS
set schedule = 'USING CRON 0 23 * * * UTC';
alter task PROCESS_ORDERS resume;
我们可以再次查询 TASK_HISTORY() 表函数,确认任务已按指定时间安排执行。然后,我们可以在第二天检查任务历史记录,验证它是否成功执行。
提示
测试完任务后,我们可以暂停它,以避免不必要的资源消耗。
要暂停任务,我们可以执行以下命令:
alter task PROCESS_ORDERS suspend;
在本章中,我们创建了第一个数据管道,并且保持了简洁的起始结构。接下来的章节中,我们将在此基础上扩展,添加更多功能并介绍不同的方式来实现类似的结果。
总结
我们旨在构建并自动化一个数据管道,该管道从 CSV 文件中摄取数据,转换数据,并按照计划定期执行任务。
为了将数据从 CSV 文件导入 Snowflake,我们将文件上传到内部阶段。Snowflake 阶段指的是云存储中数据文件的位置。
我们可以通过执行 SQL SELECT 命令来查看暂存数据文件的内容。与通常使用 **SELECT *** 来表示所有列不同,我们使用 $ 符号来引用阶段中的列。
我们执行 Snowflake 的 COPY 命令,将暂存的 CSV 文件中的数据摄取到 Snowflake 的暂存表中。为了正确解析文件结构,我们可以指定文件格式,例如忽略头行。
我们可以使用 SQL MERGE 命令,将暂存表中的新数据添加到 Snowflake 的目标表中。然而,我们必须注意数据完整性约束,如唯一性。
我们可以使用 SQL 命令进行数据转换,例如,为报告汇总数据,并将汇总数据存储在一个新表中,供下游消费者使用。
Snowflake 任务可以按计划执行一系列 SQL 语句。任务可以根据预定义的时间间隔或特定时间安排执行。