【问题标题】:Create AWS Athena view programmatically以编程方式创建 AWS Athena 视图
【发布时间】:2019-10-10 20:34:55
【问题描述】:

Can you create views in Amazon Athena? 概述了如何使用用户界面创建视图。

我想以编程方式创建 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] 没有提供此选项。

我试过了:

  • create-named-query 我无法为 CREATE OR REPLACE VIEW 之类的语句工作,因为这似乎不是该命令的预期用例。
  • start-query-execution 要求输出位置,这表明这是用于查询数据和输出结果,而不是进行有状态的更改/创建。它似乎也与stop-query-execution配对。

【问题讨论】:

    标签: amazon-web-services terraform amazon-cloudformation amazon-athena terraform-provider-aws


    【解决方案1】:

    正如您所建议的,绝对可以通过 AWS CLI 使用 start-query-execution 以编程方式创建 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"
        }
    }
    
    

    【讨论】:

    • 谢谢京东!太棒了!像魅力一样工作,很棒的文档:D
    • 在我的工作组中设置了查询结果位置,我能够将--result-configuration替换为--work-group
    • 这可以通过 Athena API 完成吗?
    【解决方案2】:

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

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

    这些是 Glue 表作为 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:: 后面有一个空格)
    • 添加一个 base 64 编码的 JSON 字符串,其中包含视图 SQL、列及其类型以及一些目录元数据(见下文)
    • 添加后缀*/* 之前有一个空格)

    描述视图的 JSON 如下所示:

    • catalog 属性的值必须为 awsdatacatalog
    • schema 属性必须是创建视图的数据库的名称(即,它必须与周围 Glue 结构的 DatabaseName 属性匹配。
    • 列的列表,每个列都有nametype
    • 带有实际视图SQL的originalSql属性(不包括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)

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

    【讨论】:

    • 补充:struct Athena 中的列数据类型需要映射到 Presto 定义 JSON 中的 row,例如Terraform/Glue 定义中的 type = "struct<col1:string>" 映射到 Presto 视图定义中的 "type": "row(col1 varchar)"
    • @NathanGriffiths 对,我写的是 struct 而不是 row,现在在我的答案中已修复。
    • 正如我在实施此答案时学到的一些附加说明,希望能对其他人有所帮助。表的所​​有 3 个表示形式中的列必须具有相同的顺序(否则是陈旧的视图)。列必须在 originalSQL 中进行转换,以匹配 presto 列中表示的内容。 (陈旧的观点)。我也误读了答案,并认为 Presto 会为我添加前缀和 base64 编码我的 JSON,但事实并非如此。 originalText = addPrefixSuffix(base64(JSON.stringify(exampleObjectabove)))
    • 令人惊讶的是,仍然没有更好的方法来处理这个问题。我想对视图的内容进行版本控制,然后将其放入 CF 模板中,但此时这似乎过于复杂。如果在 CF 模板中有一些技巧可以很好地做到这一点,我会很感兴趣,但我还没有找到任何东西。
    • 我已将此答案打包为 Terraform 模块,您可以在此处找到:github.com/iconara/terraform-aws-athena-view
    【解决方案3】:

    为了补充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 用于从aws sts assume-role 的输出中解析出必要的字段。

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

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

    【讨论】:

      【解决方案4】:

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

      edit:“int”也必须声明为“integer”!

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

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

      【讨论】:

      • 没问题!很高兴 Theo 的详细回答有帮助!
      • 我已经修正了我的答案,以便在适当的位置显示 varchar。
      【解决方案5】:

      更新上述 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 的视图,并在文件被删除时销毁它们。

      【讨论】:

      • 谢谢@Joshua Samuel 很棒的补充!我相信我们已经在这方面添加了很多文档。
      • 我喜欢这种方法,但是:错误:错误运行命令'aws athena start-query-execution --query-string "CREATE OR REPLACE VIEW Query1 AS SELECT ac, region FROM meta.getresources" - -output json --query-execution-context "Database=meta_resources" --result-configuration "OutputLocation=s3://query-log" ':退出状态 255。输出:用法:aws [options] [ ...] [parameters] aws help Unknown options: REPLACE, VIEW, Query1, AS, SELECT, ac,, region, FROM, meta.getresources", OR ... 但是,如果我复制 SQL从 CMD 输出,它在我的 SQL 客户端中运行
      • @SimonB 您必须将 --query-string 参数值用引号括起来,例如--query-string 'CREATE OR REPLACE VIEW...' 但更好的是让 AWS CLI 加载源文件,而不是在 Terraform 中加载它们:--query-string file://${each.value}
      • @MaciejMajewski 是的,我把它包装好了,用双引号和单引号完成了,同样的错误。还从文件中加载了整个“创建”语句。你在哪个版本? Terraform v0.12.20
      • @SimonB 我在 Terraform v0.12.21 上。很难说,file:// 对我们很有效
      【解决方案6】:

      根据之前的答案,这里有一个示例,仅当源文件发生更改时才会执行查询。此外,它使用file:// 适配器将 SQL 查询粘贴到命令中,而不是将其传递给 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 ...
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-21
        • 2017-05-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多