以编程方式创建AWS Athena视图

时间:2019-05-24 09:04:28

标签: amazon-cloudformation terraform aws-cli amazon-athena terraform-provider-aws

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

我想以编程方式创建一个AWS Athena View,理想情况下是使用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 之类的语句,因为这似乎不是该命令的预期用例。 / li>
  • start-query-execution要求输出位置,这表明它是用于查询数据并输出结果,而不是进行有状态的更改/创建。它似乎也与stop-query-execution配对。

6 个答案:

答案 0 :(得分:3)

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

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

这些是在胶水表上用作雅典娜视图的要求:

  • 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:(注意尾随空格)
  • 添加一个基本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(string),并且struct<foo:int>变为struct(foo int)

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

答案 1 :(得分:2)

为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的视图,并在删除文件后销毁它们。

答案 2 :(得分:2)

根据先前的回答,这是一个仅在源文件已更改的情况下执行查询的示例。此外,它使用file://适配器将SQL查询粘贴到命令中,而不是将SQL查询粘贴到命令中。

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 ...

答案 3 :(得分:1)

正如您所建议的那样,绝对有可能使用package game; import javax.swing.JFrame; import javax.swing.JPanel; public class runner extends JPanel { static MyKeyListener list; static player play; static grid gameGrid; static int size = 600; public runner() { MyKeyListener listener = new MyKeyListener(this); player one = new player(5, 4); grid Grid = new grid(10, 10, size, one); play = one; gameGrid = Grid; list = listener; this.addKeyListener(listener); setFocusable(true); } public static void main(String[] args) { runner runn = new runner(); JFrame frame = new JFrame("game"); int frameSize = size + (size / 2); frame.setSize(size, size); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); frame.add(runn); frame.addKeyListener(list); boolean isRunning = true; while (isRunning) { drawingComponent DC = gameGrid.update(); if (list.getDir() == 1) { gameGrid.moveUp(play, play.getX(), play.getY()); list.setDir(0); } if (list.getDir() == 2) { gameGrid.moveDown(play, play.getX(), play.getY()); list.setDir(0); } if (list.getDir() == 3) { gameGrid.moveLeft(play, play.getX(), play.getY()); list.setDir(0); } if (list.getDir() == 4) { gameGrid.moveRight(play, play.getX(), play.getY()); list.setDir(0); } frame.add(DC); frame.revalidate(); frame.repaint(); } } public player getPlayer() { return play; } } 通过AWS CLI以编程方式创建Athena视图。正如您所指出的那样,这确实需要您为结果提供S3位置,即使您不需要检查文件(由于某些原因,Athena也会在该位置放置一个空的txt文件)。

这里是一个例子:

start-query-execution

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

指定存储桶

您可以使用$ 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" } 命令检查视图创建是否成功。

get-query-execution

答案 4 :(得分:1)

Theo的回答:在base64编码的JSON文件中,定义cloumn属性时,“字符串”类型无效!始终在此时写“ varchar”。

edit:同样,必须将“ int”声明为“ integer”!

我采用了Theo的解决方案,并且使用了AWS Cloud Formation模板。

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

答案 5 :(得分:0)

要添加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然后可以在定义的环境变量指定的角色下执行。

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