こんにちは、CX事業本部 IoT事業部の若槻です。
Amazon Athenaで複数のテーブルのデータを結合したい時があるのですが、その際にどのJoin Typeを使用すれば目的の結合方法になるのか、いつも迷ってしまいます。
そこで今回は、Amazon Athenaでの各種Join処理の動作を改めて確認してみました。
やってみた
環境準備
AWS CDK v2(TypeScript)で次のようなCDKスタックを作成します。
lib/process-stack.ts
import { Construct } from 'constructs';
import {
aws_s3,
aws_athena,
RemovalPolicy,
Stack,
StackProps,
} from 'aws-cdk-lib';
import * as glue from '@aws-cdk/aws-glue-alpha';
export class ProcessStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
// データソース格納バケット
const dataSourceBucket = new aws_s3.Bucket(this, 'dataSourceBucket', {
bucketName: `data-source-${this.account}`,
removalPolicy: RemovalPolicy.DESTROY,
});
// マスターデータ格納バケット
const masterDataBucket = new aws_s3.Bucket(this, 'masterDataBucket', {
bucketName: `master-data-${this.account}`,
removalPolicy: RemovalPolicy.DESTROY,
});
// Athenaクエリ結果格納バケット
const athenaQueryResultBucket = new aws_s3.Bucket(
this,
'athenaQueryResultBucket',
{
bucketName: `athena-query-result-${this.account}`,
removalPolicy: RemovalPolicy.DESTROY,
},
);
// データカタログ
const dataCatalog = new glue.Database(this, 'dataCatalog', {
databaseName: 'data_catalog',
});
// データソースカタログテーブル
new glue.Table(this, 'dataSourceGlueTable', {
tableName: 'data_source_glue_table',
database: dataCatalog,
bucket: dataSourceBucket,
s3Prefix: 'data/',
dataFormat: glue.DataFormat.JSON,
columns: [
{
name: 'userId',
type: glue.Schema.STRING,
},
{
name: 'count',
type: glue.Schema.INTEGER,
},
],
});
// マスターデータカタログテーブル
new glue.Table(this, 'masterDataGlueTable', {
tableName: 'master_data_glue_table',
database: dataCatalog,
bucket: masterDataBucket,
s3Prefix: 'data/',
dataFormat: glue.DataFormat.JSON,
columns: [
{
name: 'userId',
type: glue.Schema.STRING,
},
{
name: 'name',
type: glue.Schema.STRING,
},
],
});
// Athenaワークグループ
new aws_athena.CfnWorkGroup(this, 'athenaWorkGroup', {
name: 'athenaWorkGroup',
workGroupConfiguration: {
resultConfiguration: {
outputLocation: `s3://${athenaQueryResultBucket.bucketName}/result-data`,
},
},
recursiveDeleteOption: true,
});
}
}
次のデータを各テーブルのLocationに格納します。
- rawデータテーブル
# | userId | count |
---|---|---|
1 | u001 | 3 |
2 | u001 | 1 |
3 | u002 | 5 |
4 | u002 | 8 |
5 | u003 | 2 |
6 | u999 | 4 |
- masterデータテーブル
# | userId | name |
---|---|---|
1 | u001 | John |
2 | u002 | Bob |
3 | u003 | Tom |
4 | u004 | Peter |
$ cat dataSouce
{"userId":"u001","count":3}
{"userId":"u001","count":1}
{"userId":"u002","count":5}
{"userId":"u002","count":8}
{"userId":"u003","count":2}
{"userId":"u999","count":4}
$ cat masterData
{"userId":"u001","name":"John"}
{"userId":"u002","name":"Bob"}
{"userId":"u003","name":"Tom"}
{"userId":"u004","name":"Peter"}
$ aws s3 cp dataSouce s3://${DATA_SOURCE_BUCKET_NAME}/data/dataSouce
$ aws s3 cp masterData s3://${MASTER_DATA_BUCKET_NAME}/data/masterData
動作確認
次のようなクエリを実行して前述の2つのテーブルデータを結合します。その際に様々なJoin Typeを指定して(ハイライト行)、得られる結果を確認してみます。
WITH
raw AS (
SELECT * FROM data_catalog.data_source_glue_table
),
master AS (
SELECT * FROM data_catalog.master_data_glue_table
)
SELECT
raw.userId, raw.count, master.name
FROM raw
<Join Type> master
ON raw.userId = master.userId
INNER JOIN
INNER JOIN
を使用すると、JOIN句の左辺と右辺のいずれのテーブルにもあるレコードが結合結果として取得されます。
クエリを実行した様子です。
rawおよびmasterのいずれのテーブルにもあるレコードのみ取得できています。
# | userId | count | name |
---|---|---|---|
1 | u001 | 3 | John |
2 | u001 | 1 | John |
3 | u002 | 5 | Bob |
4 | u002 | 8 | Bob |
5 | u003 | 2 | Tom |
ちなみに、単にJOIN
と指定しても既定でINNER JOINとなるので、同じ結果を得ることができます。
LEFT JOIN
LEFT JOIN
を使用すると、JOIN句の左辺のテーブルのみにあるレコードが結合結果として取得されます。
クエリを実行した様子です。
左辺のrawテーブルにあるレコードのみ取得できています。
# | userId | count | name |
---|---|---|---|
1 | u001 | 3 | John |
2 | u001 | 1 | John |
3 | u002 | 5 | Bob |
4 | u002 | 8 | Bob |
5 | u003 | 2 | Tom |
6 | u999 | 4 |
ちなみにLEFT JOIN
はLEFT OUTER JOIN
の略となり、いずれの指定でも同じ結果を得ることができます。
RIGHT JOIN
RIGHT JOIN
を使用すると、JOIN句の右辺のテーブルのみにあるレコードが結合結果として取得されます。
クエリを実行した様子です。
右辺のmasterテーブルにあるレコードのみ取得できています。
# | userId | count | name |
---|---|---|---|
1 | u002 | 5 | Bob |
2 | u002 | 8 | Bob |
3 | u001 | 3 | John |
4 | u001 | 1 | John |
5 | u003 | 2 | Tom |
6 | Peter |
ちなみにRIGHT JOIN
についてもRIGHT OUTER JOIN
の略となり、いずれの指定でも同じ結果を得ることができます。
FULL JOIN
FULL JOIN
を使用すると、JOIN句の右辺および左辺テーブル上のすべてのレコードが結合結果として取得されます。
クエリを実行した様子です。
左辺のrawまたは右辺のmasterのテーブルにあるレコードがすべて取得できています。
# | userId | count | name |
---|---|---|---|
1 | u002 | 5 | Bob |
2 | u002 | 8 | Bob |
3 | u999 | 4 | |
4 | u001 | 3 | John |
5 | u001 | 1 | John |
6 | u003 | 2 | Tom |
7 | Peter |
ベン図で表した場合
INNER JOIN(JOIN)、FULL JOIN、LEFT JOIN、RIGHT JOINをそれぞれベン図で表すと次のようになります。一目瞭然で覚えやすいですね。
types - SQL JOIN - Dofactoryより
おわりに
Amazon Athenaでの各種Join処理の動作を改めて確認してみました。
これでもうJOINも怖くありませんね。
参考
以上