i-Vinci TechBlog
株式会社i-Vinciの技術ブログ

PartiQL触ってみた

皆さんこんにちは!
i-Vinciの元自衛官エンジニア、藤田です。社内では社員の体力錬成を担当しています。
今回はPartiQLを触ってみたので記事にしたいと思います。

PartiQLとは

PartiQLのWebサイトの記載によると以下のようになります。

PartiQL is a query language that provides SQL-compatible query access across multiple data stores containing structured data, semi-structured data, and nested data. It is widely used within Amazon and now available as part of many AWS services (Amazon S3 Select, Amazon Glacier Select, Amazon Redshift Spectrum, Amazon Quantum Ledger Database) with more AWS services and third party database engines to follow.

超訳すると...

  1. 構造化データ、半構造化データ、ネストデータを含む様々なデータストアへの、SQLと互換のあるクエリによるアクセスを提供する
  2. AWSでめっちゃ使われている

データストアの違いによらずSQLを使用出来るということですね。
また、2に関しては上記のWebサイトでも記載されていますが、2020年11月23日にはAWS DynamoDBがサポートを開始、2020年12月1日にはAWS Glue Elastic Viewsで採用されることが発表されています。(2020/12/04時点)
つまり、DynamoDBでもSQLを使用してデータを取り扱うことが出来るようになるということなので、今後AWSでデータを扱うときは何らかの形で使用することになりそうですね。

因みにGitHub(partiql)の状態はこんな感じ↓
GitHub
Kotlinで書かれているみたいですね。ちょっと面白そう(JVM脳)。

きっかけ

PartiQLを触ろうと思ったきっかけはAWS Glue Elastic Views(以下GEV)というサービスです。
AWS re:Invent 2020にて、AWS GlueのComplementなサービスとしてAWS GEVがPreviewとして発表されました。
GEVの概要は、ここ(英語)に記載してあります。
なんかかっこいいサービス紹介動画はこちらです。

AWS GEVの特徴は以下のようにまとめられると思います。

Aurora, DynamoDB, RDSからSQL(ライクな言語)でViewを作って、S3を始めとする各種Data Storeにレプリケートできる

「なんかいいやん」と思ったので早速Previewでの利用をAWSに申し込みました。(受理されると嬉しいな、ふふん)
Previewに申し込んだ後にさらに調べていると、どうやら"SQLライクな言語"というのが「PartiQL」というクエリ言語であることが分かりました。ではどんなものだろうね、ということで触ってみることにしました。

さて前置きはこれくらいにして実際にPartiQLを使っていきましょう。

実践

まずは、公式ページのGetting Startedに従って進めます。

環境構築

  1. Javaをインストール
    PartiQLは、JVM上で動くためJavaのインストールを必要とします。ページ内のリンクからOpenJDKなどをインストールしましょう。

  2. PartiQLをダウンロード
    releaseページから圧縮ファイルをダウンロードします。
    この時に利用するバージョンも確認できます。私がこの記事を書いている時点(12/05)では、v0.2.4-alphaが最新のリリースとなっています。

  3. ダウンロードした圧縮ファイルを任意のフォルダに展開します。

以上の作業が完了すると、圧縮ファイルを展開した先のフォルダにbin、lib、Tutorialのフォルダ、及びReadmeが展開されます。

それぞれのフォルダについて少し説明します。

  • binフォルダ
    PartiQLをREPL(対話形式)で実行できるスクリプトファイルが存在します。

  • libフォルダ
    PartiQLを実行するための必要なJavaライブラリが一式入っています。

  • Tutorialフォルダ
    Tutorialで使用する各種ファイルが入っています。

binフォルダのスクリプトファイルが展開されたことで、とりあえずPartiQLを触ることはできるようになったようです。

ちょっとお試し

環境構築で展開されたbinフォルダに移動してPartiQLを実行してみましょう。

$ partiql  
> Welcome to the PartiQL REPL!  
> PartiQL>

お、起動した。では続けてコマンドを叩いてみましょう。
次の4パターンで実行しました。

  1. 小文字 + 末尾にセミコロン(;)あり
  2. 小文字 + 末尾にセミコロン(;)なし
  3. 大文字 + 末尾にセミコロン(;)なし
  4. 大文字 + 末尾にセミコロン(;)あり
お試し4パターン実行結果

PartiQL> select * from [1,2,3,4];
org.partiql.lang.syntax.LexerException: Lexer Error: at line 1, column 1: invalid character at, 'U+1b]

        at org.partiql.lang.syntax.SqlLexer$tokenize$1.invoke(SqlLexer.kt:435)
        at org.partiql.lang.syntax.SqlLexer.tokenize(SqlLexer.kt:460)
        at org.partiql.lang.syntax.SqlParser.parseExprNode(SqlParser.kt:2219)
        at org.partiql.lang.CompilerPipelineImpl.compile(CompilerPipeline.kt:164)
        at org.partiql.cli.Repl$executePartiQL$1.invoke(Repl.kt:248)
        at org.partiql.cli.Repl$executePartiQL$1.invoke(Repl.kt:108)
        at org.partiql.cli.Repl.executeTemplate(Repl.kt:213)
        at org.partiql.cli.Repl.executePartiQL(Repl.kt:244)
        at org.partiql.cli.Repl.run(Repl.kt:312)
        at org.partiql.cli.Main.runRepl(main.kt:155)
        at org.partiql.cli.Main.main(main.kt:140)
ERROR!
PartiQL> select * from [1,2,3,4]
   |
org.partiql.lang.syntax.LexerException: Lexer Error: at line 1, column 1: invalid character at, 'U+1b]

        at org.partiql.lang.syntax.SqlLexer$tokenize$1.invoke(SqlLexer.kt:435)
        at org.partiql.lang.syntax.SqlLexer.tokenize(SqlLexer.kt:460)
        at org.partiql.lang.syntax.SqlParser.parseExprNode(SqlParser.kt:2219)
        at org.partiql.lang.CompilerPipelineImpl.compile(CompilerPipeline.kt:164)
        at org.partiql.cli.Repl$executePartiQL$1.invoke(Repl.kt:248)
        at org.partiql.cli.Repl$executePartiQL$1.invoke(Repl.kt:108)
        at org.partiql.cli.Repl.executeTemplate(Repl.kt:213)
        at org.partiql.cli.Repl.executePartiQL(Repl.kt:244)
        at org.partiql.cli.Repl.run(Repl.kt:312)
        at org.partiql.cli.Main.runRepl(main.kt:155)
        at org.partiql.cli.Main.main(main.kt:140)
ERROR!
PartiQL> SELECT * FROM [1,2,3,4]
   |
==='
<<
  {
    '_1': 1
  },
  {
    '_1': 2
  },
  {
    '_1': 3
  },
  {
    '_1': 4
  }
>>
---
OK!
PartiQL> SELECT * FROM [1,2,3,4];
==='
<<
  {
    '_1': 1
  },
  {
    '_1': 2
  },
  {
    '_1': 3
  },
  {
    '_1': 4
  }
>>
---
OK!

1・2ではSyntaxエラー、3・4で正常に実行されました。文末にセミコロンは必要ないようですが、アッパーケースで記述する必要があるようです。
...なるほど。そういう癖をお持ちなのですね。PartiQL君。
また出力形式もJSON(っぽい)形式で出力されています。

試してみて分かったPartiQLのこと

PartiQLデータモデル

先ほどのクエリの出力結果をもう一度見てみましょう。

<<
  {
    '_1': 1
  },
  {
    '_1': 2
  },
  {
    '_1': 3
  },
  {
    '_1': 4
  }
>>

一見、Json形式の配列の中身だけ'<<'と'>>'の中に記載しているだけのように見えますが、実はこれ、重要な意味を持っています。
これは、PartiQLデータモデルに基づく記法で、'<<'と'>>'の中身は「Bag」と呼ばれます。関係データモデルのテーブルに該当するもので、Bag内での見た目上の順番(配列のインデックスをイメージしてください)では要素にアクセスすることはできません。_1 = 1や1 <= _1 AND _1 <= 4などと指定することによってアクセスすることが出来ます。関係データモデルのテーブルの要素をWhere句で抽出するのと同じイメージで大丈夫だと思います。
PartiQLのデータモデルに関してもっと突っ込んで詳しく調べたい方は、こちらのPDFをご覧ください。データモデルも含めて、PartiQLの仕様についてDraftではありますが記載してあります。
さてさて、今回の記事はとりあえず触ってみる、がテーマなので先に進みましょう。

SQLと互換性のあるクエリ

という点を確認してみましょう。以下のようなデータを準備します。(ダウンロードしたTutorialフォルダの中にあるq1.envファイルです。今回記事で使用するデータは全てTutorialフォルダ内に展開されたものになります)
hr.employeesテーブル(Bag)に3つのレコードが存在する、といった感じです。このテーブルからnameが'Bob Smith'のレコードを取得してみましょう。

{
    'hr': {
        'employees': <<
            -- a tuple is denoted by { ... } in the PartiQL data model
            { 'id': 3, 'name': 'Bob Smith',   'title': null },
            { 'id': 4, 'name': 'Susan Smith', 'title': 'Dev Mgr' },
            { 'id': 6, 'name': 'Jane Smith',  'title': 'Software Eng 2'}
        >>
    }
}

以下のクエリを実行します。RDBを扱うときにしこしこ書くいつものSQLですね。

PartiQL> SELECT e.id,
   |            e.name AS employeeName,
   |            e.title AS jobTitle
   |     FROM hr.employees e
   |     WHERE e.name = 'Bob Smith'
   |

実行結果は、

==='
<<
  {
    'id': 3,
    'employeeName': 'Bob Smith',
    'jobTitle': NULL
  }
>>
---
OK!

ふんふん。RDBでいつも書いているSQLと同じ形式のクエリで期待通りのレコードを取得することが出来ました。
では、チュートリアルにはなかったのですが、IS NULLも試してみましょう。
以下がクエリの実行とその結果です。

PartiQL> SELECT e.id,
   |            e.name AS employeeName,
   |            e.title AS jobTitle
   |     FROM hr.employees e
   |     WHERE e.title IS NULL
   |
==='
<<
  {
    'id': 3,
    'employeeName': 'Bob Smith',
    'jobTitle': NULL
  }
>>
---
OK!

ほうほう。期待通りにnameがNULLのレコードを取得出来ました。
他にも確認すべきことはありますが、確かにPartiQLはSQLと互換性があるといって間違いないようです。

ネストしたデータへのクエリ検索

さて、割と本題であるネストしたデータへのクエリを見ていきましょう。
従来われわれ開発者がSQLを書く時、ネストしたデータは、そのままでは取り扱いの対象としませんでした。それは複雑な業務のデータを安全に扱うために、関係理論に基きデータ構造を整理(正規化)していたからでした。テーブルは正規化が推奨され、子テーブルから親テーブルに対する参照などは、通常外部キーを使用して参照することが普通でした。システム開発において、RDBはトランザクションを安全に扱うための強力な武器だったのです。
しかし現在、データはRDBでトランザクショナルに管理されるだけではなく、データレイクとして多用途に利用されること、またそのように管理することが重要になっています。PartiQLは2019年8月にAWSから発表されましたが、そのような文脈の中で生まれたクエリ言語であることは言うまでもないでしょう。
さて、実際にどうなるのか見ていきましょう。

次のようなデータを考えます。

{
  'hr': {
      'employeesNest': <<
         {
          'id': 3,
          'name': 'Bob Smith',
          'title': null,
          'projects': [ { 'name': 'AWS Redshift Spectrum querying' },
                        { 'name': 'AWS Redshift security' },
                        { 'name': 'AWS Aurora security' }
                      ]
          },
          {
              'id': 4,
              'name': 'Susan Smith',
              'title': 'Dev Mgr',
              'projects': []
          },
          {
              'id': 6,
              'name': 'Jane Smith',
              'title': 'Software Eng 2',
              'projects': [ { 'name': 'AWS Redshift security' } ]
          }
      >>
    }
}

'project'の中身がコレクション(配列)データとしてネストしていますね。
これはRDBであれば、employeeテーブルとprojects_employeeテーブルに正規化して分割し、データ生成の順番から考えるとemployeeの主キーをprojects_employeeテーブルに外部キーとして保持させることが望ましいでしょう。
このデータに対し以下のクエリを投げてみます。結果はどうなるかというと......

PartiQL> SELECT e.name AS employeeName,
   |            p.name AS projectName
   |     FROM hr.employeesNest AS e,
   |          e.projects AS p
   |     WHERE p.name LIKE '%security%'
   |
==='
<<
  {
    'employeeName': 'Bob Smith',
    'projectName': 'AWS Redshift security'
  },
  {
    'employeeName': 'Bob Smith',
    'projectName': 'AWS Aurora security'
  },
  {
    'employeeName': 'Jane Smith',
    'projectName': 'AWS Redshift security'
  }
>>
---
OK!

まさに、RDBでemployeeテーブルとprojects_employeeテーブルをp.name LIKE '%security%'を条件に自然結合させたのと同じ結果ですね。
割と同じことをコードベースで行ったり、DynamoDBでフィルタをかけたりとすると、めんどくさい処理を書かないといけなかったりするのですが、SQLで簡潔に書けるとなんか安心感がありますね(笑)

まとめ

PartiQLを今回軽く触ってみました。ネストしたデータでJOIN、GROUP BYなどをした時の挙動や、ネストしたデータの生成の仕方など、まだまだ紹介すべきことがたくさんあるのですが、長くなりすぎてしまうので割愛します。知りたい方はPartiQL公式ページのGetting Startedをやってみてください。
今回皆さんにお伝えしたかったのは、PartiQLという技術そのものについてはもちろんですが、NoSQLを謳ったDynamoDBが、今後PartiQLという「新しいSQL」で取り扱われることも出来るようになること、またその技術の潮流です。
興味が湧いた人はちょっと触ってみてください。驚くほどSQLです(笑)
AWS Glue Elastic ViewsのPreviewに触れたら記事にします。(受かるといいなぁ)