CoolFace
Apppublic

dkAmulet/sql-query-optimizer

sourceHugging Facemitupdated 5mo agoView on Hugging Face
0likes
tasks.py533 linesDownload Raw Back to root
1"""2Task definitions and deterministic graders — v2 (4 tasks).3 4Tasks:5  1. select_star_removal      — easy    Remove SELECT *6  2. subquery_to_join         — medium  IN subquery → JOIN7  3. aggregation_optimization — hard    Correlated subqueries → GROUP BY8  4. cte_refactoring          — expert  Deeply nested subqueries → CTEs9 10All graders return (score: float, breakdown: dict, feedback: str).11Scores are always in [0.0, 1.0].12"""13from __future__ import annotations14 15import re16import sqlite317from typing import Dict, List, Tuple18 19from models import ExecutionMetrics20 21# ── Schema DDL (shown to agent) ───────────────────────────────────────────────22 23SCHEMA_DDL = """\24-- Users registered on the platform25CREATE TABLE users (26    user_id    INTEGER PRIMARY KEY,27    username   TEXT    NOT NULL,28    email      TEXT    NOT NULL,29    first_name TEXT,30    last_name  TEXT,31    city       TEXT,32    country    TEXT,33    is_active  INTEGER DEFAULT 1,34    created_at TEXT35);36 37-- Two-level product taxonomy38CREATE TABLE categories (39    category_id        INTEGER PRIMARY KEY,40    name               TEXT NOT NULL,41    parent_category_id INTEGER42);43 44-- Product suppliers45CREATE TABLE suppliers (46    supplier_id INTEGER PRIMARY KEY,47    name        TEXT NOT NULL,48    country     TEXT,49    rating      REAL50);51 52-- Products for sale53CREATE TABLE products (54    product_id   INTEGER PRIMARY KEY,55    name         TEXT    NOT NULL,56    category_id  INTEGER REFERENCES categories(category_id),57    supplier_id  INTEGER REFERENCES suppliers(supplier_id),58    price        REAL,59    sku          TEXT UNIQUE,60    is_available INTEGER DEFAULT 161);62 63-- Stock levels per product64CREATE TABLE inventory (65    inventory_id  INTEGER PRIMARY KEY,66    product_id    INTEGER UNIQUE REFERENCES products(product_id),67    stock_qty     INTEGER DEFAULT 0,68    reorder_level INTEGER DEFAULT 10,69    last_updated  TEXT70);71 72-- Customer orders73CREATE TABLE orders (74    order_id     INTEGER PRIMARY KEY,75    user_id      INTEGER REFERENCES users(user_id),76    status       TEXT,   -- pending|processing|shipped|delivered|cancelled77    total_amount REAL,78    coupon_id    INTEGER,79    created_at   TEXT80);81 82-- Line items within an order83CREATE TABLE order_items (84    item_id    INTEGER PRIMARY KEY,85    order_id   INTEGER REFERENCES orders(order_id),86    product_id INTEGER REFERENCES products(product_id),87    quantity   INTEGER,88    unit_price REAL89);90 91-- Product reviews92CREATE TABLE reviews (93    review_id   INTEGER PRIMARY KEY,94    product_id  INTEGER REFERENCES products(product_id),95    user_id     INTEGER REFERENCES users(user_id),96    rating      INTEGER CHECK(rating BETWEEN 1 AND 5),97    review_text TEXT,98    created_at  TEXT99);100 101-- Discount coupons102CREATE TABLE coupons (103    coupon_id    INTEGER PRIMARY KEY,104    code         TEXT UNIQUE,105    discount_pct REAL,106    is_active    INTEGER DEFAULT 1107);108 109-- Key indexes (exploit these for performance)110CREATE INDEX idx_users_active    ON users(is_active);111CREATE INDEX idx_users_country   ON users(country, is_active);112CREATE INDEX idx_orders_user     ON orders(user_id);113CREATE INDEX idx_orders_status   ON orders(status);114CREATE INDEX idx_orders_created  ON orders(created_at);115CREATE INDEX idx_items_order     ON order_items(order_id);116CREATE INDEX idx_items_product   ON order_items(product_id);117CREATE INDEX idx_products_cat    ON products(category_id);118CREATE INDEX idx_products_avail  ON products(is_available);119CREATE INDEX idx_reviews_product ON reviews(product_id);120CREATE INDEX idx_inventory_prod  ON inventory(product_id);121"""122 123# ── Task registry ─────────────────────────────────────────────────────────────124 125TASK_ORDER: List[str] = [126    "select_star_removal",127    "subquery_to_join",128    "aggregation_optimization",129    "cte_refactoring",130]131 132TASKS: Dict[str, dict] = {133    "select_star_removal": {134        "name": "SELECT * Elimination",135        "difficulty": "easy",136        "max_steps": 5,137        "description": (138            "The slow query uses SELECT * to fetch every column from the users table. "139            "Rewrite it to return ONLY the three required columns: user_id, username, email. "140            "The query MUST still return ALL active users (is_active = 1)."141        ),142        "slow_query": (143            "SELECT *\n"144            "FROM users\n"145            "WHERE is_active = 1"146        ),147    },148    "subquery_to_join": {149        "name": "Correlated Subquery to JOIN",150        "difficulty": "medium",151        "max_steps": 6,152        "description": (153            "The slow query uses IN (SELECT ...) to filter orders, causing repeated inner-query execution. "154            "Rewrite it as an efficient JOIN. "155            "Return: order_id, user_id, total_amount "156            "for DELIVERED orders from ACTIVE users in the USA."157        ),158        "slow_query": (159            "SELECT order_id, user_id, total_amount\n"160            "FROM   orders\n"161            "WHERE  user_id IN (\n"162            "    SELECT user_id\n"163            "    FROM   users\n"164            "    WHERE  country = 'USA'\n"165            "      AND  is_active = 1\n"166            ") AND status = 'delivered'"167        ),168    },169    "aggregation_optimization": {170        "name": "Aggregation Optimization",171        "difficulty": "hard",172        "max_steps": 8,173        "description": (174            "This query computes per-category revenue using correlated subqueries "175            "inside the SELECT list, which is O(n^2). "176            "Rewrite it using explicit JOINs and GROUP BY. "177            "Return: category_name (TEXT), total_revenue (REAL) "178            "for categories where total_revenue > 10000, "179            "ordered by total_revenue DESC."180        ),181        "slow_query": (182            "SELECT\n"183            "    (SELECT name FROM categories\n"184            "     WHERE  category_id = p.category_id) AS category_name,\n"185            "    (SELECT SUM(oi.quantity * oi.unit_price)\n"186            "     FROM   order_items oi\n"187            "     WHERE  oi.product_id IN (\n"188            "         SELECT product_id FROM products\n"189            "         WHERE  category_id = p.category_id\n"190            "     )) AS total_revenue\n"191            "FROM   products p\n"192            "GROUP  BY p.category_id\n"193            "HAVING total_revenue > 10000\n"194            "ORDER  BY total_revenue DESC"195        ),196    },197    "cte_refactoring": {198        "name": "CTE Refactoring",199        "difficulty": "expert",200        "max_steps": 10,201        "description": (202            "This query identifies the top 5 customers by lifetime spend, "203            "using deeply nested subqueries that are hard to read and slow to execute. "204            "Rewrite it using Common Table Expressions (WITH clauses) for clarity and performance. "205            "Return: username, email, total_spend (REAL), order_count (INTEGER) "206            "for the top 5 active customers by total_spend, ordered by total_spend DESC."207        ),208        "slow_query": (209            "SELECT username, email, total_spend, order_count\n"210            "FROM (\n"211            "    SELECT\n"212            "        (SELECT username FROM users WHERE user_id = o.user_id) AS username,\n"213            "        (SELECT email    FROM users WHERE user_id = o.user_id) AS email,\n"214            "        SUM(o.total_amount) AS total_spend,\n"215            "        COUNT(*) AS order_count\n"216            "    FROM orders o\n"217            "    WHERE o.user_id IN (\n"218            "        SELECT user_id FROM users WHERE is_active = 1\n"219            "    )\n"220            "    GROUP BY o.user_id\n"221            ") ranked\n"222            "ORDER BY total_spend DESC\n"223            "LIMIT 5"224        ),225    },226}227 228# ── SQL utilities ─────────────────────────────────────────────────────────────229 230def get_query_metrics(query: str, conn: sqlite3.Connection) -> ExecutionMetrics:231    try:232        plan_rows  = conn.execute(f"EXPLAIN QUERY PLAN {query}").fetchall()233        plan_texts = [str(r) for r in plan_rows]234        flat       = " ".join(plan_texts).upper()235        return ExecutionMetrics(236            uses_index=(237                "USING INDEX" in flat or238                ("SEARCH" in flat and flat.count("SCAN TABLE") == 0)239            ),240            full_scan_count=flat.count("SCAN TABLE"),241            query_plan=plan_texts,242        )243    except Exception:244        return ExecutionMetrics()245 246 247def _norm(q: str) -> str:248    return re.sub(r"\s+", " ", q.strip().upper())249 250def _has_select_star(q):  return bool(re.search(r"SELECT\s+\*",             _norm(q)))251def _uses_join(q):        return bool(re.search(r"\bJOIN\b",                _norm(q)))252def _uses_in_sub(q):      return bool(re.search(r"\bIN\s*\(\s*SELECT\b",    _norm(q)))253def _uses_sel_sub(q):     return bool(re.search(r"SELECT\s+\(\s*SELECT\b",  _norm(q)))254def _uses_cte(q):         return bool(re.search(r"^\s*WITH\b",              q.strip(), re.IGNORECASE))255def _uses_groupby(q):     return bool(re.search(r"\bGROUP\s+BY\b",          _norm(q)))256 257def _run(query: str, conn: sqlite3.Connection):258    cur  = conn.execute(query)259    cols = [d[0].lower() for d in cur.description]260    rows = cur.fetchall()261    return cols, rows262 263# ── Graders ───────────────────────────────────────────────────────────────────264 265GraderResult = Tuple[float, Dict[str, float], str]266 267 268def grade_task1(query: str, conn: sqlite3.Connection) -> GraderResult:269    bd   = dict(validity=0.0, correctness=0.0, performance=0.0, style=0.0)270    msgs = []271 272    try:273        cols, rows = _run(query, conn)274        bd["validity"] = 0.10275        msgs.append("✓ Valid SQL")276    except Exception as e:277        msgs.append(f"✗ SQL error: {e}")278        return 0.001, bd, " | ".join(msgs)279 280    required = ["user_id", "username", "email"]281    missing  = [c for c in required if c not in cols]282    if missing:283        bd["correctness"] = 0.05284        msgs.append(f"✗ Missing columns: {missing}")285    else:286        ref = {r[0] for r in conn.execute("SELECT user_id FROM users WHERE is_active=1").fetchall()}287        got = {row[cols.index("user_id")] for row in rows}288        if got == ref:289            bd["correctness"] = 0.40290            msgs.append(f"✓ Correct ({len(ref)} users)")291        elif len(got & ref) / max(len(ref), 1) >= 0.95:292            bd["correctness"] = 0.20293            msgs.append("~ Partial correctness")294        else:295            msgs.append("✗ Wrong result set")296 297    if not _has_select_star(query):298        bd["style"] = 0.30299        msgs.append("✓ No SELECT *")300    else:301        msgs.append("✗ Still uses SELECT *")302 303    m = get_query_metrics(query, conn)304    if m.uses_index:305        bd["performance"] = 0.20306        msgs.append("✓ Index scan")307    elif m.full_scan_count <= 1:308        bd["performance"] = 0.10309        msgs.append("~ Single scan")310    else:311        msgs.append("✗ Multiple full scans")312 313    return min(0.999, max(0.001, sum(bd.values()))), bd, " | ".join(msgs)314 315 316def grade_task2(query: str, conn: sqlite3.Connection) -> GraderResult:317    bd   = dict(validity=0.0, correctness=0.0, performance=0.0, style=0.0)318    msgs = []319 320    try:321        cols, rows = _run(query, conn)322        bd["validity"] = 0.10323        msgs.append("✓ Valid SQL")324    except Exception as e:325        msgs.append(f"✗ SQL error: {e}")326        return 0.001, bd, " | ".join(msgs)327 328    ref = {r[0] for r in conn.execute(329        "SELECT order_id FROM orders WHERE status='delivered' "330        "AND user_id IN (SELECT user_id FROM users WHERE country='USA' AND is_active=1)"331    ).fetchall()}332 333    required = ["order_id", "user_id", "total_amount"]334    missing  = [c for c in required if c not in cols]335    if missing:336        bd["correctness"] = 0.05337        msgs.append(f"✗ Missing columns: {missing}")338    else:339        got     = {row[cols.index("order_id")] for row in rows}340        overlap = len(got & ref) / max(len(ref), 1)341        if got == ref:342            bd["correctness"] = 0.40343            msgs.append(f"✓ Correct ({len(ref)} orders)")344        elif overlap >= 0.90:345            bd["correctness"] = 0.20346            msgs.append(f"~ {overlap:.0%} correct")347        else:348            msgs.append(f"✗ Wrong results ({overlap:.0%})")349 350    if not _uses_in_sub(query):351        bd["style"] = 0.20352        msgs.append("✓ No IN subquery")353    else:354        msgs.append("✗ Still uses IN subquery")355 356    uses_j = _uses_join(query)357    m      = get_query_metrics(query, conn)358    if uses_j and m.uses_index:359        bd["performance"] = 0.30360        msgs.append("✓ JOIN + index")361    elif uses_j:362        bd["performance"] = 0.15363        msgs.append("~ JOIN but no index")364    elif m.uses_index:365        bd["performance"] = 0.10366        msgs.append("~ Index but no JOIN")367    else:368        msgs.append("✗ No JOIN, no index")369 370    return min(0.999, max(0.001, sum(bd.values()))), bd, " | ".join(msgs)371 372 373def grade_task3(query: str, conn: sqlite3.Connection) -> GraderResult:374    bd   = dict(validity=0.0, correctness=0.0, performance=0.0, style=0.0)375    msgs = []376 377    try:378        cols, rows = _run(query, conn)379        bd["validity"] = 0.10380        msgs.append("✓ Valid SQL")381    except Exception as e:382        msgs.append(f"✗ SQL error: {e}")383        return 0.001, bd, " | ".join(msgs)384 385    ref_rows = conn.execute("""386        SELECT c.name, SUM(oi.quantity * oi.unit_price) AS total_revenue387        FROM categories c388        JOIN products p ON p.category_id = c.category_id389        JOIN order_items oi ON oi.product_id = p.product_id390        GROUP BY c.category_id, c.name391        HAVING SUM(oi.quantity * oi.unit_price) > 10000392        ORDER BY total_revenue DESC393    """).fetchall()394    ref_set = {(r[0], round(float(r[1]), 0)) for r in ref_rows}395 396    required = ["category_name", "total_revenue"]397    missing  = [c for c in required if c not in cols]398    if missing:399        bd["correctness"] = 0.05400        msgs.append(f"✗ Missing columns: {missing}")401    else:402        got_set = {(row[cols.index("category_name")], round(float(row[cols.index("total_revenue")]), 0)) for row in rows}403        overlap = len(got_set & ref_set) / max(len(ref_set), 1)404        if got_set == ref_set:405            bd["correctness"] = 0.35406            msgs.append(f"✓ Exact match ({len(ref_set)} categories)")407        elif overlap >= 0.80:408            bd["correctness"] = 0.18409            msgs.append(f"~ {overlap:.0%} match")410        else:411            msgs.append(f"✗ Wrong results ({overlap:.0%})")412 413    if not _uses_sel_sub(query) and not _uses_in_sub(query):414        bd["style"] = 0.25415        msgs.append("✓ No correlated subqueries")416    elif not _uses_sel_sub(query):417        bd["style"] = 0.12418        msgs.append("~ Partial: SELECT subquery removed")419    else:420        msgs.append("✗ Correlated subqueries in SELECT")421 422    perf = 0.0423    if _uses_join(query):  perf += 0.12; msgs.append("✓ JOIN")424    if _uses_groupby(query): perf += 0.10; msgs.append("✓ GROUP BY")425    m = get_query_metrics(query, conn)426    if m.uses_index: perf += 0.08; msgs.append("✓ Index")427    bd["performance"] = perf428 429    return min(0.999, max(0.001, sum(bd.values()))), bd, " | ".join(msgs)430 431 432def grade_task4(query: str, conn: sqlite3.Connection) -> GraderResult:433    """434    CTE Refactoring grader.435 436    Weights:437      validity     0.10438      correctness  0.35  — exact top-5 username+spend match439      style        0.30  — uses WITH (CTE), no nested subqueries440      performance  0.25  — JOIN + index441    """442    bd   = dict(validity=0.0, correctness=0.0, performance=0.0, style=0.0)443    msgs = []444 445    try:446        cols, rows = _run(query, conn)447        bd["validity"] = 0.10448        msgs.append("✓ Valid SQL")449    except Exception as e:450        msgs.append(f"✗ SQL error: {e}")451        return 0.001, bd, " | ".join(msgs)452 453    ref_rows = conn.execute("""454        WITH active_users AS (455            SELECT user_id, username, email FROM users WHERE is_active = 1456        ),457        user_spend AS (458            SELECT o.user_id,459                   SUM(o.total_amount) AS total_spend,460                   COUNT(*) AS order_count461            FROM orders o462            JOIN active_users au ON au.user_id = o.user_id463            GROUP BY o.user_id464        )465        SELECT au.username, au.email,466               us.total_spend, us.order_count467        FROM user_spend us468        JOIN active_users au ON au.user_id = us.user_id469        ORDER BY us.total_spend DESC470        LIMIT 5471    """).fetchall()472    ref_set = {(r[0], round(float(r[2]), 1)) for r in ref_rows}473 474    required = ["username", "email", "total_spend", "order_count"]475    missing  = [c for c in required if c not in cols]476    if missing:477        bd["correctness"] = 0.05478        msgs.append(f"✗ Missing columns: {missing}")479    elif len(rows) != 5:480        bd["correctness"] = 0.10481        msgs.append(f"✗ Expected 5 rows, got {len(rows)}")482    else:483        got_set = {(row[cols.index("username")], round(float(row[cols.index("total_spend")]), 1)) for row in rows}484        overlap = len(got_set & ref_set) / max(len(ref_set), 1)485        if got_set == ref_set:486            bd["correctness"] = 0.35487            msgs.append("✓ Exact top-5 match")488        elif overlap >= 0.80:489            bd["correctness"] = 0.18490            msgs.append(f"~ {overlap:.0%} of top-5 correct")491        else:492            msgs.append(f"✗ Wrong top-5 ({overlap:.0%})")493 494    uses_cte    = _uses_cte(query)495    has_nest_sub = _uses_sel_sub(query) or (query.upper().count("SELECT") > 2 and _uses_in_sub(query))496 497    if uses_cte and not has_nest_sub:498        bd["style"] = 0.30499        msgs.append("✓ CTE used, no nested subqueries")500    elif uses_cte:501        bd["style"] = 0.15502        msgs.append("~ CTE used but nested subqueries remain")503    else:504        msgs.append("✗ No CTE (WITH clause) found")505 506    perf = 0.0507    if _uses_join(query):    perf += 0.12; msgs.append("✓ JOIN")508    if _uses_groupby(query): perf += 0.08; msgs.append("✓ GROUP BY")509    m = get_query_metrics(query, conn)510    if m.uses_index:         perf += 0.05; msgs.append("✓ Index")511    bd["performance"] = perf512 513    return min(0.999, max(0.001, sum(bd.values()))), bd, " | ".join(msgs)514 515 516# ── Grader registry ───────────────────────────────────────────────────────────517 518TASK_GRADERS = {519    "select_star_removal":      grade_task1,520    "subquery_to_join":         grade_task2,521    "aggregation_optimization": grade_task3,522    "cte_refactoring":          grade_task4,523}524 525def _safe(fn):526    def w(q,conn):527        s,b,f=fn(q,conn)528        s=max(0.001,min(0.999,float(s)))529        b={k:max(0.001,min(0.999,float(v))) for k,v in b.items()}530        return s,b,f531    return w532TASK_GRADERS={k:_safe(v) for k,v in TASK_GRADERS.items()}533