30 Claude Prompts That Write SQL
Describe the data question in plain English and Claude returns correct, commented SQL you can run immediately. Prompts for SELECTs and JOINs, GROUP BY reporting, window functions, CTEs, schema and migrations, and query optimization. Not "give me some SQL."
In short: This page contains 30 copy-paste ready prompts, organized into 6 categories with a description and pro tip for each. The first 15 prompts are free instantly โ no signup needed. Hand-curated and tested by the AI Academy team.
SELECT & JOINs
5 promptsMulti-Table Reporting Query
1/30You are a senior data engineer who writes clean, production-grade SQL. <context> I need one SELECT query that pulls a business report by joining several related tables. The output must be a single, correct, well-commented SQL statement I can paste straight into my database client and run. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Tables and key columns: [E.G. orders(id, customer_id, total, created_at), customers(id, name, country)] - What each row of the report should represent: [E.G. ONE ORDER WITH CUSTOMER NAME AND COUNTRY] - Columns I want in the output: [LIST] - Filters: [E.G. LAST 90 DAYS, COUNTRY = 'US'] - Sort order: [E.G. NEWEST FIRST] </inputs> <task> Write a single SELECT that joins the tables on the correct keys, selects only the requested columns with clear aliases, applies the filters in WHERE, and orders the result. Use explicit INNER/LEFT JOIN syntax (never comma joins) and qualify every column with its table alias. </task> <constraints> - One runnable statement in the stated dialect; valid syntax only. - Comment each JOIN with what it connects and why; no SELECT *. - Use short, consistent table aliases and readable indentation. </constraints> <format> Return the commented SQL in a code block, then a one-line note on which JOIN type to switch to LEFT if a table may have no match. </format>
Produces one clean, commented multi-table SELECT with correct JOINs and filters, ready to run.
Pro tip: Paste your real CREATE TABLE statements or a describe/\d dump so Claude uses your exact column names and picks the right join keys.
Find Rows With No Match (Anti-Join)
2/30You are a SQL expert who specializes in data-quality and reconciliation queries. <context> I need to find records in one table that have NO matching row in another (e.g. customers who never ordered, orders with no payment). Give me a single correct, commented query. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Base table and its key: [E.G. customers(id)] - Table it should match against and the join key: [E.G. orders(customer_id)] - What 'missing' means in business terms: [E.G. CUSTOMERS WITH ZERO ORDERS] - Any extra filters: [E.G. ONLY ACTIVE CUSTOMERS] - Columns to return: [LIST] </inputs> <task> Write an anti-join using LEFT JOIN ... WHERE right_key IS NULL, return the requested columns, and apply any filters. Then, as a comment, show the equivalent NOT EXISTS version and note which is usually faster on large tables. </task> <constraints> - One runnable statement plus the commented alternative; valid syntax only. - Explain in a comment why NOT IN is avoided when the column can be NULL. - Qualify all columns with aliases. </constraints> <format> Return the commented SQL in a code block, then one sentence on when to prefer NOT EXISTS over the LEFT JOIN form. </format>
Generates a correct anti-join to surface unmatched records, with a NOT EXISTS alternative, ready to run.
Pro tip: Tell Claude your row counts for both tables; it will recommend LEFT JOIN vs NOT EXISTS based on which side is larger.
Self-Join for Hierarchies & Pairs
3/30You are a SQL specialist experienced with self-referencing and hierarchical data. <context> I need a query that joins a table to itself, either to resolve a parent/child hierarchy (employee to manager) or to compare rows to each other (find pairs). Return one correct, commented statement. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table and columns: [E.G. employees(id, name, manager_id, salary)] - Self-join goal: [E.G. LIST EACH EMPLOYEE WITH THEIR MANAGER'S NAME / FIND EMPLOYEES EARNING MORE THAN THEIR MANAGER] - Columns to output: [LIST] - Filters or ordering: [OPTIONAL] </inputs> <task> Write a self-join with two distinct aliases for the same table, join on the self-referencing key, select the requested columns from both sides with clear labels, and apply filters/ordering. Handle the top-level rows (e.g. employees with no manager) with a LEFT JOIN if they should still appear. </task> <constraints> - One runnable statement; valid syntax only. - Use two clearly different aliases (e.g. e for employee, m for manager) and comment which side is which. - Decide INNER vs LEFT based on whether root rows should show; explain the choice in a comment. </constraints> <format> Return the commented SQL in a code block, then a note on how to extend it to a full recursive hierarchy if needed. </format>
Creates a self-join for manager/child hierarchies or row-to-row comparisons, ready to run.
Pro tip: If you need the full tree (all levels), say so and Claude will hand you a recursive CTE instead of a single-level self-join.
Flexible Search With Optional Filters
4/30You are a backend engineer who writes safe, parameterized search queries. <context> I need a search query where several filters are optional (applied only when a value is provided) so the same statement powers a filter UI. Give me one correct, commented, parameterized query. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table and searchable columns: [E.G. products(id, name, category, price, in_stock)] - Optional filters and their params: [E.G. :search_term ON name, :category, :min_price, :max_price, :in_stock] - Text-match style: [EXACT / CONTAINS (LIKE) / CASE-INSENSITIVE] - Sort and limit: [E.G. NAME ASC, TOP 50] </inputs> <task> Write a SELECT that uses the pattern 'WHERE (:param IS NULL OR column = :param)' for each optional filter so unset params are ignored, handles the text search with the requested match style, and applies sort and limit. Use named bind parameters, never string concatenation. </task> <constraints> - One runnable statement; valid syntax only. - Parameterized (no injection surface); comment each optional-filter clause. - Note how NULL/empty params are treated so the caller knows what to pass. </constraints> <format> Return the commented SQL in a code block, then a one-line note on adding an index to keep the search fast. </format>
Builds a safely parameterized search query where optional filters activate only when passed, ready to run.
Pro tip: Tell Claude your app framework (e.g. Prisma raw, psycopg, JDBC) and it will match the exact bind-parameter placeholder style.
Deduplicate & Keep the Latest Row
5/30You are a data engineer who cleans up duplicate records with precise SQL. <context> My table has duplicate rows per key and I need exactly one row per key (usually the most recent). Return one correct, commented query that returns the deduplicated set, plus an optional DELETE variant. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table and columns: [E.G. contacts(id, email, updated_at, source)] - What defines a duplicate (the key): [E.G. SAME email] - Which row to keep: [E.G. MAX(updated_at); TIE-BREAK ON id] - Do I want a SELECT of the survivors, or a DELETE of the extras?: [SELECT / DELETE / BOTH] </inputs> <task> Write a query using ROW_NUMBER() OVER (PARTITION BY <key> ORDER BY <keep-rule>) in a CTE, then select rows where the row number = 1 to get survivors. If a DELETE is requested, show a safe DELETE that removes only rows where row number > 1, scoped by primary key. </task> <constraints> - One runnable statement per variant; valid syntax only. - Deterministic tie-break so results are stable; comment the partition and order logic. - The DELETE must target only the extra rows and be safe to run inside a transaction. </constraints> <format> Return the commented SQL (SELECT and, if asked, DELETE) in a code block, then a warning to run inside BEGIN/ROLLBACK first to verify counts. </format>
Generates a window-function dedup query keeping one row per key, with an optional safe DELETE, ready to run.
Pro tip: Always ask for the SELECT first, verify the survivor count, then run the DELETE inside a transaction you can ROLLBACK.
XML tags are just the start. Learn the full Claude workflow.
A growing library of 300+ hands-on AI tutorials covering Claude, ChatGPT, and 50+ tools. New tutorials added every week.
Aggregations & GROUP BY
5 promptsKPI Summary Query
6/30You are an analytics engineer who turns raw tables into KPI summaries. <context> I need one aggregation query that returns a compact KPI summary (counts, sums, averages) grouped by a dimension. Give me a single correct, commented statement I can drop into a dashboard. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table and columns: [E.G. orders(id, customer_id, status, total, created_at)] - Group-by dimension(s): [E.G. status, OR month] - Metrics I want: [E.G. ORDER COUNT, TOTAL REVENUE, AVG ORDER VALUE, DISTINCT CUSTOMERS] - Time window: [E.G. THIS YEAR] - Sort: [E.G. REVENUE DESC] </inputs> <task> Write a GROUP BY query with the requested aggregates, using COUNT/SUM/AVG/COUNT(DISTINCT ...) as appropriate, rounding money to 2 decimals and aliasing every metric clearly. Filter to the time window in WHERE and order the output. </task> <constraints> - One runnable statement; valid syntax only. - Every non-aggregated selected column must appear in GROUP BY; comment each metric. - Use ROUND for currency and give readable aliases. </constraints> <format> Return the commented SQL in a code block, then one line on how to add a grand-total row with ROLLUP or GROUPING SETS. </format>
Produces a grouped KPI summary query with counts, sums, and averages, ready to run.
Pro tip: List the exact metric names you want on the dashboard; Claude will alias the columns to match so no renaming is needed downstream.
Monthly / Time-Bucket Trend
7/30You are a data analyst who builds time-series aggregations. <context> I need revenue (or another metric) bucketed by a time period so I can chart a trend. Return one correct, commented query that outputs one row per period with no gaps. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table, date column, and metric: [E.G. orders(created_at, total)] - Bucket size: [DAY / WEEK / MONTH / QUARTER] - Metric(s) per bucket: [E.G. SUM(total) AS revenue, COUNT(*) AS orders] - Date range: [E.G. LAST 12 MONTHS] - Should empty periods show as zero?: [YES / NO] </inputs> <task> Write a query that truncates the date to the chosen bucket (DATE_TRUNC or the dialect equivalent), groups by that bucket, and computes the metrics ordered chronologically. If empty periods must show as zero, generate the full period series (generate_series / a calendar CTE) and LEFT JOIN the aggregates onto it, coalescing NULLs to 0. </task> <constraints> - One runnable statement; valid syntax only, correct for the stated dialect's date functions. - Comment the date-bucket logic and the gap-filling join if used. - Order strictly by period ascending. </constraints> <format> Return the commented SQL in a code block, then a note on the exact date function used and how to change the bucket size. </format>
Creates a time-bucketed trend query (with optional zero-filled gaps) ready to chart and run.
Pro tip: Say yes to zero-filling if the result feeds a line chart, so missing days don't collapse and distort the trendline.
Pivot Table via Conditional Aggregation
8/30You are a SQL expert who builds pivot/crosstab reports without extensions. <context> I need to pivot rows into columns (e.g. one row per customer with a column per status). Give me one correct, commented query using conditional aggregation that works in standard SQL. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table and columns: [E.G. orders(customer_id, status, total)] - Row identity (what each output row is): [E.G. customer_id] - Column to pivot on and its values: [E.G. status IN ('paid','pending','refunded')] - Value to aggregate in each cell: [E.G. SUM(total) OR COUNT(*)] </inputs> <task> Write a GROUP BY on the row identity with one aggregate per pivot value using SUM(CASE WHEN col = 'value' THEN metric ELSE 0 END) (or COUNT with FILTER where the dialect supports it). Alias each pivoted column with a clean name and include a total column. </task> <constraints> - One runnable statement; valid syntax only. - Use FILTER (WHERE ...) on Postgres, CASE elsewhere; comment which pattern you used and why. - One column per listed pivot value plus a total; readable aliases. </constraints> <format> Return the commented SQL in a code block, then one line on how to add a new pivot column when a new category appears. </format>
Generates a pivot/crosstab query via conditional aggregation with one column per category, ready to run.
Pro tip: List every pivot value you expect; hardcoded pivots need a new column when a category is added, and Claude will flag that in a comment.
Filter Groups With HAVING
9/30You are an analytics engineer who segments data with aggregate filters. <context> I need to keep only the GROUPS that meet an aggregate threshold (e.g. customers with more than 5 orders, categories over $10k revenue). Return one correct, commented query. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table and columns: [E.G. orders(customer_id, total)] - Group by: [E.G. customer_id] - Aggregate threshold(s): [E.G. COUNT(*) > 5 AND SUM(total) >= 1000] - Row-level filters before grouping: [OPTIONAL, E.G. status = 'paid'] - Columns/metrics to return and sort: [LIST] </inputs> <task> Write a GROUP BY query that applies row-level filters in WHERE, computes the aggregates, and filters the groups in HAVING using the thresholds. Return the group key plus the qualifying metrics, sorted as requested. </task> <constraints> - One runnable statement; valid syntax only. - Put row filters in WHERE and aggregate filters in HAVING; comment why each clause is where it is. - Alias metrics clearly. </constraints> <format> Return the commented SQL in a code block, then one line clarifying the difference between WHERE and HAVING so I don't misplace a filter later. </format>
Builds a GROUP BY + HAVING query that returns only groups meeting aggregate thresholds, ready to run.
Pro tip: If a filter references a raw column (not an aggregate) it belongs in WHERE; tell Claude and it keeps HAVING lean and fast.
Percent of Total per Group
10/30You are a data analyst who computes shares and contribution percentages. <context> I need each group's value AND its percentage of the overall total in the same result (e.g. revenue per category and its share of total revenue). Give me one correct, commented query. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table and columns: [E.G. sales(category, amount)] - Group by: [E.G. category] - Metric to total: [E.G. SUM(amount)] - Decimal places for the percentage: [E.G. 1] - Sort: [E.G. SHARE DESC] </inputs> <task> Write a query that aggregates the metric per group, then computes each group's percentage of the grand total using a window function (metric * 100.0 / SUM(metric) OVER ()) so it stays a single pass. Round the percentage and include a cumulative-share column so I can spot the top contributors. </task> <constraints> - One runnable statement; valid syntax only. - Multiply by 100.0 (not 100) to avoid integer division; comment that guard. - Include group metric, percent of total, and running cumulative percent. </constraints> <format> Return the commented SQL in a code block, then a one-line note on reading the cumulative column for an 80/20 analysis. </format>
Produces a per-group share query with percent-of-total and cumulative share columns, ready to run.
Pro tip: The cumulative-share column makes Pareto (80/20) analysis instant, ask Claude to sort by share descending so the top drivers surface first.
Window Functions
5 promptsRunning Total / Cumulative Sum
11/30You are a SQL expert fluent in window functions. <context> I need a running (cumulative) total of a metric over time or over an ordered sequence. Return one correct, commented query using a window function. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table and columns: [E.G. transactions(account_id, txn_date, amount)] - Order the running total by: [E.G. txn_date] - Reset the total per group?: [YES, PER account_id / NO, GLOBAL] - Columns to output: [LIST] </inputs> <task> Write a SELECT that returns the original rows plus a cumulative column using SUM(metric) OVER (PARTITION BY <group> ORDER BY <order col> ROWS UNBOUNDED PRECEDING). If no reset is needed, omit PARTITION BY. Keep rows ordered so the running total reads top to bottom. </task> <constraints> - One runnable statement; valid syntax only. - Specify the window frame explicitly (ROWS UNBOUNDED PRECEDING) and comment why the frame matters. - Handle same-timestamp ties deterministically in the ORDER BY. </constraints> <format> Return the commented SQL in a code block, then one line on how the result changes if I switch to RANGE instead of ROWS. </format>
Generates a running-total query with an explicit window frame, per-group or global, ready to run.
Pro tip: Add a tie-breaker column (like id) to the ORDER BY so rows with the same date accumulate in a stable, repeatable order.
Top N per Group (Rank)
12/30You are a SQL specialist who writes ranking queries. <context> I need the top N rows within each group (e.g. the 3 highest-paid employees per department, the top product per region). Return one correct, commented query. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table and columns: [E.G. employees(dept, name, salary)] - Group (partition) by: [E.G. dept] - Rank by: [E.G. salary DESC] - How many per group (N): [E.G. 3] - Tie handling: [KEEP TIES (RANK/DENSE_RANK) / STRICT CUTOFF (ROW_NUMBER)] </inputs> <task> Write a CTE that assigns a rank with the correct function (ROW_NUMBER for a strict cutoff, RANK/DENSE_RANK to keep ties) using OVER (PARTITION BY <group> ORDER BY <rank col>), then select rows where the rank <= N. Explain in a comment why ROW_NUMBER, RANK, and DENSE_RANK differ for ties. </task> <constraints> - One runnable statement; valid syntax only. - Choose the ranking function based on the requested tie handling; comment the choice. - Return the group, the ranked columns, and the rank value. </constraints> <format> Return the commented SQL in a code block, then a one-line summary of ROW_NUMBER vs RANK vs DENSE_RANK. </format>
Builds a top-N-per-group ranking query with the right function for your tie rules, ready to run.
Pro tip: Decide tie behavior up front: ROW_NUMBER gives exactly N rows, RANK/DENSE_RANK may return more when values tie at the cutoff.
Period-over-Period Growth (LAG)
13/30You are an analytics engineer who computes growth and deltas with window functions. <context> I need each period's value alongside the previous period's value and the percent change (month-over-month or day-over-day). Return one correct, commented query. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Source (table or a pre-aggregated CTE): [E.G. monthly_revenue(month, revenue)] - Order periods by: [E.G. month] - Compare within a group?: [YES, PER category / NO] - Metric to compare: [E.G. revenue] - Decimals for percent change: [E.G. 1] </inputs> <task> Write a query that uses LAG(metric) OVER (PARTITION BY <group> ORDER BY <period>) to fetch the prior period's value, then outputs the current value, previous value, absolute delta, and percent change. Guard against divide-by-zero when the previous value is 0 or NULL (use NULLIF). </task> <constraints> - One runnable statement; valid syntax only. - Use NULLIF to prevent divide-by-zero; multiply by 100.0 for the percentage; comment both guards. - Order chronologically; label the first period's change as NULL, not 0. </constraints> <format> Return the commented SQL in a code block, then one line on using LEAD instead of LAG to look forward. </format>
Creates a LAG-based growth query with prior value, delta, and safe percent change, ready to run.
Pro tip: Feed it a pre-aggregated monthly CTE rather than raw rows so the LAG compares clean period totals, not individual transactions.
Rolling / Moving Average
14/30You are a SQL expert who smooths noisy time series with moving windows. <context> I need a rolling average (e.g. 7-day moving average) to smooth a daily metric. Return one correct, commented query using a windowed frame. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Source and columns: [E.G. daily_metrics(day, value)] - Window size (periods): [E.G. 7] - Order by: [E.G. day] - Per group?: [YES, PER metric_name / NO] - Output: [DATE, RAW VALUE, MOVING AVG] </inputs> <task> Write a query using AVG(value) OVER (PARTITION BY <group> ORDER BY <period> ROWS BETWEEN N-1 PRECEDING AND CURRENT ROW) where N is the window size, returning the raw value beside the smoothed value. Note in a comment that the first N-1 rows average over fewer periods. </task> <constraints> - One runnable statement; valid syntax only. - Frame must be exactly the requested window size using ROWS BETWEEN; comment the frame math. - Keep raw and smoothed columns side by side; order ascending. </constraints> <format> Return the commented SQL in a code block, then one line on centering the window (N/2 preceding and following) if I want a centered average. </format>
Produces a rolling moving-average query with an explicit N-period frame, ready to run.
Pro tip: For a fair early-period average, ask Claude to null out the first N-1 rows so partial windows don't look like real dips.
Percentile / Quartile Segmentation (NTILE)
15/30You are a data analyst who segments records into buckets and percentiles. <context> I need to split rows into equal buckets (quartiles, deciles) or compute a percentile rank, e.g. to tier customers by spend. Return one correct, commented query. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table and columns: [E.G. customers(id, lifetime_value)] - Metric to segment on: [E.G. lifetime_value] - Number of buckets: [E.G. 4 FOR QUARTILES, 10 FOR DECILES] - Segment within a group?: [YES, PER region / NO] - Also include a percentile rank (0-1)?: [YES / NO] </inputs> <task> Write a query using NTILE(<buckets>) OVER (PARTITION BY <group> ORDER BY <metric>) to assign each row a bucket, and if requested add PERCENT_RANK() or CUME_DIST() for a 0-1 percentile. Label bucket 1 and the top bucket in a comment (e.g. bucket 4 = top spenders) so the tiers are unambiguous. </task> <constraints> - One runnable statement; valid syntax only. - Comment which bucket number is the high end vs low end. - Include the metric, the bucket, and (if asked) the percentile column. </constraints> <format> Return the commented SQL in a code block, then one line on NTILE vs PERCENTILE_CONT for exact percentile thresholds. </format>
Generates an NTILE/percentile segmentation query to tier rows into buckets, ready to run.
Pro tip: NTILE buckets by row count, not value ranges; if you need exact cutoffs (e.g. top 10% by value) ask for PERCENTILE_CONT instead.
CTEs & Subqueries
5 promptsMulti-Step Analysis With Chained CTEs
16/30You are a senior analytics engineer who writes readable, layered SQL. <context> I have a multi-step calculation that would be an unreadable nest of subqueries. I want it expressed as chained CTEs, each a clear step. Return one correct, commented query. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Tables and columns: [LIST] - The steps in plain English: [E.G. 1) FILTER PAID ORDERS 2) SUM REVENUE PER CUSTOMER 3) JOIN CUSTOMER INFO 4) KEEP TOP 100] - Final output columns and sort: [LIST] </inputs> <task> Write a WITH clause where each CTE is one named step from the list (e.g. paid_orders, revenue_per_customer, enriched), each building on the previous, ending in a final SELECT. Name every CTE for what it produces and add a one-line comment above each explaining its purpose. </task> <constraints> - One runnable statement; valid syntax only. - One logical step per CTE; no redundant recomputation; descriptive CTE names. - Final SELECT only references the last CTE(s); comment the data flow. </constraints> <format> Return the commented SQL in a code block, then a short list showing the pipeline of CTE names in order. </format>
Produces a layered, commented chained-CTE query that reads as clear sequential steps, ready to run.
Pro tip: Describe your logic as numbered plain-English steps; Claude maps each number to one named CTE so the query mirrors your thinking.
Recursive CTE (Hierarchies & Series)
17/30You are a SQL expert who writes recursive queries. <context> I need a recursive CTE to walk a hierarchy (full org tree, category tree, bill of materials) or to generate a series (dates, numbers). Return one correct, commented query with a termination guard. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Use case: [HIERARCHY / SERIES] - If hierarchy: table, id, parent_id, and label columns: [E.G. categories(id, parent_id, name)] - Starting point (anchor): [E.G. ROOT ROWS WHERE parent_id IS NULL / A SPECIFIC id] - If series: start, end, and step: [E.G. 2026-01-01 TO 2026-12-31 BY 1 MONTH] - Extra output (e.g. depth, path): [OPTIONAL] </inputs> <task> Write a WITH RECURSIVE query: an anchor member (the starting rows) UNION ALL a recursive member that joins back to the CTE. For a hierarchy, track a depth level and a breadcrumb path; for a series, increment until the end bound. Include a safeguard against infinite loops (depth cap or cycle detection). </task> <constraints> - One runnable statement; valid syntax only for the dialect (WITH RECURSIVE, or the SQL Server CTE form). - Comment the anchor vs recursive member clearly and the termination condition. - Include a depth/level column and guard against cycles. </constraints> <format> Return the commented SQL in a code block, then one line on adding cycle detection if the data might contain loops. </format>
Generates a recursive CTE for full hierarchies or generated series with a loop safeguard, ready to run.
Pro tip: Ask for a materialized 'path' column (e.g. Root > Sub > Leaf) so the recursive output is instantly readable as a tree.
Correlated Subquery for Per-Row Lookups
18/30You are a SQL specialist who knows when correlated subqueries are the cleanest tool. <context> I need a per-row value that depends on the current row (e.g. each customer's most recent order date, or a count of related rows). Return one correct, commented query and note when to switch to a JOIN for speed. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Outer table and columns: [E.G. customers(id, name)] - Related table and link: [E.G. orders(customer_id, created_at, total)] - Per-row value(s) I need: [E.G. LATEST ORDER DATE, TOTAL ORDER COUNT, LAST ORDER TOTAL] - Filters/sort on the outer query: [OPTIONAL] </inputs> <task> Write an outer SELECT with a scalar correlated subquery in the select list for each per-row value (correlated on the outer row's key). Then, in a comment, show the equivalent LEFT JOIN + GROUP BY (or LATERAL/window) rewrite and note that on large tables the set-based version usually scales better. </task> <constraints> - One runnable statement plus the commented rewrite; valid syntax only. - Each subquery must return exactly one value per outer row; comment the correlation. - Explain the performance trade-off in a comment. </constraints> <format> Return the commented SQL in a code block, then one line on when the correlated form is fine vs when to refactor to a join. </format>
Builds a correlated-subquery lookup query with a set-based rewrite noted, ready to run.
Pro tip: Correlated subqueries are readable for a few lookups; if you need many per-row values, ask Claude for the single JOIN + GROUP BY version.
Refactor Nested Subqueries Into CTEs
19/30You are a SQL reviewer who refactors messy queries into clean, maintainable ones. <context> I have a hard-to-read query with subqueries nested inside subqueries. I want it refactored into readable CTEs with identical results. Return the rewritten, commented query. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - My current query: [PASTE THE FULL QUERY] - Anything I want preserved (column names, ordering, exact result): [NOTES] </inputs> <task> Refactor the query into a WITH clause where each formerly-nested subquery becomes a named CTE, flattening the nesting. Preserve the exact output columns, order, and semantics. Point out (as comments) any redundant or repeated subqueries you were able to compute once and reuse. </task> <constraints> - One runnable statement; valid syntax only; identical result set to the original. - Descriptive CTE names; comment what each replaced. - Do not change filters or joins that would alter results; flag anything ambiguous instead of guessing. </constraints> <format> Return the refactored, commented SQL in a code block, then a short before/after note on what became clearer and any dedup you did. </format>
Rewrites a nested-subquery query into clean, named CTEs with identical results, ready to run.
Pro tip: Paste the exact original query and any sample output; Claude preserves semantics and calls out subqueries it could compute once and reuse.
Funnel / Sessionization Query
20/30You are a product analytics engineer who builds funnel and session queries from event data. <context> I have an events table and need either a step-by-step funnel (how many users reach each step) or sessions grouped by inactivity gaps. Return one correct, commented query built from CTEs. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Events table and columns: [E.G. events(user_id, event_name, occurred_at)] - Goal: [FUNNEL / SESSIONIZATION] - If funnel: ordered steps: [E.G. 'view', 'add_to_cart', 'checkout', 'purchase'] - If sessions: inactivity gap that starts a new session: [E.G. 30 MINUTES] - Time window: [E.G. LAST 30 DAYS] </inputs> <task> For a FUNNEL: use CTEs to find the first timestamp of each step per user (respecting step order), then count users reaching each step and the step-to-step conversion rate. For SESSIONIZATION: order events per user, flag a new session when the gap since the prior event exceeds the threshold (LAG on occurred_at), and assign session ids with a cumulative sum of the flags. Comment the core logic either way. </task> <constraints> - One runnable statement; valid syntax only. - Funnel steps must be counted in order (a later step only counts if the earlier one happened first); comment that ordering. - Sessionization gap logic must use LAG + running sum; comment the threshold. </constraints> <format> Return the commented SQL in a code block, then one line on adjusting the funnel window or session gap. </format>
Generates a CTE-based funnel or sessionization query from raw event data, ready to run.
Pro tip: For funnels, tell Claude whether steps must be strictly ordered per user; it changes the counting logic and the resulting conversion rates.
Schema, DDL & Migrations
5 promptsDesign a Normalized Schema
21/30You are a database architect who designs normalized, well-constrained schemas. <context> I'm describing an app in plain English and need a normalized relational schema as runnable CREATE TABLE statements. Return commented DDL I can execute in order. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SQL SERVER] - What the app does and its main entities: [E.G. A BOOKING APP: users, venues, bookings, reviews] - Key relationships: [E.G. A USER HAS MANY BOOKINGS; A BOOKING BELONGS TO ONE VENUE] - Important fields per entity: [LIST] - Any known constraints/rules: [E.G. EMAIL UNIQUE, RATING 1-5] </inputs> <task> Design a normalized schema (3NF where sensible). Output CREATE TABLE statements in dependency order with primary keys, foreign keys (with ON DELETE behavior), NOT NULL, UNIQUE, CHECK constraints, sensible types, and created_at/updated_at timestamps. Add the indexes the obvious queries will need. Comment each table's purpose and each non-obvious constraint. </task> <constraints> - Runnable DDL in the stated dialect, ordered so foreign keys resolve; valid syntax only. - Use appropriate types (no everything-as-text); explicit FK actions; comment design choices. - Include indexes for foreign keys and common lookups. </constraints> <format> Return the commented DDL in a code block, then a short note on any denormalization trade-offs you deliberately avoided or would consider. </format>
Produces a normalized schema as ordered, constrained, commented CREATE TABLE DDL, ready to run.
Pro tip: List the top 3 queries the app runs most; Claude will add exactly the indexes those need instead of guessing.
Safe Additive Migration
22/30You are a database migration expert focused on zero-downtime changes. <context> I need to add a column (or table) to a live, populated table without breaking production. Return a safe, ordered, commented migration plus a rollback. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SQL SERVER] - Table and change: [E.G. ADD status TEXT TO orders, DEFAULT 'pending'] - Should existing rows be backfilled?: [YES, WITH VALUE X / NO] - Must it end up NOT NULL?: [YES / NO] - New constraints/indexes needed: [E.G. INDEX ON status] - Approx row count (for locking concerns): [E.G. 5M ROWS] </inputs> <task> Write the migration as safe, ordered steps: add the column as nullable first, backfill in the same or a separate step, add the default, then add NOT NULL/constraints/indexes last (using CREATE INDEX CONCURRENTLY on Postgres for large tables). Explain in comments why the order avoids long locks. Include a matching rollback (DOWN) script. </task> <constraints> - Runnable, ordered DDL/DML; valid syntax only. - Avoid a blocking rewrite/long lock on a large table; comment the locking behavior of each step. - Provide an explicit rollback that undoes every step. </constraints> <format> Return the UP and DOWN scripts in separate labeled code blocks, then one line on running the backfill in batches if the table is huge. </format>
Generates a zero-downtime additive migration with ordered steps and a rollback, ready to run.
Pro tip: Give Claude the row count; above a few million rows it will split the backfill into batches and use CONCURRENTLY to dodge table locks.
Indexing Strategy for a Query
23/30You are a database performance engineer who designs indexes. <context> A specific query is slow and I want the right indexes (not a shotgun of them). Return the CREATE INDEX statements with reasoning. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SQL SERVER] - The query (or query shape): [PASTE IT] - Relevant table sizes: [E.G. orders 20M ROWS] - Existing indexes: [LIST OR 'JUST PRIMARY KEYS'] - Read vs write balance: [READ-HEAVY / WRITE-HEAVY] </inputs> <task> Recommend the minimal set of indexes to speed up the query: identify the columns used in WHERE, JOIN, ORDER BY, and GROUP BY, propose composite indexes in the correct column order (equality columns first, then range/sort), and note where a covering index or partial index helps. Output the CREATE INDEX DDL and, in comments, explain each index and the write-cost trade-off. </task> <constraints> - Runnable CREATE INDEX statements; valid syntax only; CONCURRENTLY on Postgres for live tables. - Justify column order in composite indexes; avoid redundant/overlapping indexes. - Flag any index that duplicates an existing one. </constraints> <format> Return the commented DDL in a code block, then a short note on how to confirm the index is used (EXPLAIN) and the write overhead to expect. </format>
Recommends a minimal, correctly-ordered index set as commented CREATE INDEX DDL, ready to run.
Pro tip: Paste the actual query plus EXPLAIN output; Claude reads the plan and targets the exact scan or sort that's costing you time.
Data-Integrity Constraints
24/30You are a database engineer who enforces data quality at the schema level. <context> Bad data is getting into a table and I want the database to reject it. Return ALTER TABLE statements adding constraints, with a check for existing violations first. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SQL SERVER] - Table and columns: [E.G. accounts(id, email, status, balance, plan)] - Rules to enforce: [E.G. email UNIQUE + FORMAT, status IN SET, balance >= 0, plan REFERENCES plans] - Can I fix or must I skip existing bad rows?: [FIX / REPORT ONLY] </inputs> <task> First write SELECTs that find rows currently violating each rule (so I can clean them before the constraint is added). Then write the ALTER TABLE ... ADD CONSTRAINT statements: CHECK constraints, UNIQUE, NOT NULL, and FOREIGN KEY as needed. Comment each constraint and why adding it will fail if violations remain. </task> <constraints> - Runnable validation SELECTs and ALTER statements; valid syntax only. - Order: detect violations, (optionally) clean, then add constraints. - Name each constraint explicitly so it can be dropped later; comment each. </constraints> <format> Return the detection SELECTs and the ALTER statements in separate labeled code blocks, then one line on validating constraints NOT VALID first on large tables (Postgres). </format>
Generates violation-detection SELECTs plus named constraint ALTERs to enforce data integrity, ready to run.
Pro tip: Always run the detection SELECTs first; adding a constraint to a table with existing bad rows fails, and Claude sequences it so you clean up before enforcing.
Idempotent Seed / Reference Data
25/30You are a backend engineer who writes safe, re-runnable seed scripts. <context> I need to insert reference/lookup data (statuses, roles, plans, categories) in a script that can run multiple times without creating duplicates. Return one idempotent, commented script. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SQL SERVER] - Target table and columns: [E.G. plans(code, name, price_cents, is_active)] - The rows to seed: [LIST THE VALUES] - Natural key that must stay unique: [E.G. code] - On conflict, update or ignore?: [UPDATE THE ROW / LEAVE IT AS IS] </inputs> <task> Write an idempotent upsert: use INSERT ... ON CONFLICT (key) DO UPDATE/DO NOTHING on Postgres, INSERT ... ON DUPLICATE KEY UPDATE on MySQL, or MERGE on SQL Server. Ensure re-running the script leaves the table in the same correct state. Comment the conflict target and what happens on repeat runs. </task> <constraints> - One runnable script; valid syntax for the stated dialect only. - Truly idempotent (safe to run N times); relies on the natural key, not auto-increment ids. - Comment each row group and the conflict behavior. </constraints> <format> Return the commented script in a code block, then one line on wrapping it in a transaction so a partial failure rolls back cleanly. </format>
Produces an idempotent upsert seed script that is safe to re-run without duplicates, ready to run.
Pro tip: Seed on a natural key (like a code), never the auto-increment id; that's what makes the script safe to run in every environment.
Most people use 10% of Claude. Tutorials unlock the rest.
AI Academy: 300+ hands-on tutorials on Claude, ChatGPT, Midjourney, and 50+ AI tools. New tutorials added every week.
Query Optimization
5 promptsRewrite a Slow Query
26/30You are a SQL performance engineer who rewrites slow queries into fast ones. <context> I have a query that runs too slowly and I want a faster version that returns identical results. Return the optimized, commented query and a plain-English list of what you changed. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - The slow query: [PASTE IT] - Table sizes and existing indexes: [LIST] - How slow / how fast it needs to be: [E.G. 40s NOW, NEED UNDER 2s] - Anything that must not change (result shape, ordering): [NOTES] </inputs> <task> Analyze the query for common performance killers (functions on indexed columns in WHERE, SELECT *, correlated subqueries that could be joins, OR conditions that block index use, needless DISTINCT, implicit type casts) and rewrite it to be faster while returning the exact same rows. List every change and why it helps. </task> <constraints> - One runnable rewritten statement; valid syntax only; identical result set. - Don't change semantics; if a rewrite risks different results, flag it instead of applying it. - Comment each optimization inline. </constraints> <format> Return the optimized SQL in a code block, then a numbered list of the changes and any indexes I should add for the biggest win. </format>
Rewrites a slow query into a faster, result-identical version with a changelog, ready to run.
Pro tip: Include existing indexes and table sizes; the fastest fix is often a new index the rewrite depends on, and Claude will call it out explicitly.
Read & Fix an EXPLAIN Plan
27/30You are a query-tuning expert who reads execution plans. <context> I have a query and its EXPLAIN (or EXPLAIN ANALYZE) output and want to know what's slow and how to fix it. Return the diagnosis plus a fixed query and/or index. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SQL SERVER] - The query: [PASTE IT] - The EXPLAIN / EXPLAIN ANALYZE output: [PASTE IT] - Table sizes and existing indexes: [LIST] </inputs> <task> Walk through the plan: identify the expensive nodes (sequential scans on big tables, nested loops over many rows, large sorts/spills to disk, bad row-count estimates), explain in plain English what each means, then give concrete fixes: the index to add, the query rewrite, or the statistics to refresh. Provide the corrected SQL/DDL. </task> <constraints> - Ground the analysis in the actual plan numbers provided; don't invent nodes not present. - Provide runnable, valid DDL/SQL for each fix. - Prioritize fixes by expected impact; comment each. </constraints> <format> Return a short plan-reading summary, then the fix SQL/DDL in a code block, then how to re-check the plan to confirm improvement. </format>
Diagnoses an EXPLAIN plan and returns prioritized, runnable fixes (index or rewrite), ready to run.
Pro tip: Paste EXPLAIN ANALYZE (not just EXPLAIN) so Claude sees real timings and row counts, not just estimates, and targets the true bottleneck.
Fix an N+1 With a Set-Based Query
28/30You are a backend performance engineer who eliminates N+1 query patterns. <context> My app runs one query per row in a loop (an N+1 problem). I want a single set-based SQL query that fetches everything at once. Return the consolidated, commented query. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - The loop / per-row query I run today: [PASTE THE INNER QUERY OR DESCRIBE IT] - The parent set I loop over: [E.G. A LIST OF order_ids / ALL ACTIVE users] - Related data I fetch per parent: [E.G. LINE ITEMS, LATEST STATUS, TOTALS] - How I want it shaped for the app: [ONE ROW PER PARENT / ONE ROW PER CHILD / AGGREGATED] </inputs> <task> Replace the per-row loop with one query using JOINs and/or aggregation (or json_agg/array_agg to nest children under each parent where the dialect supports it). Return the shape the app needs so it can consume the result in a single round trip. Comment how this collapses N+1 calls into one. </task> <constraints> - One runnable statement; valid syntax only. - Use IN (...) or a JOIN against the parent set instead of a per-row query; comment the change. - If nesting children, use the dialect's aggregate JSON/array function and note it. </constraints> <format> Return the commented SQL in a code block, then one line on how the app should map the single result set back to its objects. </format>
Collapses an N+1 loop into one set-based query (with optional nested children), ready to run.
Pro tip: Tell Claude the exact shape your app wants (nested children vs flat rows); on Postgres it can return ready-to-parse JSON per parent.
Keyset Pagination (No More OFFSET)
29/30You are a backend engineer who builds fast pagination for large tables. <context> Deep pagination with LIMIT/OFFSET has gotten slow because high offsets scan and discard rows. I want efficient keyset (cursor) pagination. Return the query and how to page through it. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SNOWFLAKE / SQL SERVER] - Table and columns: [E.G. posts(id, created_at, title)] - Sort order for the list: [E.G. created_at DESC, id DESC] - Page size: [E.G. 20] - Columns to return: [LIST] - Filters that always apply: [OPTIONAL] </inputs> <task> Write a keyset-pagination query: order by a unique, stable key (add a tie-breaker like id so the order is total), and fetch the next page using a WHERE clause comparing against the last row's cursor values (e.g. (created_at, id) < (:last_created_at, :last_id)) with LIMIT page_size. Show the first-page query (no cursor) and the next-page query (with cursor params). </task> <constraints> - Two runnable statements (first page, next page); valid syntax only; parameterized cursor. - The ORDER BY must be a total order (include a unique tie-breaker); comment why OFFSET is avoided. - Explain what cursor values the client must send back for the next page. </constraints> <format> Return both queries in a code block, then a short note on how the client tracks and passes the cursor between pages. </format>
Generates efficient keyset (cursor) pagination queries that stay fast at any depth, ready to run.
Pro tip: Keyset paging needs a total order; always include a unique tie-breaker column in ORDER BY or rows can be skipped or repeated between pages.
Batch a Big UPDATE/DELETE Safely
30/30You are a database engineer who runs large data changes without locking production. <context> I need to UPDATE or DELETE millions of rows, but doing it in one statement locks the table and bloats the transaction log. I want a safe batched approach. Return the batched, commented script. </context> <inputs> - SQL dialect: [POSTGRES / MYSQL / SQL SERVER] - Operation: [UPDATE ... SET ... / DELETE] - Table and the WHERE that identifies target rows: [E.G. DELETE FROM events WHERE created_at < '2025-01-01'] - Approx rows affected: [E.G. 30M] - Batch size preference: [E.G. 10000] - Any ordering or key column to batch on: [E.G. id] </inputs> <task> Write a batched loop that changes rows in chunks of the given size (using a key range or LIMIT-based selection depending on the dialect), committing each batch so locks release and the transaction log stays small, until no rows remain. Include a verification COUNT before and after. Comment the loop, the commit points, and how to throttle between batches. </task> <constraints> - Runnable script in the stated dialect (procedural block or a documented repeatable statement); valid syntax only. - Each batch commits separately; the WHERE must reliably target only unprocessed rows so it terminates. - Comment how to pause/resume and how to verify progress. </constraints> <format> Return the commented batching script in a code block, then one line on running it during low-traffic hours and watching lock/replication lag. </format>
Produces a safe, batched UPDATE/DELETE script that avoids long locks on huge tables, ready to run.
Pro tip: Test the batch loop on a copy first and start with a small batch size; raise it only after confirming locks and replication lag stay healthy.
Frequently Asked Questions
Prompts are the starting line. Tutorials are the finish.
A growing library of 300+ hands-on tutorials on ChatGPT, Claude, Midjourney, and 50+ AI tools. New tutorials added every week.
7-day free trial. Cancel anytime.