PySparkは常にデータを問い合わせるための素晴らしいSQLとPython APIを提供してきました。 Databricks Runtime 12.1とApache Spark 3.4の時点で、パラメータ化されたクエリは、Pythonicプログラミングパラダイムを使用してSQLでデータをクエリする安全で表現力豊かな方法をサポートしています。
この投稿では、PySparkでパラメータ化されたクエリを作成する方法と、それがあなたのコードにとって良いデザインパターンである場合について説明します。
パラメータは、Sparkコードの再利用やテストを容易にするのに役立ちます。 また、良いコーディングの実践も奨励しています。 この記事では、PySparkのクエリをパラメータ化する2つの異なる方法を示します:
- PySparkカスタム文字列フォーマット
- パラメータマーカー
両方のタイプのPySparkパラメータ付きクエリの使い方を見て、組み込みの機能が他の選択肢よりも優れている理由を探ってみましょう。
パラメータ化されたクエリの利点
パラメータ化されたクエリは、"Don't Repeat yourself" (DRY)パターンを奨励し、ユニットテストを容易にし、SQLを再利用しやすくします。 また、セキュリティの脆弱性をもたらすSQLインジェクション攻撃も防ぐことができます。
似たようなクエリを書くとき、SQLの大きな塊をコピー&ペーストしたくなることがあります。 パラメータ化されたクエリは、パターンを抽象化し、DRYパターンでコードを書くことを推奨します。
パラメータ化されたクエリはテストも簡単です。 クエリをパラメータ化することで、本番データやテストデータセットで簡単に実行できます。
一方、Pythonのf-stringsを使ってSQLクエリを手動でパラメータ化するのは、あまり良い方法ではありません。 以下のデメリットを考えてみてください:
- Pythonのf-stringsはSQLインジェクション攻撃への防御を提供しません。
- Pythonのf-stringsはDataFramesやカラム、特殊文字などのPythonネイティブオブジェクトを理解しません。
SQLインジェクションの脆弱性からコードを保護し、文字列形式の一般的なPySparkインスタンスの自動型変換をサポートするパラメータマーカーでクエリをパラメータ化する方法を見てみましょう。
PySpark カスタム文字列フォーマットによるパラメータ化クエリ
h20_1e9という
9列のデータ・テーブルがあるとします:
以下の SQL クエリをパラメータ化したいとします:
このクエリを、id1
の値を変えて簡単に実行できるようにしたいとします。 ここでは、id1 の
値を変えてクエリをパラメータ化して実行する方法を説明します。
別の引数を指定してクエリを再実行します:
PySpark の文字列フォーマッタを使うと、明示的に一時ビューを定義することなく DataFrame に対して直接 SQL クエリを実行することもできます。
次のようなperson_dfという
DataFrameがあるとします:
SQLでDataFrameに問い合わせる方法は以下の通りです。
一時ビューを手動で登録することなく、SQL構文を使ってDataFrame上でクエリを実行できるのは非常に便利です!
それでは、パラメータマーカーで引数を指定してクエリをパラメータ化する方法を見てみましょう。
パラメータマーカーによるパラメータ化クエリ
引数のディクショナリを使用して、パラメータマーカーでパラメータ化された SQL クエリを作成することもできます。
some_purchases
という名前の以下のビューがあるとします:
ここでは、名前付きパラメータマーカーを使用してパラメータ化されたクエリを作成し、指定されたアイテムの合計金額を計算する方法を示します。
靴下に費やした総額を計算してください。
無名のパラメータマーカーを使用してクエリをパラメータ化することもできます。
Apache Sparkはパラメータマーカーをサニタイズするので、このパラメータ化手法はSQLインジェクション攻撃からも保護されます。
PySparkがパラメータ付きクエリをサニタイズする方法
以下は、Sparkが名前付きパラメータ化クエリをどのようにサニタイズするかについての高レベルの説明です:
- SQLクエリは、オプションのキー/値パラメータ・リストとともに送信されます。
- Apache SparkはSQLクエリを解析し、パラメータ参照を対応する解析ツリーノードに置き換えます。
- 分析中、Catalystルールが実行され、これらの参照をパラメータから提供されたパラメータ値に置き換えます。
- この方法はリテラル値のみをサポートするため、SQLインジェクション攻撃から保護されます。 正規文字列補間はSQL文字列に対して置換を適用します。文字列が意図したリテラル値 以外のSQL構文を含んでいる場合、この戦略は攻撃に対して脆弱になる可能性があります。
前述したように、PySparkでは2種類のパラメータ付きクエリがサポートされています:
- PEP 3101に基づく{}構文を使用したクライアント側のパラメータ化(私たちはこれをカスタム文字列フォーマットと呼んでいます)
- 名前付きパラメータマーカーまたは無名パラメータマーカーを使用したサーバサイドのパラメータ化
構文 {} は
クライアント側でSQLクエリの文字列置換を行い、使いやすさとプログラムしやすさを向上させます。 ただし、クエリテキストは Spark サーバーに送信される前に置換されるため、SQL インジェクション攻撃からは保護されません。
パラメータ化はsql()
API のargs
引数を使用し、SQL テキストとパラメータを別々にサーバに渡します。 SQLテキストはパラメータのプレースホルダと共に解析され、解析されたクエリツリーのargsで
指定されたパラメータの値に置き換えられます。
サーバサイドパラメータ化クエリには、名前付きパラメータマーカーと無名パラメータマーカーの2種類があります。 名前付きパラメータマーカーでは、プレースホルダに :<param_name>
構文を使用します。 無名パラメータマーカーの使用方法の詳細については、ドキュメントを参照してください。
パラメータ化されたクエリーと文字列の補間
通常のPython文字列補間を使用してクエリをパラメータ化することもできますが、それほど便利ではありません。
Pythonのf-stringsを使った先ほどのクエリのパラメータは以下のようになります:
次のような理由から、これはあまりいいことではありません:
- 一時的なビューを作成する必要があります。
- Python の日付ではなく、文字列として日付を表現する必要があります。
- SQL文字列を適切にフォーマットするために、クエリ内で日付をシングルクォートで囲む必要があります。
- これはSQLインジェクション攻撃を防御するものではありません。
まとめると、組み込みのクエリ・パラメータ化機能は、文字列補間よりも安全で効果的です。
まとめ
PySpark のパラメータ化されたクエリは、使い慣れた SQL 構文できれいなコードを書くための新しい機能を提供します。 SQLでSpark DataFrameをクエリするときに便利です。 浮動小数点値、文字列、日付、datetimeのような一般的なPythonデータ型を使うことができ、これらは自動的にSQL値に変換されます。 このようにして、一般的なPythonイディオムを活用し、美しいコードを書くことができるようになりました。
今すぐPySparkのパラメータ付きクエリを活用することで、より高品質なコードベースのメリットをすぐに享受できます。