

# ユーザー定義関数を使用してクエリする
<a name="querying-udf"></a>

Amazon Athena のユーザー定義関数 (UDF) を使用すると、レコードまたはレコードのグループを処理するためのカスタム関数を作成できます。UDF は、パラメータを受け入れ、作業を実行し、結果を返します。

Athena で UDF を使用するには、SQL クエリの `USING EXTERNAL FUNCTION` ステートメントの前に `SELECT` 句を記述します。この `SELECT` ステートメントは UDF を参照し、クエリの実行時に、UDF に渡される変数を定義します。SQL クエリは UDF を呼び出すときに、Java ランタイムを使用して Lambda 関数を呼び出します。UDF は、Java デプロイパッケージのメソッドとして Lambda 関数内で定義されます。Lambda 関数の同じ Java デプロイパッケージで複数の UDF を定義できます。また、`USING EXTERNAL FUNCTION` 句で Lambda 関数の名前も指定します。

Athena UDF 用の Lambda 関数のデプロイには 2 つのオプションがあり、Lambda を使用して直接関数をデプロイすることも、AWS Serverless Application Repository を使用することもできます。UDF の既存の Lambda 関数を検索するには、パブリック AWS Serverless Application Repository またはプライベートリポジトリを検索し、Lambda にデプロイします。また、Java ソースコードを作成または変更して、JAR ファイルにパッケージ化し、Lambda または AWS Serverless Application Repository を使用してデプロイすることもできます。これを開始するための Java ソースコードとパッケージの例については、「[Lambda を利用して UDF を作成およびデプロイする](udf-creating-and-deploying.md)」を参照してください。Lambda の詳細については、[AWS Lambda デベロッパーガイド](https://docs.aws.amazon.com/lambda/latest/dg/)を参照してください。AWS Serverless Application Repository の詳細については、[AWS Serverless Application Repository 開発者ガイド](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/)を参照してください。

Athena で UDF を使用してテキストを翻訳および分析する例については、AWS Machine Learning ブログ記事「[Amazon Athena、Amazon Translate、および Amazon Comprehend で SQL 関数を使用してテキストを翻訳および分析する](https://aws.amazon.com/blogs/machine-learning/translate-and-analyze-text-using-sql-functions-with-amazon-athena-amazon-translate-and-amazon-comprehend/)」を参照、または [video](udf-videos.md#udf-videos-xlate) をご覧ください。

UDF を使用して Amazon Athena で地理空間クエリを拡張する例については、*AWS Big Data Blog* の「[Extend geospatial queries in Amazon Athena with UDFs and AWS Lambda](https://aws.amazon.com/blogs/big-data/extend-geospatial-queries-in-amazon-athena-with-udfs-and-aws-lambda/)」を参照してください。

**Topics**
+ [Athena の UDF の動画](udf-videos.md)
+ [考慮事項と制限事項](udf-considerations-limitations.md)
+ [UDF クエリ構文を使用してクエリする](udf-query-syntax.md)
+ [Lambda を利用して UDF を作成およびデプロイする](udf-creating-and-deploying.md)

# Athena の UDF の動画
<a name="udf-videos"></a>

Athena での UDF の使用に関する詳細については、以下の動画をご覧ください。

**動画: Amazon Athena のユーザー定義関数 (UDF) の概要**  
以下の動画は、Amazon Athena で UDF を使用して、機密情報のリダクションを行う方法を紹介しています。

**注記**  
この動画で示されている構文はリリース前のものですが、概念は同じです。`AmazonAthenaPreviewFunctionality` ワークグループなしで Athena を使用してください。

[![AWS Videos](http://img.youtube.com/vi/https://www.youtube.com/embed/AxJ6jP4Pfmo/0.jpg)](http://www.youtube.com/watch?v=https://www.youtube.com/embed/AxJ6jP4Pfmo)


**動画:Amazon Athena で SQL クエリを使用したテキストフィールドの翻訳、分析、およびリダクション**  
以下の動画は、その他の AWS のサービス と共に Amazon Athena の UDF を使用して、テキストを翻訳し、分析する方法を紹介しています。

**注記**  
この動画で示されている構文はリリース前のものですが、概念は同じです。正しい構文については、*AWS Machine Learning ブログ*の関連するブログ記事「[Amazon Athena、Amazon Translate、および Amazon Comprehend による SQL 関数を使用したテキストの翻訳、修正、分析](https://aws.amazon.com/blogs/machine-learning/translate-and-analyze-text-using-sql-functions-with-amazon-athena-amazon-translate-and-amazon-comprehend/)」を参照してください。

[![AWS Videos](http://img.youtube.com/vi/https://www.youtube.com/embed/Od7rXG-WMO4/0.jpg)](http://www.youtube.com/watch?v=https://www.youtube.com/embed/Od7rXG-WMO4)


# 考慮事項と制限事項
<a name="udf-considerations-limitations"></a>

Athena でユーザー定義関数 (UDF) を使用する際には、次の点を考慮してください。
+ **組み込み Athena 関数** – Athena の組み込み関数は、高い性能を発揮するように設計されています。可能な場合は、UDF よりも組み込み関数を使用することをお勧めします。組み込み関数の詳細については、「[Amazon Athena の関数](functions.md)」を参照してください。
+ **スカラー UDF 限定** – Athena は、一度に 1 行を処理し、単一の列値を返すスカラー UDF のみをサポートします。Athena は、Lambda を呼び出すたびに行のバッチを UDF に渡しましますが、これは並行的に行われる可能性があります。UDF とクエリを設計するときは、この処理がネットワークトラフィックに及ぼす可能性がある影響に注意する必要があります。
+ **UDF ハンドラ関数は省略形式を使用** – UDF 関数には、省略形式 (フルフォーマットではない) を使用します (例:`package.Class::method` の代わりに `package.Class`)。
+ **UDF メソッドは小文字を使用する必要がある** – UDF メソッドは小文字にする必要があります。キャメルケースは使用できません。
+ **パラメータを必要とする UDF メソッド** — UDF メソッドには少なくとも 1 つの入力パラメータが必要です。入力パラメータなしで定義された UDF を呼び出そうとすると、ランタイム例外が発生する原因となります。UDF はデータレコードに対して関数を実行するためのものですが、引数のない UDF はデータを取り込まないため、例外が発生します。
+ **Java ランタイムサポート** – 現在、Athena UDF は Lambda について Java 8、Java 11、Java 17 のランタイムをサポートしています。詳細については、*AWS Lambda デベロッパーガイド*の「[Java による Lambda 関数のビルド](https://docs.aws.amazon.com/lambda/latest/dg/lambda-java.html)」を参照してください。
**注記**  
 Java 17 の場合、`JAVA_TOOL_OPTIONS` 環境変数の値を Lambda の `--add-opens=java.base/java.nio=ALL-UNNAMED` に設定する必要があります。
+ **IAM 許可** – Athena で UDF クエリステートメントを作成して実行するには、クエリを実行する IAM プリンシパルに、Athena 関数以外のアクションを実行することが許可されている必要があります。詳細については、「[Athena UDF へのアクセスを許可する: ポリシーの例](udf-iam-access.md)」を参照してください。
+ **Lambda のクォータ** – UDF には Lambda のクォータが適用されます。詳細については、*AWS Lambda デベロッパーガイド*の「[Lambda のクォータ](https://docs.aws.amazon.com/lambda/latest/dg/limits.html)」を参照してください。
+ **行レベルのフィルタリング** – UDF では、Lake Formation の行レベルのフィルタリングはサポートされていません。
+ **ビュー** – UDF でビューを使用することはできません。
+ **既知の問題** – 既知の問題に関する最新のリストについては、GitHub の awslabs/aws-athena-query-federation セクションにある「[Limitations and Issues](https://github.com/awslabs/aws-athena-query-federation/wiki/Limitations_And_Issues)」(制限と問題) を参照してください。

# UDF クエリ構文を使用してクエリする
<a name="udf-query-syntax"></a>

`USING EXTERNAL FUNCTION` 句は、クエリで後続の `SELECT` ステートメントで参照できる UDF または複数の UDF を指定します。UDF のメソッド名と UDF をホストする Lambda 関数の名前が必要です。Lambda 関数名の代わりに、Lambda ARN を使用できます。クロスアカウントのシナリオでは、Lambda ARN が必要です。

## 概要
<a name="udf-synopsis"></a>

```
USING EXTERNAL FUNCTION UDF_name(variable1 data_type[, variable2 data_type][,...])
RETURNS data_type
LAMBDA 'lambda_function_name_or_ARN'
[, EXTERNAL FUNCTION UDF_name2(variable1 data_type[, variable2 data_type][,...]) 
RETURNS data_type 
LAMBDA 'lambda_function_name_or_ARN'[,...]]
SELECT  [...] UDF_name(expression) [, UDF_name2(expression)] [...]
```

## パラメータ
<a name="udf-parameters"></a>

**USING EXTERNAL FUNCTION *UDF\$1name*(*variable1* *data\$1type*[, *variable2* *data\$1type*][,...])**  
*UDF\$1name* は UDF の名前を指定します。この名前は、参照される Lambda 関数内の Java メソッドに対応している必要があります。各 *variable data\$1type* は、UDF が入力として受け入れる名前付きの変数とそれに対応するデータ型を指定します。*data\$1type* は、以下の表に記載されているサポート対象の Athena データ型の 1 つであり、対応する Java データ型にマップする必要があります。      
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/athena/latest/ug/udf-query-syntax.html)

**RETURNS *data\$1type***  
`data_type` は、UDF が出力として返す SQL データ型を指定します。上記の表にリストされている Athena データ型がサポートされます。`DECIMAL` データ型の場合、この構文 `RETURNS DECIMAL(precision, scale)` を使います。*精度*と*スケール*は整数です。

**LAMBDA '*lambda\$1function*'**  
*lambda\$1function* は、UDF の実行時に呼び出される Lambda 関数の名前を指定します。

**SELECT [...] *UDF\$1name*(*expression*) [...]**  
UDF に値を渡し、結果を返す `SELECT` クエリです。*UDF\$1Name* は使用する UDF を指定し、値を渡すために評価される *expression* がその後に続きます。渡される値と返される値は、`USING EXTERNAL FUNCTION` 句で UDF に指定された対応するデータ型と一致する必要があります。

### 例
<a name="udf-examples"></a>

GitHub にある [AthenaUDFHandler.java](https://github.com/awslabs/aws-athena-query-federation/blob/master/athena-udfs/src/main/java/com/amazonaws/athena/connectors/udfs/AthenaUDFHandler.java) コードに基づいたクエリの例については、GitHub の「[Amazon Athena UDF connector](https://github.com/awslabs/aws-athena-query-federation/tree/master/athena-udfs)」(Amazon Athena UDF コネクタ) ページを参照してください。

# Lambda を利用して UDF を作成およびデプロイする
<a name="udf-creating-and-deploying"></a>

カスタム UDF を作成するには、`UserDefinedFunctionHandler` クラスを拡張して新しい Java クラスを作成します。SDK の [UserDefinedFunctionHandler.java](https://github.com/awslabs/aws-athena-query-federation/blob/master/athena-federation-sdk/src/main/java/com/amazonaws/athena/connector/lambda/handlers/UserDefinedFunctionHandler.java) のソースコードは、GitHub の awslabs/aws-athena-query-federation/athena-federation-sdk [リポジトリ](https://github.com/awslabs/aws-athena-query-federation/tree/master/athena-federation-sdk)で入手できます。このコードには、検証および変更して UDF を作成するために使用できる [UDF 実装例](https://github.com/awslabs/aws-athena-query-federation/tree/master/athena-udfs)が付属しています。

このセクションの手順では、コマンドラインとデプロイから [Apache Maven](https://maven.apache.org/index.html) を使用してカスタム UDF Jar ファイルを記述し、構築する方法を示します。

Maven を使用して Athena のカスタム UDF を作成するには、次のステップを実行します。

1. [SDK のクローン作成と開発環境の準備](#udf-create-install-sdk-prep-environment)

1. [Maven プロジェクトの作成](#create-maven-project)

1. [Maven プロジェクトへの依存関係とプラグインの追加](#udf-add-maven-dependencies)

1. [UDF に Java コードを記述](#udf-write-java)

1. [JAR ファイルのビルド](#udf-create-package-jar)

1. [JAR を AWS Lambda にデプロイする](#udf-create-deploy)

## SDK のクローン作成と開発環境の準備
<a name="udf-create-install-sdk-prep-environment"></a>

始める前に、`sudo yum install git -y` を使用して git がシステムにインストールされていることを確認してください。

**AWS クエリフェデレーション SDK をインストールするには**
+ コマンドラインで次のコマンドを入力して、SDK リポジトリのクローンを作成します。このリポジトリには、SDK、例、データソースコネクタのスイートが含まれています。データソースコネクタの詳細については、「[Amazon Athena フェデレーティッドクエリを使用する](federated-queries.md)」を参照してください。

  ```
  git clone https://github.com/awslabs/aws-athena-query-federation.git
  ```

**この手順の前提条件をインストールする**

Apache Maven、AWS CLI、および AWS Serverless Application Model ビルドツールが既にインストールされている開発マシンで作業している場合は、このステップをスキップできます。

1. クローン作成時に作成した `aws-athena-query-federation` ディレクトリのルートから、開発環境を準備する [prepare\$1dev\$1env.sh](https://github.com/awslabs/aws-athena-query-federation/blob/master/tools/prepare_dev_env.sh) スクリプトを実行します。

1. インストールプロセスによって作成された新しい変数を調達するようにシェルを更新するか、ターミナルセッションを再起動します。

   ```
   source ~/.profile
   ```
**重要**  
このステップをスキップすると、後で AWS CLI または AWS SAM ビルドツールが Lambda 関数を公開できないというエラーが表示されます。

## Maven プロジェクトの作成
<a name="create-maven-project"></a>

次のコマンドを実行して、Maven プロジェクトを作成します。*groupId* を組織の一意の ID に置き換え、*my-athena-udf* をアプリケーション名に置き換えます。詳細については、Apache Maven ドキュメントの「[How do I make my first Maven project?](https://maven.apache.org/guides/getting-started/index.html#How_do_I_make_my_first_Maven_project)」を参照してください。

```
mvn -B archetype:generate \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DgroupId=groupId \
-DartifactId=my-athena-udfs
```

## Maven プロジェクトへの依存関係とプラグインの追加
<a name="udf-add-maven-dependencies"></a>

Maven プロジェクト `pom.xml` ファイルに次の設定を追加します。例については、GitHub の [pom.xml](https://github.com/awslabs/aws-athena-query-federation/blob/master/athena-udfs/pom.xml) ファイルを参照してください。

```
<properties>
    <aws-athena-federation-sdk.version>2022.47.1</aws-athena-federation-sdk.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-athena-federation-sdk</artifactId>
        <version>${aws-athena-federation-sdk.version}</version>
    </dependency>
</dependencies>
    
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.2.1</version>
            <configuration>
                <createDependencyReducedPom>false</createDependencyReducedPom>
                <filters>
                    <filter>
                        <artifact>*:*</artifact>
                        <excludes>
                            <exclude>META-INF/*.SF</exclude>
                            <exclude>META-INF/*.DSA</exclude>
                            <exclude>META-INF/*.RSA</exclude>
                        </excludes>
                    </filter>
                </filters>
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
```

## UDF に Java コードを記述
<a name="udf-write-java"></a>

[UserDefinedFunctionHandler.java](https://github.com/awslabs/aws-athena-query-federation/blob/master/athena-federation-sdk/src/main/java/com/amazonaws/athena/connector/lambda/handlers/UserDefinedFunctionHandler.java) を拡張して、新しいクラスを作成します。クラス内に UDF を記述します。

次の例では、UDF の 2 つの Java メソッド、`compress()` および `decompress()` がクラス `MyUserDefinedFunctions` 内に作成されます。

```
*package *com.mycompany.athena.udfs;

public class MyUserDefinedFunctions
        extends UserDefinedFunctionHandler
{
    private static final String SOURCE_TYPE = "MyCompany";

    public MyUserDefinedFunctions()
    {
        super(SOURCE_TYPE);
    }

    /**
     * Compresses a valid UTF-8 String using the zlib compression library.
     * Encodes bytes with Base64 encoding scheme.
     *
     * @param input the String to be compressed
     * @return the compressed String
     */
    public String compress(String input)
    {
        byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);

        // create compressor
        Deflater compressor = new Deflater();
        compressor.setInput(inputBytes);
        compressor.finish();

        // compress bytes to output stream
        byte[] buffer = new byte[4096];
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(inputBytes.length);
        while (!compressor.finished()) {
            int bytes = compressor.deflate(buffer);
            byteArrayOutputStream.write(buffer, 0, bytes);
        }

        try {
            byteArrayOutputStream.close();
        }
        catch (IOException e) {
            throw new RuntimeException("Failed to close ByteArrayOutputStream", e);
        }

        // return encoded string
        byte[] compressedBytes = byteArrayOutputStream.toByteArray();
        return Base64.getEncoder().encodeToString(compressedBytes);
    }

    /**
     * Decompresses a valid String that has been compressed using the zlib compression library.
     * Decodes bytes with Base64 decoding scheme.
     *
     * @param input the String to be decompressed
     * @return the decompressed String
     */
    public String decompress(String input)
    {
        byte[] inputBytes = Base64.getDecoder().decode((input));

        // create decompressor
        Inflater decompressor = new Inflater();
        decompressor.setInput(inputBytes, 0, inputBytes.length);

        // decompress bytes to output stream
        byte[] buffer = new byte[4096];
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(inputBytes.length);
        try {
            while (!decompressor.finished()) {
                int bytes = decompressor.inflate(buffer);
                if (bytes == 0 && decompressor.needsInput()) {
                    throw new DataFormatException("Input is truncated");
                }
                byteArrayOutputStream.write(buffer, 0, bytes);
            }
        }
        catch (DataFormatException e) {
            throw new RuntimeException("Failed to decompress string", e);
        }

        try {
            byteArrayOutputStream.close();
        }
        catch (IOException e) {
            throw new RuntimeException("Failed to close ByteArrayOutputStream", e);
        }

        // return decoded string
        byte[] decompressedBytes = byteArrayOutputStream.toByteArray();
        return new String(decompressedBytes, StandardCharsets.UTF_8);
    }
}
```

## JAR ファイルのビルド
<a name="udf-create-package-jar"></a>

`mvn clean install` を実行して、プロジェクトをビルドします。正常にビルドされると、JAR ファイルが `target` という名前のプロジェクトの `artifactId-version.jar` フォルダに作成されます。ここで、*artifactId* は Maven プロジェクトで指定した名前（例: `my-athena-udfs`）です。

## JAR を AWS Lambda にデプロイする
<a name="udf-create-deploy"></a>

コードを Lambda にデプロイする、次の 2 つの方法があります。
+ AWS Serverless Application Repository を使用してデプロイする（推奨）
+ JAR ファイルから Lambda 関数を作成する

### オプション 1: AWS Serverless Application Repository にデプロイする
<a name="udf-create-deploy-sar"></a>

JAR ファイルを AWS Serverless Application Repository にデプロイするときは、アプリケーションのアーキテクチャを表す AWS SAM テンプレート YAML ファイルを作成します。次に、この YAML ファイルと、アプリケーションのアーティファクトがアップロードされて AWS Serverless Application Repository に対して利用可能になる Simple Storage Service (Amazon S3) バケットを指定します。以下の手順では、先ほどクローンした Athena Query Federation SDK の `athena-query-federation/tools` ディレクトリにある [publish.sh](https://github.com/awslabs/aws-athena-query-federation/blob/master/tools/publish.sh) スクリプトを使用します。

詳細と要件については、「AWS Serverless Application Repository デベロッパーガイド」の「[アプリケーションの公開](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/serverlessrepo-publishing-applications.html)」、「AWS Serverless Application Model デベロッパーガイド」の「[AWS SAM テンプレートの概念](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html)」、および「[AWS SAM CLI を使用してサーバーレスアプリケーションを公開する](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-publishing-applications.html)」を参照してください。

次の例は、YAML ファイルのパラメータを示しています。同様のパラメータを YAML ファイルに追加し、プロジェクトディレクトリに保存します。完全な例については、GitHub の [athena-udf.yaml](https://github.com/awslabs/aws-athena-query-federation/blob/master/athena-udfs/athena-udfs.yaml) を参照してください。

```
Transform: 'AWS::Serverless-2016-10-31'
Metadata:
  'AWS::ServerlessRepo::Application':
    Name: MyApplicationName
    Description: 'The description I write for my application'
    Author: 'Author Name'
    Labels:
      - athena-federation
    SemanticVersion: 1.0.0
Parameters:
  LambdaFunctionName:
    Description: 'The name of the Lambda function that will contain your UDFs.'
    Type: String
  LambdaTimeout:
    Description: 'Maximum Lambda invocation runtime in seconds. (min 1 - 900 max)'
    Default: 900
    Type: Number
  LambdaMemory:
    Description: 'Lambda memory in MB (min 128 - 3008 max).'
    Default: 3008
    Type: Number
Resources:
  ConnectorConfig:
    Type: 'AWS::Serverless::Function'
    Properties:
      FunctionName: !Ref LambdaFunctionName
      Handler: "full.path.to.your.handler. For example, com.amazonaws.athena.connectors.udfs.MyUDFHandler"
      CodeUri: "Relative path to your JAR file. For example, ./target/athena-udfs-1.0.jar"
      Description: "My description of the UDFs that this Lambda function enables."
      Runtime: java8
      Timeout: !Ref LambdaTimeout
      MemorySize: !Ref LambdaMemory
```

YAML ファイルを保存したプロジェクトディレクトリに `publish.sh` スクリプトをコピーし、次のコマンドを実行します。

```
./publish.sh MyS3Location MyYamlFile
```

たとえば、バケットの場所が `s3://amzn-s3-demo-bucket/mysarapps/athenaudf` で、YAML ファイルが `my-athena-udfs.yaml` として保存された場合:

```
./publish.sh amzn-s3-demo-bucket/mysarapps/athenaudf my-athena-udfs
```

**Lambda 関数を作成するには**

1. [https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/) で Lambda コンソールを開いて [**Create function**] (関数の作成) をクリックし、[**Browse serverless app repository**] (Serverless Application Repository の参照) を選択します。

1. [**プライベートアプリケーション**] を選択し、リストでアプリケーションを見つけるか、キーワードを使用してアプリケーションを検索して選択します。

1. アプリケーションの詳細を確認して指定し、[**デプロイ**] を選択します。

   これで、Lambda 関数の JAR ファイルで定義されたメソッド名を Athena で UDF として使用できるようになりました。

### オプション 2: Lambda 関数を直接作成する
<a name="udf-create-deploy-lambda"></a>

コンソールまたは AWS CLI を使用して、Lambda 関数を直接作成することもできます。以下の例は、Lambda `create-function` CLI コマンドを使用する方法を示しています。

```
aws lambda create-function \
 --function-name MyLambdaFunctionName \
 --runtime java8 \
 --role arn:aws:iam::1234567890123:role/my_lambda_role \
 --handler com.mycompany.athena.udfs.MyUserDefinedFunctions \
 --timeout 900 \
 --zip-file fileb://./target/my-athena-udfs-1.0-SNAPSHOT.jar
```