👨‍💻

SELECT * がデータモデルの保守性を下げる話

に公開1

SQLコーディング規約で SELECT * を使わずにカラム名を明示化する、というポリシーを時々見るのですが、正直なところ自分はこれまで「どっちでもいいかな〜」くらいに思ってました。アドホックな分析なら速いし、dbtで開発していても最下流のデータマートなら「カラムが増えても勝手についてきてくれるし、楽でいいじゃん」くらいに考えていました。

一方、ディメンショナルモデリングを運用する中で、これを入れておかないと、知らぬ間に不適切なカラムが公開されて保守性が著しく下がる ということを痛感する出来事がありました。今回は、その実体験の共有となぜエンジニアリングの観点で SELECT * がアンチパターンとされるのかについて考えたことをまとめておきたいと思います。

Incrementalモデルで起きた「再構築」の手間

当時の状況

  • データパイプラインは、大まかに staging -> itm (intermediate) -> mart という3層構造をとっているものとします。
  • 当時そのitmモデルは上流のstagingテーブルに対して SELECT * を記述しているモデルがいくつかありました。当時は何も考えずにとりあえず書いていたような気もします。また、このモデルはデータ量が多いため、dbtの Incremental model(増分更新)として設定していました。

何が起きたか

  • ある日、上流のデータソースに変更があり、staging 層にとあるカラムが「意図しない型」で新規連携されてきました。
  • もしここで、カラム名を明示的に指定していれば、新規カラムなので連携されません。しかし、SELECT * はその変更を「素通し」してしまいます。
  • その結果、誤った型のデータがそのまま itm テーブルの増分として追加されてしまった。

Incrementalモデルの罠

  • これが単なる View や毎回作り直す Table であれば、修正して再実行すれば済みます。 しかし、Incrementalモデルは過去の状態(データ)を保持しています。
  • 一度混入してしまった「誤った型のデータ」を取り除くには、テーブル全体をfull-refresh(再構築)する必要があります。データ量が大きいテーブルだったので、このリカバリ作業は再構築時間もコストもかかる大変なものでした。
  • 書くときは数秒の手間を惜しんだ結果、運用で数時間のトラブルシューティングを強いられるといった保守性が著しく低い状態であることを体感しました。

エンジニアリング観点でみる SELECT * の問題点

この経験を経て、改めて「なぜアナリティクスエンジニアリングにおいて SELECT * は推奨されないのか」を整理してみました。

1. 暗黙的な依存と壊れやすさの増加

  • SELECT * を使うということは、上流のテーブルにある「すべてのカラム」に対して暗黙的に期待しない依存を増やすことになる
  • 結果上流のテーブルにとっても利用先があると判断され、運用保守性を考えることになる。
  • 上流でカラムの追加や変更があった場合、下流のモデルが無防備にそれを受け入れてしまい、意図せずカラム名が重複してJOINでエラーになったり、ロジックが静かに狂ったりする可能性がある

2. インターフェースとしての契約が不明瞭で保守性を下げる

  • カラムの提供がAPIと同じでインターフェースとして定義されるべき。つまり上流のモデル(API)で定義されているこれらのカラムを使います(パラメータ付きリクエスト)という明示的な宣言とみなす。
  • SELECT * はソースにあるものを全部くださいという要件もよくわからないことをしている。
  • コードを読む人(未来の自分含む)にとっても、そのモデルが「何のために、どのデータを使っているのか」が読み取れないのは、ドキュメントとしてのコードの価値を下げている

3. パフォーマンスとコスト

  • BigQueryのようなカラムナ型(列指向)データベースでは、クエリで参照したカラムの分だけ課金やスキャンコストが発生します。
  • 数カラムしか必要ないのに SELECT * で100カラム全部読み込むのは、クラウド破産への第一歩です。これも「エンジニアリング」としては避けたい実装です。

逆に SELECT * でも良いケースはあるのか?

ここまで散々デメリットを話してきましたが、私はSELECT * をすべてのモデルではやる必要はないとも思っています。ただ、モデリングレイヤーに応じた責務を整理し、レイヤーごとの定義選択肢としてこれを考えることは重要です。

エンジニアリングの観点でも、以下のようなケースでは SELECT * が許容される、あるいは推奨される場合があります。

    1. Ad-hocな分析とデータ探索: 一回限りの使い捨てクエリ(Ad-hoc)であれば、効率が良い
    1. プロトタイピング(PoC)フェーズ: 「まだこのモデルがマート層として定着するか分からない」「どんなデータが取れるか探索しながらモデルを作っている」という初期段階です。 スキーマが激しく変わる段階で厳密な記述を求めると、試行錯誤のスピードが落ちてしまうので。ただし、「本番運用(Production)に乗せる時や、他者が利用する段階になったら書き直す」 という規律とセットであることが前提です。

おわりに:コーディング規約で守る

データ分析が個人の中で完結していた時代なら、SELECT * で十分でしたが、チームで開発し、長期的に運用する「データプロダクト」としてdbtモデルを扱う以上、改めてソフトウェアエンジニアリングの規律の重要性を感じました。

というわけで、最近ディメンショナルモデリングを行う層でのコーディング規約には、「SELECT * を禁止し、カラムを明示する」 というルールを明示的に追加した。

最近はAIの補完機能を使えばベースのSQL生成は一瞬でできるため、さほど面倒ではなくなってきたのもあり、古いSQLやdbt modelを改めて年末のお掃除でキレイにしていくのは大事です。

「書くときの手間」よりも「守るときの安心」をとる。 そんな当たり前のことを、痛い目を見て再確認した話でした。

Discussion

Tatsunori UchinoTatsunori Uchino

SQLアンチパターンでも槍玉に上がってますね(インプリシットカラム)