以编程方式创建 AWS Athena 视图

Create AWS Athena view programmatically

概述了如何使用用户界面创建视图。

我想以编程方式创建 AWS Athena 视图,最好使用 Terraform(调用 CloudFormation)。

我按照此处列出的步骤进行操作:https://ujjwalbhardwaj.me/post/create-virtual-views-with-aws-glue-and-query-them-using-athena,但是我 运行 遇到了一个问题,因为视图很快就会过时。

...._view' is stale; it must be re-created.

terraform 代码如下所示:

resource "aws_glue_catalog_table" "adobe_session_view" {

  database_name = "${var.database_name}"
  name = "session_view"

  table_type = "VIRTUAL_VIEW"
  view_original_text = "/* Presto View: ${base64encode(data.template_file.query_file.rendered)} */"
  view_expanded_text = "/* Presto View */"

  parameters = {
    presto_view = "true"
    comment = "Presto View"
  }

  storage_descriptor {
    ser_de_info {
      name = "ParquetHiveSerDe"
      serialization_library = "org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe"
    }

    columns { name = "first_column" type = "string" }
    columns { name = "second_column" type = "int" }
    ...
    columns { name = "nth_column" type = "string" }
}

我很乐意使用的替代方法是 AWS CLI,但是 aws athena [option] 没有为此提供任何选项。

我试过:

正如您所建议的,绝对可以使用 start-query-execution 通过 AWS CLI 以编程方式创建 Athena 视图。正如您所指出的,这确实需要您为结果提供 S3 位置,即使您不需要检查文件(Athena 出于某种原因会在该位置放置一个空的 txt 文件)。

这是一个例子:

$ aws athena start-query-execution --query-string "create view my_view as select * from my_table" --result-configuration "OutputLocation=s3://my-bucket/tmp" --query-execution-context "Database=my_database"

{
    "QueryExecutionId": "1744ed2b-e111-4a91-80ea-bcb1eb1c9c25"
}

您可以避免让客户端通过 creating a workgroup and setting the location there.

指定存储桶

您可以使用get-query-execution 命令检查您的视图创建是否成功。

$ aws --region athena get-query-execution --query-execution-id bedf3eba-55b0-42de-9a7f-7c0ba71c6d9b
{
    "QueryExecution": {
        "QueryExecutionId": "1744ed2b-e111-4a91-80ea-bcb1eb1c9c25",
        "Query": "create view my_view as select * from my_table",
        "StatementType": "DDL",
        "ResultConfiguration": {
            "OutputLocation": "s3://my-bucket/tmp/1744ed2b-e111-4a91-80ea-bcb1eb1c9c25.txt"
        },
        "Status": {
            "State": "SUCCEEDED",
            "SubmissionDateTime": 1558744806.679,
            "CompletionDateTime": 1558744807.312
        },
        "Statistics": {
            "EngineExecutionTimeInMillis": 548,
            "DataScannedInBytes": 0
        },
        "WorkGroup": "primary"
    }
}

在 Athena 中以编程方式创建视图没有记录,也不受支持,但可以。当您使用 StartQueryExecution 创建视图时,在幕后发生的事情是 Athena 让 Presto 创建视图,然后提取 Presto 的内部表示并将其放入 Glue 目录中。

陈旧问题通常来自 Presto 元数据和 Glue 元数据中的列不同步。一个 Athena 视图实际上包含视图的三个描述:视图 SQL、Glue 格式的列及其类型以及 Presto 格式的列和类型。如果其中任何一个不同步,您将得到“……陈旧;它必须是 re-created”。错误。

这些是 Glue table 用作 Athena 视图的要求:

  • TableType 必须是 VIRTUAL_VIEW
  • Parameters 必须包含 presto_view: true
  • TableInput.ViewOriginalText 必须包含编码的 Presto 视图(见下文)
  • StorageDescriptor.SerdeInfo 必须是空映射
  • StorageDescriptor.Columns 必须包含视图定义的所有列及其类型

棘手的部分是编码的 Presto 视图。该结构由以下代码创建:https://github.com/prestosql/presto/blob/27a1b0e304be841055b461e2c00490dae4e30a4e/presto-hive/src/main/java/io/prestosql/plugin/hive/HiveUtil.java#L597-L600,这或多或少是它的作用:

  • 添加前缀 /* Presto View:(在 : 之后添加 space)
  • 添加 base 64 编码的 JSON 字符串,其中包含视图 SQL、列及其类型和一些目录元数据(见下文)
  • 添加后缀 */(在 * 之前添加 space)

描述视图的 JSON 如下所示:

  • A catalog 属性 必须具有值 awsdatacatalog.
  • A schema 属性 必须是创建视图的数据库的名称(即它必须匹配周围 Glue 结构的 DatabaseName 属性 .
  • 列列表,每个列都有一个 nametype
  • 一个originalSql属性,实际视图SQL(不包括CREATE VIEW …,应该以SELECT …WITH …开头)

这是一个例子:

{
  "catalog": "awsdatacatalog",
  "schema": "some_database",
  "columns": [
    {"name": "col1", "type": "varchar"},
    {"name": "col2", "type": "bigint"}
  ],
  "originalSql": "SELECT col1, col2 FROM some_other_table"
}

这里需要注意的是,列的类型几乎(但不完全)与 Glue 中的名称相同。如果 Athena/Glue 将具有 string,则此 JSON 中的值必须是 varchar。如果 Athena/Glue 使用 array<string> 则此 JSON 中的值必须为 array(varchar),并且 struct<foo:int> 变为 row(foo int).

这非常混乱,将它们放在一起需要一些摆弄和测试。让它工作的最简单方法是创建一些视图并解码上面的指令以查看它们的外观,然后尝试自己做。

为了补充 JD DTheo 的答案,使用他们的解决方案,我们已经弄清楚了如何通过以下 terraform 调用 AWS Cli:

resource "null_resource" "athena_view" {

  provisioner "local-exec" {
    command = <<EOF
aws sts assume-role \
  --output json \
  --region my_region \
  --role-arn arn:aws:iam::${var.account_number}:role/my_role \
  --role-session-name create_my_view > /tmp/credentials.json

export AWS_SESSION_TOKEN=$(jq -r '.Credentials.SessionToken' /tmp/credentials.json)
export AWS_ACCESS_KEY_ID=$(jq -r '.Credentials.AccessKeyId' /tmp/credentials.json)
export AWS_SECRET_ACCESS_KEY=$(jq -r '.Credentials.SecretAccessKey' /tmp/credentials.json)

aws athena start-query-execution \
  --output json \
  --region my_region \
  --query-string "CREATE OR REPLACE VIEW my_view AS SELECT * FROM my_table \
  --query-execution-context "Database=${var.database_name}" \
  --result-configuration "OutputLocation=s3://${aws_s3_bucket.my-bucket.bucket}"
EOF
  }
}

我们使用 null_resource ... 运行 不直接与特定资源关联的供应商

aws sts assume-role的结果输出为JSON到/tmp/credentials.json.

jq is used to parse the necessary fields out of the output of aws sts assume-role .

aws athena start-query-execution然后可以在定义的环境变量指定的角色下执行。

可以指定 --work-group 而不是 --result-configuration "OutputLocation=s3://....,请注意,这是 start-query-execution 上的单独标志,而不是 --result-configuration 字符串的一部分。

补充Theo的答案:在base64编码的JSON文件中,定义cloumn属性时类型"string"无效!此时总是写"varchar"。

编辑:另外 "int" 必须声明为 "integer"!

我选择了 Theo 的解决方案,它使用 AWS Cloud Formation 模板工作。

我只是想添加一点提示,可以节省您数小时的调试时间。我写这不是评论,因为我还没有发表评论的权利。请随意将其复制并粘贴到 Theo 回答的评论部分。

为 Terraform 0.12+ 语法更新上述示例, 并添加从文件系统读取视图查询:

resource "null_resource" "athena_views" {
  for_each = {
    for filename in fileset("${path.module}/athenaviews/", "**"):
           replace(filename,"/","_") => file("${path.module}/athenaviews/${filename}")
  }

  provisioner "local-exec" {
    command = <<EOF
    aws athena start-query-execution \
      --output json \
      --query-string CREATE OR REPLACE VIEW ${each.key} AS ${each.value} \
      --query-execution-context "Database=${var.athena_database}" \
      --result-configuration "OutputLocation=s3://${aws_s3_bucket.my-bucket.bucket}"
EOF
  }

  provisioner "local-exec" {
    when    = "destroy"
    command = <<EOF
    aws athena start-query-execution \
      --output json \
      --query-string DROP VIEW IF EXISTS ${each.key} \
      --query-execution-context "Database=${var.athena_database}" \
      --result-configuration "OutputLocation=s3://${aws_s3_bucket.my-bucket.bucket}"
EOF
  }
}

另请注意 when= "destroy" 块以确保在拆除堆栈时删除视图。

将带有 SELECT 查询的文本文件放在模块路径下的目录下(在本例中为 athenaview/),它将选取它们并创建视图。 这将创建名为 subfolder_filename 的视图,并在文件被删除时销毁它们。

根据之前的回答,下面是一个仅在源文件发生更改时才执行查询的示例。也不是将 SQL 查询粘贴到命令中,而是使用 file:// 适配器将其传递给 AWS CLI 命令。

resource "null_resource" "views" {
  for_each = {
    for filename in fileset("${var.sql_files_dir}/", "**/*.sql") :
    replace(replace(filename, "/", "_"), ".sql", "") => "${var.sql_files_dir}/${filename}"
  }

  triggers = {
    md5 = filemd5(each.value)

    # External references from destroy provisioners are not allowed -
    # they may only reference attributes of the related resource.
    database_name = var.database_name
    s3_bucket_query_output = var.s3_bucket_query_output
  }

  provisioner "local-exec" {
    command = <<EOF
      aws athena start-query-execution \
        --output json \
        --query-string file://${each.value} \
        --query-execution-context "Database=${var.database_name}" \
        --result-configuration "OutputLocation=s3://${var.s3_bucket_query_output}"
EOF
  }

  provisioner "local-exec" {
    when    = destroy
    command = <<EOF
      aws athena start-query-execution \
        --output json \
        --query-string 'DROP VIEW IF EXISTS ${each.key}' \
        --query-execution-context "Database=${self.triggers.database_name}" \
        --result-configuration "OutputLocation=s3://${self.triggers.s3_bucket_query_output}"
EOF
  }
}

要使销毁工作正确,请将文件命名为文件名 - example.sql 与查询相关:

CREATE OR REPLACE VIEW example AS ...