はじめに
こんにちは。マイクロアドのデータマネジメントユニットの前西です。業務では社内の分析基盤(主にRedash × Trino)での広告配信データの集計・分析用SQLの作成・管理などをメインに担当しています。
マイクロアドではRedashを使って非常に多くのSQLを作成・運用していますが、SQLはアプリケーションのコードほど厳格に管理されてこなかった領域でした。そのため、次のような課題がありました。
- 同じようなクエリが量産される
- 書いた人にしか読み解けないクエリが生まれる
- 実行効率の悪いクエリが業務で使われ、データ基盤に負荷をかけてしまう
現在ではRedashのクエリをGitHubで管理する仕組みを導入し、GitHubのPull Request上でレビューすることで、クエリの品質担保を実現しています。
今回の記事では、上記の取り組みの中で導入している「分析SQLコーディング規約」と、生成AI(GitHub Copilot)を活用した規約の適用について、実例を交えて紹介します。
分析SQLコーディング規約の必要性
分析基盤上のクエリは、ダッシュボード用・アラート用・アドホックな調査用など、目的や作成者もさまざまです。書き方そのものがバラバラだと、可読性の低下や属人化、レビューコストの増大といった問題が起きがちです。
分析SQLコーディング規約を導入するメリットとして、こちらの記事*1では以下のような点が紹介されています。
- すべてのクエリに一貫性があるのでミスを発見しやすくなる
- クエリを書くたびに書き方で悩む必要がなくなる
- クエリが読みやすくなり、レビュー効率も上がる
- クエリを解読するのが容易になり、再利用しやすく会社の資産としても管理しやすい
分析SQLのスタイルガイドは他社でも先行事例があり*2*3、私たちの規約もそうした考え方をベースに実務に合わせて調整したものです。
規約の内容
実際に運用している規約を、箇条書きで紹介します。
- 予約語・関数名・データ型はすべて小文字で記載する(
select,sum,intなど) - インデントは半角スペース4つ
- 改行後の行頭にカンマを置く(末尾カンマにしない)
- カンマの後ろには半角スペースを1つ入れる
from句で指定するテーブルは改行してインデントするjoinのインデントはfromよりも下にする(テーブル名と同じ高さに置く)。onはjoinのさらに下にインデントする- 結合の種類(
inner/left/fullなど)は必ず明示する - エイリアスは
asで明示する - エイリアスや一時テーブル名は意味の分かる命名にする(
a,bのような無意味な文字は使わない。元のテーブル名・カラム名の頭文字を使う分には問題ない。例:ads_performance_summary as aps) andなどの演算子は行末ではなく行頭に置く- サブクエリはネストさせず、
with句で切り出して定義する(切り出すことで却って可読性が下がる場合を除く) with句と本体のselect文の間は1行空ける- 1行が長くなる場合は適宜改行・インデントを入れる
文章だけではイメージが伝わりにくいため、すべての規約に違反したNGクエリと、逆にすべての規約を反映したOKクエリを並べてみます。
❌ NG例:規約に違反したクエリ
SELECT a.campaign_id,a.campaign_name,b.dt,b.total_cost,b.sp_cost,CAST(b.sp_cost AS DOUBLE) / NULLIF(b.total_cost,0) AS sp_cost_ratio FROM (SELECT campaign_id,campaign_name FROM campaign_master WHERE status = 'active' AND CAST(start_date AS DATE) <= CURRENT_DATE) a JOIN (SELECT dt,campaign_id,SUM(cost) AS total_cost,SUM(CASE WHEN device_type = 'sp' THEN cost ELSE 0 END) AS sp_cost FROM cost_log WHERE dt BETWEEN DATE('2026-07-01') AND DATE('2026-07-31') GROUP BY dt,campaign_id) b ON a.campaign_id = b.campaign_id WHERE b.total_cost > 0 AND b.dt IS NOT NULL ORDER BY b.dt,a.campaign_id
- 予約語がすべて大文字(
SELECT,FROM,JOIN...) - インデントが無く1行に詰め込まれ、改行もされていない
- カンマの後ろにスペースが無い(
a.campaign_id,a.campaign_name) - サブクエリが
with句に切り出されず、from句にネストしている - 結合の種類を明示せず
JOINとだけ書いている - サブクエリのエイリアスが
a,bという意味の分からない命名で、しかもasが省略されている andが行末に置かれている(WHERE b.total_cost > 0 AND)- 1行が長大でも改行・インデントされていない
✅ OK例:規約を満たしたクエリ
同じ内容のSQLを、規約に沿って書き直すとこうなります。
with target_campaign as ( select campaign_id , campaign_name from campaign_master where status = 'active' and cast(start_date as date) <= current_date ) , daily_cost as ( select dt , campaign_id , sum(cost) as total_cost , sum(case when device_type = 'sp' then cost else 0 end) as sp_cost from cost_log where dt between date('2026-07-01') and date('2026-07-31') group by dt , campaign_id ) select campaign.campaign_id , campaign.campaign_name , cost.dt , cost.total_cost , cost.sp_cost , cast(cost.sp_cost as double) / nullif(cost.total_cost, 0) as sp_cost_ratio from target_campaign as campaign inner join daily_cost as cost on campaign.campaign_id = cost.campaign_id where cost.total_cost > 0 and cost.dt is not null order by cost.dt , campaign.campaign_id
同じロジックのクエリでも、規約を守るだけでここまで見た目と可読性が変わります。
生成AIの活用
上記のSQLコーディング規約だけでなく、クエリの命名規則、クエリ仕様の記載など、私たちのチームには他にも様々な「開発ルール」を設けています。ただ、初めてルールに触れるメンバーには全てを漏れなく適用するのが難しく、レビューのたびに同じような指摘を繰り返し受けることが多くなっていました。
アプリケーションのコードでは、linterやformatterを導入することで、コーディング規約の多くを機械的に強制できます。しかし、分析SQLの規約・開発ルールには、「エイリアスや一時テーブル名を意味の分かる命名にする」「クエリ仕様を記載する」など、クエリの意図や文脈を理解して初めて判断できる項目が多く含まれます。こうした項目は決まったルールに基づく静的解析だけでは機械的にチェックしづらく、これまでは人手でのレビューに頼らざるを得ませんでした。
そこで、SQL管理用のリポジトリに.github/copilot-instructions.mdを置き、GitHub Copilotにこれらの規約・開発ルールを読み込ませることにしました。GitHub Copilotはこのファイルを自動的に読み込み、コード生成やチャットでの提案に反映してくれます。先ほど紹介したSQLコーディング規約に加えて、クエリの命名などに関する開発ルールもまとめて記載しました。これにより、Copilotへ新しいクエリの作成やレビューを依頼するとき、SQLの書き方だけでなくルール全体を踏まえた提案・チェックをしてもらえるようになりました。
Copilotが規約・開発ルールに沿っているかどうかをチェックしてくれるようになったため、人間のレビューはSQLのロジックが正しいか、意図した集計になっているかという本質的な部分へ集中できるようになりました。その結果、レビューの効率が大幅に向上しています。
まとめ
マイクロアドで運用している分析SQLコーディング規約と、生成AI(GitHub Copilot)を活用した取り組みを紹介しました。
生成AIの活用についてはまだまだ改善の余地があると感じているため、運用を重ねながら更なる改善を続けていきます。この記事が同じような分析基盤の運用に悩んでいる方の参考になれば幸いです。