mcpField
S
sql-cookbook

sql-cookbook

github.com/okou-aiデータ・データベース8157 回インストール1 週間前に更新
リポジトリ

sql-cookbook について

PostgreSQL、Snowflake、BigQuery、Redshift、Databricks SQLを扱う、分析担当者向けのSQLリファレンススキルです。方言ごとの構文、分析パターン、最適化、トラブルシューティングをまとめています。

主な機能

  1. 015種類のSQL方言の構文ガイド
  2. 02ウィンドウ関数とCTEのパターン
  3. 03コホートとファネル分析の例
  4. 04クエリ最適化の指針
  5. 05よくあるクエリエラーの確認項目

活用シーン

  1. 01方言に応じた日付やJSONクエリの調整
  2. 02リテンションとコンバージョン分析の作成
  3. 03クエリエラーの原因調査
  4. 04クエリ実行効率の改善

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/okou-ai/okou-skills --skill sql-cookbook

インストール後、Claude Code で/sql-cookbookを入力して読み込まれたか確認します。

詳細

プラットフォーム固有の構文ガイド

PostgreSQL(Aurora、RDS、Supabase、Neon)

日付と時刻の操作:

-- Today and now
CURRENT_DATE, CURRENT_TIMESTAMP, NOW()

-- Adding and subtracting intervals
some_date + INTERVAL '7 days'
some_date - INTERVAL '1 month'

-- Round down to a time boundary
DATE_TRUNC('month', event_ts)

-- Pull out individual components
EXTRACT(YEAR FROM event_ts)
EXTRACT(DOW FROM event_ts)  -- Sunday = 0

-- Render as formatted text
TO_CHAR(event_ts, 'YYYY-MM-DD')

テキスト操作:

-- Joining strings together
first_name || ' ' || last_name
CONCAT(first_name, ' ', last_name)

-- Flexible matching
col ILIKE '%term%'  -- ignores case
col ~ '^regex$'     -- POSIX regex

-- Splitting and extracting
LEFT(str, n), RIGHT(str, n)
SPLIT_PART(str, delimiter, part_num)
REGEXP_REPLACE(str, pattern, replacement)

JSON と配列の処理:

-- Accessing JSON fields
payload->>'key'             -- returns text
payload->'nested'->'field'  -- returns json object
payload#>>'{a,b,c}'        -- deep path as text

-- Array utilities
ARRAY_AGG(col)
ANY(arr_col)
arr_col @> ARRAY['val']

チューニングの指針:

  • EXPLAIN ANALYZE を実行して、実際の実行プランを確認します
  • WHERE 条件や JOIN 条件で使用される列にインデックスを追加します
  • 相関検索では IN よりも EXISTS を優先します
  • 頻繁に使用されるフィルター述語には、部分インデックスを検討します
  • 同時実行されるワークロードを処理する場合は、コネクションプーリングを導入します
Snowflake

日付と時刻の関数:

-- Getting current moments
CURRENT_DATE(), CURRENT_TIMESTAMP(), SYSDATE()

-- Shifting dates
DATEADD(day, 7, some_date)
DATEDIFF(day, start_dt, end_dt)

-- Truncation
DATE_TRUNC('month', event_ts)

-- Component extraction
YEAR(event_ts), MONTH(event_ts), DAY(event_ts)
DAYOFWEEK(event_ts)

-- Formatting
TO_CHAR(event_ts, 'YYYY-MM-DD')

テキストとパターンマッチング:

-- Case-insensitive matching (default collation dependent)
col ILIKE '%term%'
REGEXP_LIKE(col, 'pattern')

-- Working with VARIANT / semi-structured data
col:key::string
PARSE_JSON('{"a": 1}')
GET_PATH(variant_col, 'path.to.field')

-- Expanding nested arrays
SELECT f.value FROM tbl, LATERAL FLATTEN(input => arr_col) f

半構造化列の操作:

-- Dot-path access on VARIANT
payload:customer:name::STRING
payload:items[0]:price::NUMBER

-- Exploding nested arrays into rows
SELECT
    t.id,
    elem.value:name::STRING AS item_name,
    elem.value:qty::NUMBER AS item_qty
FROM my_table t,
LATERAL FLATTEN(input => t.payload:items) elem

チューニングの指針:

  • 大規模なテーブルでは、従来のインデックスではなくクラスタリングキーを活用します
  • パーティションプルーニングを最大化するため、フィルターではクラスタリングキーの列を対象にします
  • クエリの複雑さに応じて、仮想ウェアハウスのサイズを適切に設定します
  • RESULT_SCAN(LAST_QUERY_ID()) で結果を再利用し、クエリの再実行を避けます
  • 一時的なステージングデータには一時テーブルを使用します
BigQuery

日付と時刻の関数:

-- Current moments
CURRENT_DATE(), CURRENT_TIMESTAMP()

-- Shifting dates forward and backward
DATE_ADD(some_date, INTERVAL 7 DAY)
DATE_SUB(some_date, INTERVAL 1 MONTH)
DATE_DIFF(end_dt, start_dt, DAY)
TIMESTAMP_DIFF(end_ts, start_ts, HOUR)

-- Truncation
DATE_TRUNC(event_ts, MONTH)
TIMESTAMP_TRUNC(event_ts, HOUR)

-- Component extraction
EXTRACT(YEAR FROM event_ts)
EXTRACT(DAYOFWEEK FROM event_ts)  -- Sunday = 1

-- Display formatting
FORMAT_DATE('%Y-%m-%d', date_col)
FORMAT_TIMESTAMP('%Y-%m-%d %H:%M:%S', ts_col)

テキスト操作:

-- No native ILIKE; lowercase first
LOWER(col) LIKE '%term%'
REGEXP_CONTAINS(col, r'pattern')
REGEXP_EXTRACT(col, r'pattern')

-- Splitting and reassembling
SPLIT(str, delimiter)  -- produces an ARRAY
ARRAY_TO_STRING(arr, delimiter)

配列と構造体:

-- Array utilities
ARRAY_AGG(col)
UNNEST(arr_col)
ARRAY_LENGTH(arr_col)
val IN UNNEST(arr_col)

-- Struct field access
struct_col.field_name

チューニングの指針:

  • スキャンするバイト数を最小限に抑えるため、必ずパーティション列(通常は日付)でフィルターを適用します
  • WHERE 句に頻繁に登場する列をクラスタリング対象にします
  • カーディナリティの高いデータでは、COUNT(DISTINCT ...) を APPROX_COUNT_DISTINCT() に置き換えます
  • 課金額は読み取るバイト数に応じて増えるため、SELECT * は避けます
  • パラメーター化されたスクリプトのロジックには DECLARE / SET を使用します
  • コストの高いクエリを実行する前に、ドライランで見積もりを確認します
Redshift

日付と時刻の関数:

-- Current moments
CURRENT_DATE, GETDATE(), SYSDATE

-- Shifting dates
DATEADD(day, 7, some_date)
DATEDIFF(day, start_dt, end_dt)

-- Truncation
DATE_TRUNC('month', event_ts)

-- Component extraction
EXTRACT(YEAR FROM event_ts)
DATE_PART('dow', event_ts)

テキスト操作:

-- Case-insensitive matching
col ILIKE '%term%'
REGEXP_INSTR(col, 'pattern') > 0

-- Splitting and concatenation
SPLIT_PART(str, delimiter, part_num)
LISTAGG(col, ', ') WITHIN GROUP (ORDER BY col)

チューニングの指針:

  • JOIN するテーブルが同じノードに配置されるよう、ディストリビューションキー(DISTKEY)を設定します
  • フィルターによく使用される列にソートキー(SORTKEY)を定義します
  • EXPLAIN でクエリプランを確認します
  • プラン内の DS_BCAST と DS_DIST に注意します(ノード間のコストの高いデータ移動を示します)
  • 定期的に ANALYZE と VACUUM を実行します
  • スキーマが変更されても柔軟に対応できるよう、遅延バインディングビューを使用します
Databricks SQL

日付と時刻の関数:

-- Current moments
CURRENT_DATE(), CURRENT_TIMESTAMP()

-- Shifting dates
DATE_ADD(some_date, 7)
DATEDIFF(end_dt, start_dt)
ADD_MONTHS(some_date, 1)

-- Truncation
DATE_TRUNC('MONTH', event_ts)
TRUNC(date_col, 'MM')

-- Component extraction
YEAR(event_ts), MONTH(event_ts)
DAYOFWEEK(event_ts)

Delta Lake の機能:

-- Query historical snapshots
SELECT * FROM tbl TIMESTAMP AS OF '2024-01-15'
SELECT * FROM tbl VERSION AS OF 42

-- Inspect table history
DESCRIBE HISTORY tbl

-- Upsert with MERGE
MERGE INTO target USING source
ON target.id = source.id
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *

チューニングの指針:

  • 頻繁にクエリされる列に対して、ZORDER を指定して OPTIMIZE を実行します
  • 計算負荷の高いワークロードでは Photon エンジンを有効にします
  • 繰り返し読み取られるデータセットには CACHE TABLE を適用します
  • 効率的なプルーニングのため、カーディナリティの低い日付列でパーティション分割します

再利用可能な分析パターン

ウィンドウ関数のレシピ
-- Assign sequential position within groups
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_ts DESC)
RANK() OVER (PARTITION BY category ORDER BY revenue DESC)
DENSE_RANK() OVER (ORDER BY score DESC)

-- Cumulative sums and rolling averages
SUM(amount) OVER (ORDER BY dt ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_total
AVG(amount) OVER (ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS rolling_7d_avg

-- Accessing adjacent rows
LAG(val, 1) OVER (PARTITION BY entity ORDER BY dt) AS prior_val
LEAD(val, 1) OVER (PARTITION BY entity ORDER BY dt) AS next_val

-- Boundary values within a partition
FIRST_VALUE(status) OVER (PARTITION BY user_id ORDER BY event_ts ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
LAST_VALUE(status) OVER (PARTITION BY user_id ORDER BY event_ts ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)

-- Proportional share
revenue / SUM(revenue) OVER () AS share_of_total
revenue / SUM(revenue) OVER (PARTITION BY category) AS share_within_category
CTE を使用した構造化クエリ
WITH
-- Stage 1: Narrow down to the target population
eligible_users AS (
    SELECT user_id, signup_date, tier
    FROM users
    WHERE signup_date >= DATE '2024-01-01'
      AND account_status = 'active'
),

-- Stage 2: Derive per-user measures
per_user AS (
    SELECT
        u.user_id,
        u.tier,
        COUNT(DISTINCT e.session_id) AS sessions,
        SUM(e.revenue) AS revenue
    FROM eligible_users u
    LEFT JOIN events e ON u.user_id = e.user_id
    GROUP BY u.user_id, u.tier
),

-- Stage 3: Roll up to tier-level summary
tier_summary AS (
    SELECT
        tier,
        COUNT(*) AS users,
        AVG(sessions) AS avg_sessions,
        SUM(revenue) AS total_revenue
    FROM per_user
    GROUP BY tier
)

SELECT * FROM tier_summary ORDER BY total_revenue DESC;
コホートの継続率の追跡
WITH signup_cohorts AS (
    SELECT
        user_id,
        DATE_TRUNC('month', first_seen) AS cohort
    FROM users
),
monthly_activity AS (
    SELECT
        user_id,
        DATE_TRUNC('month', activity_date) AS active_month
    FROM user_events
)
SELECT
    s.cohort,
    COUNT(DISTINCT s.user_id) AS cohort_size,
    COUNT(DISTINCT CASE
        WHEN a.active_month = s.cohort THEN a.user_id
    END) AS m0,
    COUNT(DISTINCT CASE
        WHEN a.active_month = s.cohort + INTERVAL '1 month' THEN a.user_id
    END) AS m1,
    COUNT(DISTINCT CASE
        WHEN a.active_month = s.cohort + INTERVAL '3 months' THEN a.user_id
    END) AS m3
FROM signup_cohorts s
LEFT JOIN monthly_activity a ON s.user_id = a.user_id
GROUP BY s.cohort
ORDER BY s.cohort;
コンバージョンファネルの測定
WITH step_flags AS (
    SELECT
        user_id,
        MAX(CASE WHEN event_name = 'page_view' THEN 1 ELSE 0 END) AS saw_page,
        MAX(CASE WHEN event_name = 'signup_start' THEN 1 ELSE 0 END) AS began_signup,
        MAX(CASE WHEN event_name = 'signup_complete' THEN 1 ELSE 0 END) AS finished_signup,
        MAX(CASE WHEN event_name = 'first_purchase' THEN 1 ELSE 0 END) AS made_purchase
    FROM events
    WHERE event_date >= CURRENT_DATE - INTERVAL '30 days'
    GROUP BY user_id
)
SELECT
    COUNT(*) AS total_users,
    SUM(saw_page) AS viewers,
    SUM(began_signup) AS signup_starts,
    SUM(finished_signup) AS signup_completions,
    SUM(made_purchase) AS purchasers,
    ROUND(100.0 * SUM(began_signup) / NULLIF(SUM(saw_page), 0), 1) AS view_to_start_rate,
    ROUND(100.0 * SUM(finished_signup) / NULLIF(SUM(began_signup), 0), 1) AS start_to_finish_rate,
    ROUND(100.0 * SUM(made_purchase) / NULLIF(SUM(finished_signup), 0), 1) AS finish_to_purchase_rate
FROM step_flags;
重複レコードの削除
-- Retain only the latest version of each entity
WITH ordered AS (
    SELECT
        *,
        ROW_NUMBER() OVER (
            PARTITION BY entity_id
            ORDER BY modified_at DESC
        ) AS seq
    FROM raw_table
)
SELECT * FROM ordered WHERE seq = 1;

クエリ失敗のトラブルシューティング

クエリでエラーが発生した場合は、次の項目を順に確認します。

  1. 構文の問題:方言との互換性を確認します(例:BigQuery には ILIKE がなく、SAFE_DIVIDE があるのは BigQuery のみです)
  2. 列の欠落:実際のスキーマと照らし合わせて列名を確認します。PostgreSQL では、引用符で囲まれた識別子の大文字と小文字の区別に注意します
  3. 型の不一致:型が一致しない値を比較する場合は、明示的なキャストを追加します(CAST(col AS DATE) または col::DATE)
  4. ゼロ除算:分母を NULLIF(denominator, 0) で囲むか、プラットフォーム固有の安全な除算を使用します
  5. 列名の曖昧さ:JOIN のすべてのテーブルにエイリアスを付け、列参照には必ずテーブル名を付けます
  6. GROUP BY 違反:集計関数の中にない選択列はすべて GROUP BY に含める必要があります(例外として、BigQuery ではエイリアスを参照できます)

コメント