Можно ли создавать представления в 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.
Код терраформирования выглядит так:
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, однако aws athena [option] не предоставляет для этого возможности.
Я пробовал:





Как вы предположили, определенно возможно программно создать представление Athena через интерфейс командной строки AWS с помощью start-query-execution. Как вы указали, это требует от вас указать местоположение S3 для результатов, даже если вам не нужно будет проверять файл (по какой-то причине Athena поместит в это место пустой текстовый файл).
Вот пример:
$ 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.
$ 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"
}
}
С Местоположение результата запроса, установленным на моем рабочая группа, я смог заменить --result-configuration на --work-group.
Можно ли это сделать через Athena API?
Программное создание представлений в Athena не документировано и не поддерживается, но возможно. Что происходит за кулисами, когда вы создаете представление с помощью StartQueryExecution, так это то, что Athena позволяет Presto создать представление, а затем извлекает внутреннее представление Presto и помещает его в каталог Glue.
Проблема устаревания обычно возникает из-за того, что столбцы в метаданных Presto и метаданные Glue не синхронизированы. Представление Athena действительно содержит три описания представления: представление SQL, столбцы и их типы в формате Glue, а также столбцы и типы в формате Presto. Если какой-либо из них выйдет из строя, вы получите сообщение «… устарело; его необходимо создать заново». ошибка.
Вот требования к таблице Glue для работы в качестве представления Athena:
TableType должно быть VIRTUAL_VIEWParameters должен содержать presto_view: trueTableInput.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: (с пробелом после :)*/ (с пробелом перед *)JSON, описывающий представление, выглядит так:
catalog, которое должно иметь значение awsdatacatalog.schema, которое должно быть именем базы данных, в которой создается представление (т. е. оно должно соответствовать свойству DatabaseName окружающей структуры Glue.name и typeoriginalSql с фактическим представлением 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).
Это довольно запутанно, и для того, чтобы собрать все это воедино, нужно немного повозиться и протестировать. Самый простой способ заставить его работать — создать несколько представлений и декодировать приведенные выше инструкции в обратном порядке, чтобы увидеть, как они выглядят, а затем попробовать сделать это самостоятельно.
Чтобы добавить к этому: struct типы данных столбца в Athena должны быть сопоставлены с row в определении Presto JSON, например. type = "struct<col1:string>" в определении Terraform/Glue отображается на "type": "row(col1 varchar)" в определении представления Presto.
@NathanGriffiths, вы правы, я написал struct вместо row, теперь исправлено в моем ответе.
Так же, как пара дополнительных заметок из моих знаний при реализации этого ответа, которые, надеюсь, помогут другим. Столбцы во всех трех представлениях таблицы должны быть в одном порядке (в противном случае вид будет устаревшим). Столбцы должны быть отлиты в originalSQL, чтобы соответствовать обозначениям в предварительных столбцах. (устаревший вид). Я также неправильно прочитал ответ и подумал, что Presto добавит префикс, а base64 закодирует мой JSON для меня, но это не так. originalText = addPrefixSuffix(base64(JSON.stringify(exampleObjectabove)))
Удивительно, что до сих пор нет лучшего способа справиться с этим. Я хочу управлять версиями содержимого моего представления, а затем поместить его в шаблон CF, но на данный момент это кажется слишком сложным. Если есть какой-то трюк, чтобы сделать это красиво в шаблоне CF, мне было бы интересно, но я пока ничего не нашел.
Я упаковал этот ответ как модуль Terraform, который вы можете найти здесь: github.com/iconara/terraform-aws-athena-view
Чтобы добавить ответы JD D и Theo, работая с их решениями, мы выяснили, как вызывать AWS Cli через terraform следующим образом:
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 взять на себя роль.
Затем AWS Athena запуск-запрос-выполнение может выполняться под ролью, указанной определенными переменными среды.
Вместо --result-configuration "OutputLocation=s3://.... можно указать --work-group. ОБРАТИТЕ ВНИМАНИЕ, что это отдельный флаг для start-query-execution, а не часть строки --result-configuration.
Дополнение к ответу Тео: в файле JSON с кодировкой base64 тип «строка» недействителен при определении атрибутов cloumn! Всегда пишите «varchar» в этот момент.
редактировать: также "int" должен быть объявлен как "целое число"!
Я выбрал решение Тео, и оно работало с использованием шаблонов AWS Cloud Formation.
Я просто хотел добавить небольшую подсказку, которая может сэкономить вам часы отладки. Я не пишу это как комментарий, потому что у меня пока нет прав комментировать. Не стесняйтесь копировать и вставлять это в раздел комментариев ответа Тео.
Без проблем! Рад, что очень подробный ответ Тео помог!
Я исправил свой ответ, так что он говорит varchar в соответствующем месте.
Обновление приведенных выше примеров для синтаксиса 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 "СОЗДАТЬ ИЛИ ЗАМЕНИТЬ ПРОСМОТР Query1 AS SELECT ac, region FROM meta.getresources" --output json --query-execution -context "Database=meta_resources" --result-configuration "OutputLocation=s3://query-log" ': статус выхода 255. Вывод: использование: aws [опции] <команда> <подкоманда> [<подкоманда> ... ] [параметры] aws help Неизвестные параметры: REPLACE, VIEW, Query1, AS, SELECT, ac,, region, FROM, meta.getresources", ИЛИ ... однако, если я скопирую SQL из вывода CMD, он запустится в моем SQL-клиент
@SimonB вы должны заключить значение параметра --query-string в кавычки, например. --query-string 'CREATE OR REPLACE VIEW...' но еще лучше заставить AWS CLI загружать исходные файлы, а не загружать их в Terraform: --query-string file://${each.value}
@ MaciejMajewski Да, я завернул его, сделал с двойными и одинарными кавычками, та же ошибка. Также загружен весь оператор «Создать» из файла. На какой ты версии? Терраформ v0.12.20
@SimonB Я использую Terraform v0.12.21. Трудно сказать, с file:// у нас это работает хорошо
Работает на меня, но только в первый раз, когда он создан. когда я удалил вид из Афины и попытался воссоздать его, этого не произошло. есть проблема с null_resource? это работает только один раз?
Основываясь на предыдущих ответах, вот пример, который будет выполнять запросы, только если исходный файл изменился. Кроме того, вместо того, чтобы вставлять 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 ...
Спасибо, ДжейДи! Это потрясающе! Работал как шарм, отличная документация: D