Krishna Prakash commited on
Commit
e7cf806
·
1 Parent(s): 55cf619

Initial commit For SQL Practice Platform

Browse files
Dockerfile ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+ COPY requirements.txt .
5
+ RUN pip install -r requirements.txt
6
+ COPY app/ .
7
+
8
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
app/__init__.py ADDED
File without changes
app/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (137 Bytes). View file
 
app/__pycache__/database.cpython-313.pyc ADDED
Binary file (891 Bytes). View file
 
app/__pycache__/main.cpython-313.pyc ADDED
Binary file (19 kB). View file
 
app/__pycache__/schemas.cpython-313.pyc ADDED
Binary file (694 Bytes). View file
 
app/database.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+
3
+ def create_session_db():
4
+ conn = sqlite3.connect(":memory:", check_same_thread=False)
5
+ conn.row_factory = sqlite3.Row
6
+ return conn
7
+
8
+ def get_session_db(session_id: str):
9
+ from main import sessions
10
+ return sessions.get(session_id, {}).get("conn")
11
+
12
+ def close_session_db(conn):
13
+ conn.close()
app/main.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, Header, Request
2
+ from fastapi.responses import JSONResponse, HTMLResponse
3
+ from fastapi.staticfiles import StaticFiles
4
+ from pydantic import BaseModel
5
+ import sqlite3
6
+ import sqlparse
7
+ import os
8
+ import uuid
9
+ import json # Added for json.load()
10
+ from typing import Dict, Any
11
+ from app.database import create_session_db, close_session_db
12
+ from app.schemas import RunQueryRequest, ValidateQueryRequest
13
+
14
+ app = FastAPI()
15
+ app.mount("/static", StaticFiles(directory=os.path.join(os.path.dirname(__file__), "static")), name="static")
16
+
17
+ @app.exception_handler(Exception)
18
+ async def custom_exception_handler(request: Request, exc: Exception):
19
+ return JSONResponse(status_code=500, content={"detail": str(exc)})
20
+
21
+ sessions: Dict[str, dict] = {}
22
+
23
+ BASE_DIR = os.path.dirname(__file__)
24
+
25
+ def load_questions(domain: str):
26
+ file_path = os.path.join(BASE_DIR, "questions", f"{domain}.json")
27
+ if not os.path.exists(file_path):
28
+ raise FileNotFoundError(f"Question file not found: {file_path}")
29
+ with open(file_path, "r") as f:
30
+ return json.load(f) # Replaced eval with json.load()
31
+
32
+ def load_schema_sql(domain: str):
33
+ file_path = os.path.join(BASE_DIR, "schemas", f"{domain}.sql")
34
+ if not os.path.exists(file_path):
35
+ raise FileNotFoundError(f"Schema file not found: {file_path}")
36
+ with open(file_path, "r") as f:
37
+ return f.read()
38
+
39
+ def is_safe_query(sql: str) -> bool:
40
+ parsed = sqlparse.parse(sql.lower())[0]
41
+ return str(parsed).lower().strip().startswith("select") and all(kw not in str(parsed).lower() for kw in ["drop", "attach", "detach", "pragma", "insert", "update", "delete"])
42
+
43
+ def extract_tables(sql: str) -> list:
44
+ tables = set()
45
+ tokens = sql.replace("\n", " ").lower().split()
46
+ in_subquery = in_openquery = in_values = False
47
+
48
+ for i, token in enumerate(tokens):
49
+ if token == "(" and not in_subquery and not in_values:
50
+ in_values = i > 0 and tokens[i - 1] == "values"
51
+ in_subquery = not in_values
52
+ if token == ")" and (in_subquery or in_values):
53
+ if in_values and i + 1 < len(tokens) and tokens[i + 1] == "as": in_values = False
54
+ elif in_subquery: in_subquery = False
55
+ if token == "openquery" and i + 1 < len(tokens) and tokens[i + 1] == "(": in_openquery = True
56
+ if token == ")" and in_openquery: in_openquery = False
57
+ if in_openquery: continue
58
+
59
+ if token in ["from", "join", "update", "delete", "insert", "into", "using", "apply", "pivot", "table"]:
60
+ next_token = tokens[i + 1].replace(",);", "") if i + 1 < len(tokens) else ""
61
+ if next_token and next_token not in ["select", "where", "on", "order", "group", "having", "as", "("]:
62
+ if i + 2 < len(tokens) and tokens[i + 2] == "as": next_token = next_token
63
+ elif next_token not in ["left", "right", "inner", "outer", "cross", "full"]: tables.add(next_token)
64
+ i += 1
65
+ elif token == "merge" and i + 1 < len(tokens) and tokens[i + 1] == "into":
66
+ next_token = tokens[i + 2].replace(",);", "") if i + 2 < len(tokens) else ""
67
+ if next_token and next_token not in ["using", "select", "where"]: tables.add(next_token)
68
+ i += 2
69
+ while i + 1 < len(tokens) and tokens[i + 1] != "using": i += 1
70
+ if i + 2 < len(tokens) and (next_token := tokens[i + 2].replace(",);", "")) and next_token not in ["select", "where"]: tables.add(next_token)
71
+ elif token == "select" and i + 1 < len(tokens) and tokens[i + 1] == "into":
72
+ next_token = tokens[i + 2].replace(",);", "") if i + 2 < len(tokens) else ""
73
+ if next_token and next_token not in ["from", "select"]: tables.add(next_token)
74
+ i += 2
75
+ while i + 1 < len(tokens) and tokens[i + 1] != "from": i += 1
76
+ if i + 2 < len(tokens) and (next_token := tokens[i + 2].replace(",);", "")) and next_token not in ["where", "join"]: tables.add(next_token)
77
+ elif token == "with":
78
+ while i + 1 < len(tokens) and tokens[i + 1] != "as": i += 1
79
+ if i + 2 < len(tokens) and tokens[i + 2] == "(":
80
+ bracket_count = 1
81
+ subquery_start = i + 2
82
+ i += 2
83
+ while i < len(tokens) and bracket_count > 0:
84
+ if tokens[i] == "(": bracket_count += 1
85
+ elif tokens[i] == ")": bracket_count -= 1
86
+ i += 1
87
+ if bracket_count == 0 and i > subquery_start:
88
+ subquery = " ".join(tokens[subquery_start:i - 1])
89
+ tables.update(t for t in extract_tables(subquery) if t not in tables)
90
+ elif token == "values" and i + 1 < len(tokens) and tokens[i + 1] == "(":
91
+ while i + 1 < len(tokens) and tokens[i + 1] != "as": i += 1
92
+ if i + 2 < len(tokens) and (alias := tokens[i + 2].replace(",);", "")): tables.add(alias)
93
+ elif token in ["exists", "in"]:
94
+ subquery_start = i + 1
95
+ while i + 1 < len(tokens) and tokens[i + 1] != ")": i += 1
96
+ if i > subquery_start:
97
+ subquery = " ".join(tokens[subquery_start:i + 1])
98
+ tables.update(t for t in extract_tables(subquery) if t not in tables)
99
+ return list(tables)
100
+
101
+ @app.post("/api/session")
102
+ async def create_session():
103
+ session_id = str(uuid.uuid4())
104
+ sessions[session_id] = {"conn": create_session_db(), "domain": None}
105
+ return {"session_id": session_id}
106
+
107
+ @app.get("/api/databases")
108
+ async def get_databases():
109
+ questions_dir = os.path.join(BASE_DIR, "questions")
110
+ return {"databases": [f.replace(".json", "") for f in os.listdir(questions_dir) if f.endswith(".json")] if os.path.exists(questions_dir) else []}
111
+
112
+ @app.post("/api/load-schema/{domain}")
113
+ async def load_schema(domain: str, session_id: str = Header(...)):
114
+ if session_id not in sessions: raise HTTPException(status_code=401, detail="Invalid session")
115
+ sessions[session_id] = {"conn": create_session_db(), "domain": domain}
116
+ try:
117
+ sessions[session_id]["conn"].executescript(load_schema_sql(domain))
118
+ sessions[session_id]["conn"].commit()
119
+ except sqlite3.Error as e:
120
+ close_session_db(sessions[session_id]["conn"]) # Cleanup on error
121
+ del sessions[session_id]
122
+ raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
123
+ return {"message": f"Database {domain} loaded"}
124
+
125
+ @app.get("/api/schema/{domain}")
126
+ async def get_schema(domain: str, session_id: str = Header(...)):
127
+ if session_id not in sessions or sessions[session_id]["domain"] != domain: raise HTTPException(status_code=401, detail="Invalid session or domain not loaded")
128
+ conn = sessions[session_id]["conn"]
129
+ cursor = conn.cursor()
130
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
131
+ return {"schema": {table: [{"name": row["name"], "type": row["type"]} for row in conn.execute(f"PRAGMA table_info({table});")] for table in [row["name"] for row in cursor.fetchall()]}}
132
+
133
+ @app.get("/api/sample-data/{domain}")
134
+ async def get_sample_data(domain: str, session_id: str = Header(...)):
135
+ if session_id not in sessions or sessions[session_id]["domain"] != domain: raise HTTPException(status_code=401, detail="Invalid session or domain not loaded")
136
+ conn = sessions[session_id]["conn"]
137
+ cursor = conn.cursor()
138
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
139
+ return {"sample_data": {table: {"columns": [desc[0] for desc in conn.execute(f"SELECT * FROM {table} LIMIT 5").description], "rows": [dict(row) for row in conn.execute(f"SELECT * FROM {table} LIMIT 5")]} for table in [row["name"] for row in cursor.fetchall()]}}
140
+
141
+ @app.post("/api/run-query")
142
+ async def run_query(request: RunQueryRequest, session_id: str = Header(...)):
143
+ if session_id not in sessions or not sessions[session_id]["domain"]: raise HTTPException(status_code=401, detail="Invalid session or no database loaded")
144
+ if not is_safe_query(request.query): raise HTTPException(status_code=400, detail="Only SELECT queries are allowed")
145
+ conn = sessions[session_id]["conn"]
146
+ cursor = conn.cursor()
147
+ cursor.execute(request.query)
148
+ if cursor.description:
149
+ columns = [desc[0] for desc in cursor.description]
150
+ return {"columns": columns, "rows": [dict(zip(columns, row)) for row in cursor.fetchall()]}
151
+ return {"message": "Query executed successfully (no results)"}
152
+
153
+ @app.get("/api/questions/{domain}")
154
+ async def get_questions(domain: str, difficulty: str = None):
155
+ questions = load_questions(domain)
156
+ if difficulty: questions = [q for q in questions if q["difficulty"].lower() == difficulty.lower()]
157
+ return [{"id": q["id"], "title": q["title"], "difficulty": q["difficulty"], "description": q["description"], "hint": q["hint"], "expected_sql": q["expected_sql"]} for q in questions]
158
+
159
+ @app.post("/api/validate")
160
+ async def validate_query(request: ValidateQueryRequest, session_id: str = Header(...)):
161
+ if session_id not in sessions or not sessions[session_id]["domain"]: raise HTTPException(status_code=401, detail="Invalid session or no database loaded")
162
+ conn = sessions[session_id]["conn"]
163
+ cursor = conn.cursor()
164
+ cursor.execute(request.user_query)
165
+ user_result = [tuple(str(x).lower() for x in row) for row in cursor.fetchall()] if cursor.description else []
166
+ cursor.execute(request.expected_query)
167
+ expected_result = [tuple(str(x).lower() for x in row) for row in cursor.fetchall()] if cursor.description else []
168
+ return {"valid": user_result == expected_result, "error": "Results do not match." if user_result != expected_result else ""}
169
+
170
+ @app.on_event("shutdown")
171
+ async def cleanup():
172
+ for session_id in list(sessions): close_session_db(sessions[session_id]["conn"]); del sessions[session_id]
173
+
174
+ @app.get("/", response_class=HTMLResponse)
175
+ async def serve_frontend():
176
+ file_path = os.path.join(BASE_DIR, "static", "index.html")
177
+ if not os.path.exists(file_path): raise HTTPException(status_code=500, detail=f"Frontend file not found: {file_path}")
178
+ with open(file_path, "r") as f: return f.read()
app/questions/banking.json ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "q1",
4
+ "title": "List all customers",
5
+ "difficulty": "Beginner",
6
+ "description": "Retrieve the names and emails of all customers.",
7
+ "hint": "Use SELECT on the customers table.",
8
+ "expected_sql": "SELECT name, email FROM customers;"
9
+ },
10
+ {
11
+ "id": "q2",
12
+ "title": "Show savings accounts",
13
+ "difficulty": "Beginner",
14
+ "description": "List all accounts of type 'Savings' with their balances.",
15
+ "hint": "Use WHERE clause to filter by account_type.",
16
+ "expected_sql": "SELECT account_id, balance FROM accounts WHERE account_type = 'Savings';"
17
+ },
18
+ {
19
+ "id": "q3",
20
+ "title": "Find customers in New York",
21
+ "difficulty": "Beginner",
22
+ "description": "Show the names and ages of customers living in New York.",
23
+ "hint": "Use WHERE clause to filter by city.",
24
+ "expected_sql": "SELECT name, age FROM customers WHERE city = 'New York';"
25
+ },
26
+ {
27
+ "id": "q4",
28
+ "title": "Count total accounts",
29
+ "difficulty": "Beginner",
30
+ "description": "Count the total number of accounts in the system.",
31
+ "hint": "Use COUNT() function on the accounts table.",
32
+ "expected_sql": "SELECT COUNT(*) AS total_accounts FROM accounts;"
33
+ },
34
+ {
35
+ "id": "q5",
36
+ "title": "List accounts opened in 2023",
37
+ "difficulty": "Beginner",
38
+ "description": "Retrieve all accounts opened in 2023.",
39
+ "hint": "Use WHERE clause with LIKE on opened_on.",
40
+ "expected_sql": "SELECT account_id, account_type, opened_on FROM accounts WHERE opened_on LIKE '2023%';"
41
+ },
42
+ {
43
+ "id": "q6",
44
+ "title": "List distinct account types",
45
+ "difficulty": "Beginner",
46
+ "description": "Get all unique account types from the accounts table.",
47
+ "hint": "Use DISTINCT to avoid duplicate account types.",
48
+ "expected_sql": "SELECT DISTINCT account_type FROM accounts;"
49
+ },
50
+ {
51
+ "id": "q7",
52
+ "title": "Find accounts for John Doe",
53
+ "difficulty": "Beginner",
54
+ "description": "List all accounts owned by John Doe.",
55
+ "hint": "Join accounts with customers and filter by name.",
56
+ "expected_sql": "SELECT a.account_id, a.account_type, a.balance FROM accounts a JOIN customers c ON a.customer_id = c.customer_id WHERE c.name = 'John Doe';"
57
+ },
58
+ {
59
+ "id": "q8",
60
+ "title": "Show customers under 30",
61
+ "difficulty": "Beginner",
62
+ "description": "Retrieve the names and cities of customers younger than 30 years old.",
63
+ "hint": "Use WHERE clause to filter by age.",
64
+ "expected_sql": "SELECT name, city FROM customers WHERE age < 30;"
65
+ },
66
+ {
67
+ "id": "q9",
68
+ "title": "List loan accounts",
69
+ "difficulty": "Beginner",
70
+ "description": "Show all accounts of type 'Loan' with their balances.",
71
+ "hint": "Use WHERE clause to filter by account_type.",
72
+ "expected_sql": "SELECT account_id, balance FROM accounts WHERE account_type = 'Loan';"
73
+ },
74
+ {
75
+ "id": "q10",
76
+ "title": "Find customer emails",
77
+ "difficulty": "Beginner",
78
+ "description": "Retrieve the names and email addresses of all customers.",
79
+ "hint": "Select name and email from customers table.",
80
+ "expected_sql": "SELECT name, email FROM customers;"
81
+ },
82
+ {
83
+ "id": "q11",
84
+ "title": "Count accounts per customer",
85
+ "difficulty": "Intermediate",
86
+ "description": "Show the number of accounts each customer owns.",
87
+ "hint": "Join customers and accounts, then GROUP BY customer name.",
88
+ "expected_sql": "SELECT c.name, COUNT(a.account_id) AS account_count FROM customers c LEFT JOIN accounts a ON c.customer_id = a.customer_id GROUP BY c.name;"
89
+ },
90
+ {
91
+ "id": "q12",
92
+ "title": "Total balance per customer",
93
+ "difficulty": "Intermediate",
94
+ "description": "Calculate the total balance across all accounts for each customer.",
95
+ "hint": "Join customers and accounts, then GROUP BY customer name and SUM balance.",
96
+ "expected_sql": "SELECT c.name, SUM(a.balance) AS total_balance FROM customers c JOIN accounts a ON c.customer_id = a.customer_id GROUP BY c.name;"
97
+ },
98
+ {
99
+ "id": "q13",
100
+ "title": "Average balance by account type",
101
+ "difficulty": "Intermediate",
102
+ "description": "Find the average balance for each account type.",
103
+ "hint": "GROUP BY account_type and use AVG on balance.",
104
+ "expected_sql": "SELECT account_type, AVG(balance) AS avg_balance FROM accounts GROUP BY account_type;"
105
+ },
106
+ {
107
+ "id": "q14",
108
+ "title": "Customers with multiple accounts",
109
+ "difficulty": "Intermediate",
110
+ "description": "List customers who have more than one account.",
111
+ "hint": "Join customers and accounts, GROUP BY customer, and use HAVING clause.",
112
+ "expected_sql": "SELECT c.name, COUNT(a.account_id) AS account_count FROM customers c JOIN accounts a ON c.customer_id = a.customer_id GROUP BY c.name HAVING COUNT(a.account_id) > 1;"
113
+ },
114
+ {
115
+ "id": "q15",
116
+ "title": "Accounts with negative balance",
117
+ "difficulty": "Intermediate",
118
+ "description": "Show all accounts with a negative balance, including customer names.",
119
+ "hint": "Join accounts with customers and filter by balance < 0.",
120
+ "expected_sql": "SELECT a.account_id, c.name, a.account_type, a.balance FROM accounts a JOIN customers c ON a.customer_id = c.customer_id WHERE a.balance < 0;"
121
+ },
122
+ {
123
+ "id": "q16",
124
+ "title": "Customers with no accounts",
125
+ "difficulty": "Intermediate",
126
+ "description": "List customers who do not have any accounts.",
127
+ "hint": "Use LEFT JOIN and check for NULL in accounts table.",
128
+ "expected_sql": "SELECT c.name FROM customers c LEFT JOIN accounts a ON c.customer_id = a.customer_id WHERE a.account_id IS NULL;"
129
+ },
130
+ {
131
+ "id": "q17",
132
+ "title": "Account details with customer info",
133
+ "difficulty": "Intermediate",
134
+ "description": "Show account details including customer name and city.",
135
+ "hint": "Join accounts and customers tables.",
136
+ "expected_sql": "SELECT a.account_id, a.account_type, a.balance, c.name, c.city FROM accounts a JOIN customers c ON a.customer_id = c.customer_id;"
137
+ },
138
+ {
139
+ "id": "q18",
140
+ "title": "Count accounts by city",
141
+ "difficulty": "Intermediate",
142
+ "description": "Count the number of accounts for customers in each city.",
143
+ "hint": "Join customers and accounts, GROUP BY city.",
144
+ "expected_sql": "SELECT c.city, COUNT(a.account_id) AS account_count FROM customers c JOIN accounts a ON c.customer_id = a.customer_id GROUP BY c.city;"
145
+ },
146
+ {
147
+ "id": "q19",
148
+ "title": "Accounts opened before 2023",
149
+ "difficulty": "Intermediate",
150
+ "description": "List all accounts opened before 2023, including customer names.",
151
+ "hint": "Join accounts with customers and filter by opened_on.",
152
+ "expected_sql": "SELECT a.account_id, a.account_type, c.name FROM accounts a JOIN customers c ON a.customer_id = c.customer_id WHERE a.opened_on < '2023-01-01';"
153
+ },
154
+ {
155
+ "id": "q20",
156
+ "title": "Total balance by account type",
157
+ "difficulty": "Intermediate",
158
+ "description": "Calculate the total balance for each account type.",
159
+ "hint": "GROUP BY account_type and SUM balance.",
160
+ "expected_sql": "SELECT account_type, SUM(balance) AS total_balance FROM accounts GROUP BY account_type;"
161
+ },
162
+ {
163
+ "id": "q21",
164
+ "title": "Customers with highest total balance",
165
+ "difficulty": "Advanced",
166
+ "description": "Find the customer with the highest total balance across all their accounts.",
167
+ "hint": "Join tables, SUM balance, GROUP BY customer, and use LIMIT.",
168
+ "expected_sql": "SELECT c.name, SUM(a.balance) AS total_balance FROM customers c JOIN accounts a ON c.customer_id = a.customer_id GROUP BY c.name ORDER BY total_balance DESC LIMIT 1;"
169
+ },
170
+ {
171
+ "id": "q22",
172
+ "title": "Youngest customer with loan account",
173
+ "difficulty": "Advanced",
174
+ "description": "Identify the youngest customer who has a Loan account.",
175
+ "hint": "Join tables, filter by account_type, and use MIN on age.",
176
+ "expected_sql": "SELECT c.name, c.age FROM customers c JOIN accounts a ON c.customer_id = a.customer_id WHERE a.account_type = 'Loan' ORDER BY c.age ASC LIMIT 1;"
177
+ },
178
+ {
179
+ "id": "q23",
180
+ "title": "Account age in years",
181
+ "difficulty": "Advanced",
182
+ "description": "Calculate the age of each account in years as of '2025-07-11'.",
183
+ "hint": "Use JULIANDAY to calculate date difference and divide by 365.25.",
184
+ "expected_sql": "SELECT account_id, account_type, ROUND((JULIANDAY('2025-07-11') - JULIANDAY(opened_on)) / 365.25, 1) AS account_age FROM accounts;"
185
+ },
186
+ {
187
+ "id": "q24",
188
+ "title": "Customers with diverse account types",
189
+ "difficulty": "Advanced",
190
+ "description": "List customers who have more than one type of account.",
191
+ "hint": "Join tables, count distinct account types, and use HAVING.",
192
+ "expected_sql": "SELECT c.name, COUNT(DISTINCT a.account_type) AS type_count FROM customers c JOIN accounts a ON c.customer_id = a.customer_id GROUP BY c.name HAVING type_count > 1;"
193
+ },
194
+ {
195
+ "id": "q25",
196
+ "title": "Account openings by year",
197
+ "difficulty": "Advanced",
198
+ "description": "Show the number of accounts opened each year.",
199
+ "hint": "Use STRFTIME to extract year and GROUP BY.",
200
+ "expected_sql": "SELECT STRFTIME('%Y', opened_on) AS year, COUNT(account_id) AS account_count FROM accounts GROUP BY year;"
201
+ },
202
+ {
203
+ "id": "q26",
204
+ "title": "Customers with high negative balance",
205
+ "difficulty": "Advanced",
206
+ "description": "Find customers with a total balance less than -5000 across all accounts.",
207
+ "hint": "Join tables, SUM balance, and use HAVING clause.",
208
+ "expected_sql": "SELECT c.name, SUM(a.balance) AS total_balance FROM customers c JOIN accounts a ON c.customer_id = a.customer_id GROUP BY c.name HAVING total_balance < -5000;"
209
+ },
210
+ {
211
+ "id": "q27",
212
+ "title": "Oldest account per customer",
213
+ "difficulty": "Advanced",
214
+ "description": "Show the earliest opened account for each customer.",
215
+ "hint": "Join tables, use MIN on opened_on, and GROUP BY customer.",
216
+ "expected_sql": "SELECT c.name, MIN(a.opened_on) AS earliest_opened FROM customers c JOIN accounts a ON c.customer_id = a.customer_id GROUP BY c.name;"
217
+ },
218
+ {
219
+ "id": "q28",
220
+ "title": "Cities with high average balance",
221
+ "difficulty": "Advanced",
222
+ "description": "List cities where the average account balance is greater than 1000.",
223
+ "hint": "Join tables, GROUP BY city, and use HAVING clause.",
224
+ "expected_sql": "SELECT c.city, AVG(a.balance) AS avg_balance FROM customers c JOIN accounts a ON c.customer_id = a.customer_id GROUP BY c.city HAVING avg_balance > 1000;"
225
+ },
226
+ {
227
+ "id": "q29",
228
+ "title": "Customers with no savings accounts",
229
+ "difficulty": "Advanced",
230
+ "description": "List customers who do not have a Savings account.",
231
+ "hint": "Use NOT IN or LEFT JOIN to exclude customers with Savings accounts.",
232
+ "expected_sql": "SELECT c.name FROM customers c WHERE c.customer_id NOT IN (SELECT a.customer_id FROM accounts a WHERE a.account_type = 'Savings');"
233
+ },
234
+ {
235
+ "id": "q30",
236
+ "title": "Most common account type per city",
237
+ "difficulty": "Advanced",
238
+ "description": "Find the most common account type in each city.",
239
+ "hint": "Join tables, group by city and account_type, use subquery or LIMIT.",
240
+ "expected_sql": "SELECT c.city, a.account_type, COUNT(a.account_id) AS account_count FROM customers c JOIN accounts a ON c.customer_id = a.customer_id GROUP BY c.city, a.account_type HAVING COUNT(a.account_id) = (SELECT MAX(account_count) FROM (SELECT COUNT(account_id) AS account_count FROM accounts a2 JOIN customers c2 ON a2.customer_id = c2.customer_id WHERE c2.city = c.city GROUP BY a2.account_type) AS counts);"
241
+ }
242
+ ]
app/questions/college.json ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "q1",
4
+ "title": "List all students",
5
+ "difficulty": "Beginner",
6
+ "description": "Retrieve the names and emails of all students.",
7
+ "hint": "Use SELECT on the students table.",
8
+ "expected_sql": "SELECT name, email FROM students;"
9
+ },
10
+ {
11
+ "id": "q2",
12
+ "title": "List all courses",
13
+ "difficulty": "Beginner",
14
+ "description": "Show all courses with their names and credits.",
15
+ "hint": "Use SELECT on the courses table.",
16
+ "expected_sql": "SELECT name, credits FROM courses;"
17
+ },
18
+ {
19
+ "id": "q3",
20
+ "title": "Find Spring 2023 enrollments",
21
+ "difficulty": "Beginner",
22
+ "description": "Display student IDs and course IDs for enrollments in Spring 2023.",
23
+ "hint": "Use WHERE clause to filter by semester.",
24
+ "expected_sql": "SELECT student_id, course_id FROM enrollments WHERE semester = 'Spring 2023';"
25
+ },
26
+ {
27
+ "id": "q4",
28
+ "title": "Count total students",
29
+ "difficulty": "Beginner",
30
+ "description": "Count the total number of students in the system.",
31
+ "hint": "Use COUNT() on the students table.",
32
+ "expected_sql": "SELECT COUNT(*) AS total_students FROM students;"
33
+ },
34
+ {
35
+ "id": "q5",
36
+ "title": "List students in CSE department",
37
+ "difficulty": "Beginner",
38
+ "description": "Show names and emails of students in the CSE department.",
39
+ "hint": "Use WHERE clause to filter by department.",
40
+ "expected_sql": "SELECT name, email FROM students WHERE department = 'CSE';"
41
+ },
42
+ {
43
+ "id": "q6",
44
+ "title": "List distinct departments",
45
+ "difficulty": "Beginner",
46
+ "description": "Retrieve all unique departments from the students table.",
47
+ "hint": "Use DISTINCT to avoid duplicate departments.",
48
+ "expected_sql": "SELECT DISTINCT department FROM students;"
49
+ },
50
+ {
51
+ "id": "q7",
52
+ "title": "List courses in CSE department",
53
+ "difficulty": "Beginner",
54
+ "description": "Show all course names and instructors in the CSE department.",
55
+ "hint": "Use WHERE clause to filter by department.",
56
+ "expected_sql": "SELECT name, instructor FROM courses WHERE department = 'CSE';"
57
+ },
58
+ {
59
+ "id": "q8",
60
+ "title": "List second-year students",
61
+ "difficulty": "Beginner",
62
+ "description": "Retrieve names and emails of students in year 2.",
63
+ "hint": "Use WHERE clause to filter by year.",
64
+ "expected_sql": "SELECT name, email FROM students WHERE year = 2;"
65
+ },
66
+ {
67
+ "id": "q9",
68
+ "title": "Find instructor for Data Structures",
69
+ "difficulty": "Beginner",
70
+ "description": "Show the instructor for the 'Data Structures' course.",
71
+ "hint": "Use WHERE clause to filter by course name.",
72
+ "expected_sql": "SELECT instructor FROM courses WHERE name = 'Data Structures';"
73
+ },
74
+ {
75
+ "id": "q10",
76
+ "title": "List student IDs and names",
77
+ "difficulty": "Beginner",
78
+ "description": "Show all student IDs and their corresponding names.",
79
+ "hint": "Use SELECT on the students table.",
80
+ "expected_sql": "SELECT student_id, name FROM students;"
81
+ },
82
+ {
83
+ "id": "q11",
84
+ "title": "Students and their enrolled courses",
85
+ "difficulty": "Intermediate",
86
+ "description": "Display student names and the names of courses they are enrolled in.",
87
+ "hint": "Join students, enrollments, and courses tables.",
88
+ "expected_sql": "SELECT s.name AS student_name, c.name AS course_name FROM enrollments e JOIN students s ON e.student_id = s.student_id JOIN courses c ON e.course_id = c.course_id;"
89
+ },
90
+ {
91
+ "id": "q12",
92
+ "title": "Count students per course",
93
+ "difficulty": "Intermediate",
94
+ "description": "Show the number of students enrolled in each course.",
95
+ "hint": "Join enrollments with courses and GROUP BY course name.",
96
+ "expected_sql": "SELECT c.name, COUNT(e.student_id) AS student_count FROM enrollments e JOIN courses c ON e.course_id = c.course_id GROUP BY c.name;"
97
+ },
98
+ {
99
+ "id": "q13",
100
+ "title": "Students with A grades",
101
+ "difficulty": "Intermediate",
102
+ "description": "List names of students who received an 'A' grade in any course.",
103
+ "hint": "Join enrollments with students and filter by grade.",
104
+ "expected_sql": "SELECT DISTINCT s.name FROM enrollments e JOIN students s ON e.student_id = s.student_id WHERE e.grade = 'A';"
105
+ },
106
+ {
107
+ "id": "q14",
108
+ "title": "Courses with no enrollments",
109
+ "difficulty": "Intermediate",
110
+ "description": "Find courses that have no students enrolled.",
111
+ "hint": "Use LEFT JOIN and filter for NULL enrollment IDs.",
112
+ "expected_sql": "SELECT c.name FROM courses c LEFT JOIN enrollments e ON c.course_id = e.course_id WHERE e.enrollment_id IS NULL;"
113
+ },
114
+ {
115
+ "id": "q15",
116
+ "title": "Grades for Operating Systems",
117
+ "difficulty": "Intermediate",
118
+ "description": "Show student names and grades for the 'Operating Systems' course.",
119
+ "hint": "Join enrollments, students, and courses, filter by course name.",
120
+ "expected_sql": "SELECT s.name, e.grade FROM enrollments e JOIN students s ON e.student_id = s.student_id JOIN courses c ON e.course_id = c.course_id WHERE c.name = 'Operating Systems';"
121
+ },
122
+ {
123
+ "id": "q16",
124
+ "title": "Courses taught by Dr. Anil Kapoor",
125
+ "difficulty": "Intermediate",
126
+ "description": "List all courses taught by Dr. Anil Kapoor.",
127
+ "hint": "Use WHERE clause to filter by instructor.",
128
+ "expected_sql": "SELECT name FROM courses WHERE instructor = 'Dr. Anil Kapoor';"
129
+ },
130
+ {
131
+ "id": "q17",
132
+ "title": "Students with multiple enrollments",
133
+ "difficulty": "Intermediate",
134
+ "description": "Find students enrolled in more than one course.",
135
+ "hint": "Group by student name and use HAVING clause.",
136
+ "expected_sql": "SELECT s.name, COUNT(e.course_id) AS course_count FROM enrollments e JOIN students s ON e.student_id = s.student_id GROUP BY s.name HAVING COUNT(e.course_id) > 1;"
137
+ },
138
+ {
139
+ "id": "q18",
140
+ "title": "Total credits per student",
141
+ "difficulty": "Intermediate",
142
+ "description": "Calculate the total credits each student is enrolled in.",
143
+ "hint": "Join enrollments with courses and SUM credits.",
144
+ "expected_sql": "SELECT s.name, SUM(c.credits) AS total_credits FROM enrollments e JOIN students s ON e.student_id = s.student_id JOIN courses c ON e.course_id = c.course_id GROUP BY s.name;"
145
+ },
146
+ {
147
+ "id": "q19",
148
+ "title": "Courses per department",
149
+ "difficulty": "Intermediate",
150
+ "description": "Count the number of courses offered by each department.",
151
+ "hint": "Group by department in the courses table.",
152
+ "expected_sql": "SELECT department, COUNT(course_id) AS course_count FROM courses GROUP BY department;"
153
+ },
154
+ {
155
+ "id": "q20",
156
+ "title": "Student enrollment semesters",
157
+ "difficulty": "Intermediate",
158
+ "description": "List student names and the semesters they are enrolled in.",
159
+ "hint": "Join enrollments with students.",
160
+ "expected_sql": "SELECT s.name, e.semester FROM enrollments e JOIN students s ON e.student_id = s.student_id;"
161
+ },
162
+ {
163
+ "id": "q21",
164
+ "title": "Average GPA per course",
165
+ "difficulty": "Advanced",
166
+ "description": "Calculate the average GPA per course, mapping grades to numeric values (A=4.0, A-=3.7, B+=3.3, B=3.0).",
167
+ "hint": "Use CASE to map grades and AVG for calculation.",
168
+ "expected_sql": "SELECT c.name, AVG(CASE e.grade WHEN 'A' THEN 4.0 WHEN 'A-' THEN 3.7 WHEN 'B+' THEN 3.3 WHEN 'B' THEN 3.0 ELSE 0 END) AS avg_gpa FROM enrollments e JOIN courses c ON e.course_id = c.course_id GROUP BY c.name;"
169
+ },
170
+ {
171
+ "id": "q22",
172
+ "title": "Top student per course",
173
+ "difficulty": "Advanced",
174
+ "description": "Find the student with the highest grade in each course.",
175
+ "hint": "Use a subquery to find the maximum grade per course.",
176
+ "expected_sql": "SELECT s.name, c.name AS course_name, e.grade FROM enrollments e JOIN students s ON e.student_id = s.student_id JOIN courses c ON e.course_id = c.course_id WHERE (e.course_id, e.grade) IN (SELECT course_id, MAX(grade) FROM enrollments GROUP BY course_id);"
177
+ },
178
+ {
179
+ "id": "q23",
180
+ "title": "Average credits per department",
181
+ "difficulty": "Advanced",
182
+ "description": "Calculate the average total credits per student in each department.",
183
+ "hint": "Group by student and department, then average credits.",
184
+ "expected_sql": "SELECT s.department, AVG(total_credits) AS avg_credits FROM (SELECT s.student_id, s.department, SUM(c.credits) AS total_credits FROM enrollments e JOIN students s ON e.student_id = s.student_id JOIN courses c ON e.course_id = c.course_id GROUP BY s.student_id, s.department) AS temp GROUP BY department;"
185
+ },
186
+ {
187
+ "id": "q24",
188
+ "title": "Students not enrolled",
189
+ "difficulty": "Advanced",
190
+ "description": "List students who are not enrolled in any course.",
191
+ "hint": "Use LEFT JOIN and filter for NULL enrollments.",
192
+ "expected_sql": "SELECT s.name FROM students s LEFT JOIN enrollments e ON s.student_id = e.student_id WHERE e.enrollment_id IS NULL;"
193
+ },
194
+ {
195
+ "id": "q25",
196
+ "title": "Course with highest average GPA",
197
+ "difficulty": "Advanced",
198
+ "description": "Find the course with the highest average GPA.",
199
+ "hint": "Map grades to GPA, group by course, and use LIMIT.",
200
+ "expected_sql": "SELECT c.name, AVG(CASE e.grade WHEN 'A' THEN 4.0 WHEN 'A-' THEN 3.7 WHEN 'B+' THEN 3.3 WHEN 'B' THEN 3.0 ELSE 0 END) AS avg_gpa FROM enrollments e JOIN courses c ON e.course_id = c.course_id GROUP BY c.name ORDER BY avg_gpa DESC LIMIT 1;"
201
+ },
202
+ {
203
+ "id": "q26",
204
+ "title": "Students enrolled in all CSE courses",
205
+ "difficulty": "Advanced",
206
+ "description": "List students who have enrolled in every CSE department course.",
207
+ "hint": "Compare count of CSE courses with enrolled courses using HAVING.",
208
+ "expected_sql": "SELECT s.name FROM enrollments e JOIN students s ON e.student_id = s.student_id JOIN courses c ON e.course_id = c.course_id WHERE c.department = 'CSE' GROUP BY s.name HAVING COUNT(DISTINCT c.course_id) = (SELECT COUNT(*) FROM courses WHERE department = 'CSE');"
209
+ },
210
+ {
211
+ "id": "q27",
212
+ "title": "Courses with multi-department students",
213
+ "difficulty": "Advanced",
214
+ "description": "Find courses with students from more than one department.",
215
+ "hint": "Group by course and count distinct departments.",
216
+ "expected_sql": "SELECT c.name FROM enrollments e JOIN courses c ON e.course_id = c.course_id JOIN students s ON e.student_id = s.student_id GROUP BY c.name HAVING COUNT(DISTINCT s.department) > 1;"
217
+ },
218
+ {
219
+ "id": "q28",
220
+ "title": "Instructor average GPA",
221
+ "difficulty": "Advanced",
222
+ "description": "Calculate the average GPA for students in courses taught by each instructor.",
223
+ "hint": "Join tables, map grades to GPA, and group by instructor.",
224
+ "expected_sql": "SELECT c.instructor, AVG(CASE e.grade WHEN 'A' THEN 4.0 WHEN 'A-' THEN 3.7 WHEN 'B+' THEN 3.3 WHEN 'B' THEN 3.0 ELSE 0 END) AS avg_gpa FROM enrollments e JOIN courses c ON e.course_id = c.course_id GROUP BY c.instructor;"
225
+ },
226
+ {
227
+ "id": "q29",
228
+ "title": "Students with consistent grades",
229
+ "difficulty": "Advanced",
230
+ "description": "List students who received the same grade in multiple courses.",
231
+ "hint": "Group by student and grade, filter for count > 1.",
232
+ "expected_sql": "SELECT s.name, e.grade FROM enrollments e JOIN students s ON e.student_id = s.student_id GROUP BY s.name, e.grade HAVING COUNT(*) > 1;"
233
+ },
234
+ {
235
+ "id": "q30",
236
+ "title": "Highest credit course per department",
237
+ "difficulty": "Advanced",
238
+ "description": "Find the course with the highest credits in each department.",
239
+ "hint": "Use a window function or subquery to rank courses by credits.",
240
+ "expected_sql": "SELECT department, name, credits FROM (SELECT department, name, credits, RANK() OVER (PARTITION BY department ORDER BY credits DESC) AS rnk FROM courses) AS ranked WHERE rnk = 1;"
241
+ }
242
+ ]
app/questions/e_commerce.json ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "q1",
4
+ "title": "List electronics products",
5
+ "difficulty": "Beginner",
6
+ "description": "Retrieve the names and sale amounts of all products in the Electronics category.",
7
+ "hint": "Use WHERE clause to filter by category.",
8
+ "expected_sql": "SELECT product_name, sale_amount FROM sales WHERE category = 'Electronics';"
9
+ },
10
+ {
11
+ "id": "q2",
12
+ "title": "Find sales after specific date",
13
+ "difficulty": "Beginner",
14
+ "description": "Show all sales that occurred after January 5, 2023.",
15
+ "hint": "Use WHERE clause with date comparison.",
16
+ "expected_sql": "SELECT product_name, sale_date FROM sales WHERE sale_date > '2023-01-05';"
17
+ },
18
+ {
19
+ "id": "q3",
20
+ "title": "List orders by customer",
21
+ "difficulty": "Beginner",
22
+ "description": "Retrieve all orders placed by customer with ID 2001.",
23
+ "hint": "Use WHERE clause to filter by customer_id.",
24
+ "expected_sql": "SELECT order_id, product_id, order_date FROM orders WHERE customer_id = 2001;"
25
+ },
26
+ {
27
+ "id": "q4",
28
+ "title": "Find high-value sales",
29
+ "difficulty": "Beginner",
30
+ "description": "Show products with a sale amount greater than 1000.",
31
+ "hint": "Use WHERE clause to filter by sale_amount.",
32
+ "expected_sql": "SELECT product_name, sale_amount FROM sales WHERE sale_amount > 1000;"
33
+ },
34
+ {
35
+ "id": "q5",
36
+ "title": "List unique categories",
37
+ "difficulty": "Beginner",
38
+ "description": "Retrieve all unique product categories from the sales table.",
39
+ "hint": "Use DISTINCT to avoid duplicate categories.",
40
+ "expected_sql": "SELECT DISTINCT category FROM sales;"
41
+ },
42
+ {
43
+ "id": "q6",
44
+ "title": "Find orders with quantity greater than 1",
45
+ "difficulty": "Beginner",
46
+ "description": "Show order details where the quantity is greater than 1.",
47
+ "hint": "Use WHERE clause to filter by quantity.",
48
+ "expected_sql": "SELECT order_id, product_id, quantity FROM orders WHERE quantity > 1;"
49
+ },
50
+ {
51
+ "id": "q7",
52
+ "title": "List sales by date",
53
+ "difficulty": "Beginner",
54
+ "description": "Show all sales ordered by sale date.",
55
+ "hint": "Use ORDER BY clause on sale_date.",
56
+ "expected_sql": "SELECT product_name, sale_date FROM sales ORDER BY sale_date;"
57
+ },
58
+ {
59
+ "id": "q8",
60
+ "title": "Find accessories sales",
61
+ "difficulty": "Beginner",
62
+ "description": "Retrieve all sales from the Accessories category.",
63
+ "hint": "Use WHERE clause to filter by category.",
64
+ "expected_sql": "SELECT product_name, sale_amount FROM sales WHERE category = 'Accessories';"
65
+ },
66
+ {
67
+ "id": "q9",
68
+ "title": "List order quantities",
69
+ "difficulty": "Beginner",
70
+ "description": "Show the quantity for each order.",
71
+ "hint": "Select quantity from orders table.",
72
+ "expected_sql": "SELECT order_id, quantity FROM orders;"
73
+ },
74
+ {
75
+ "id": "q10",
76
+ "title": "Find sales with low amounts",
77
+ "difficulty": "Beginner",
78
+ "description": "Retrieve products with a sale amount less than 100.",
79
+ "hint": "Use WHERE clause to filter by sale_amount.",
80
+ "expected_sql": "SELECT product_name, sale_amount FROM sales WHERE sale_amount < 100;"
81
+ },
82
+ {
83
+ "id": "q11",
84
+ "title": "Total revenue by category",
85
+ "difficulty": "Intermediate",
86
+ "description": "Calculate the total sale amount for each product category.",
87
+ "hint": "Use GROUP BY on category and SUM on sale_amount.",
88
+ "expected_sql": "SELECT category, SUM(sale_amount) AS total_revenue FROM sales GROUP BY category;"
89
+ },
90
+ {
91
+ "id": "q12",
92
+ "title": "Count orders per product",
93
+ "difficulty": "Intermediate",
94
+ "description": "Show the number of orders for each product.",
95
+ "hint": "Join sales and orders, then GROUP BY product_name.",
96
+ "expected_sql": "SELECT s.product_name, COUNT(o.order_id) AS order_count FROM sales s JOIN orders o ON s.id = o.product_id GROUP BY s.product_name;"
97
+ },
98
+ {
99
+ "id": "q13",
100
+ "title": "Average sale amount per product",
101
+ "difficulty": "Intermediate",
102
+ "description": "Find the average sale amount for each product name.",
103
+ "hint": "Use GROUP BY on product_name and AVG on sale_amount.",
104
+ "expected_sql": "SELECT product_name, AVG(sale_amount) AS avg_sale FROM sales GROUP BY product_name;"
105
+ },
106
+ {
107
+ "id": "q14",
108
+ "title": "Customer order count",
109
+ "difficulty": "Intermediate",
110
+ "description": "Show the number of orders placed by each customer.",
111
+ "hint": "Use GROUP BY on customer_id and COUNT.",
112
+ "expected_sql": "SELECT customer_id, COUNT(order_id) AS order_count FROM orders GROUP BY customer_id;"
113
+ },
114
+ {
115
+ "id": "q15",
116
+ "title": "High quantity orders",
117
+ "difficulty": "Intermediate",
118
+ "description": "List orders with a total quantity greater than 2.",
119
+ "hint": "Use GROUP BY on order_id and HAVING clause.",
120
+ "expected_sql": "SELECT order_id, SUM(quantity) AS total_quantity FROM orders GROUP BY order_id HAVING total_quantity > 2;"
121
+ },
122
+ {
123
+ "id": "q16",
124
+ "title": "Products not ordered",
125
+ "difficulty": "Intermediate",
126
+ "description": "List products that have not been ordered.",
127
+ "hint": "Use LEFT JOIN and check for NULL in orders table.",
128
+ "expected_sql": "SELECT s.product_name FROM sales s LEFT JOIN orders o ON s.id = o.product_id WHERE o.order_id IS NULL;"
129
+ },
130
+ {
131
+ "id": "q17",
132
+ "title": "Order details with products",
133
+ "difficulty": "Intermediate",
134
+ "description": "Show order details including product names and quantities.",
135
+ "hint": "Join sales and orders tables.",
136
+ "expected_sql": "SELECT o.order_id, s.product_name, o.quantity FROM orders o JOIN sales s ON o.product_id = s.id;"
137
+ },
138
+ {
139
+ "id": "q18",
140
+ "title": "Total quantity per customer",
141
+ "difficulty": "Intermediate",
142
+ "description": "Calculate the total quantity of items ordered by each customer.",
143
+ "hint": "Use GROUP BY on customer_id and SUM on quantity.",
144
+ "expected_sql": "SELECT customer_id, SUM(quantity) AS total_quantity FROM orders GROUP BY customer_id;"
145
+ },
146
+ {
147
+ "id": "q19",
148
+ "title": "Orders by date range",
149
+ "difficulty": "Intermediate",
150
+ "description": "List all orders placed between January 1, 2023, and January 5, 2023.",
151
+ "hint": "Use BETWEEN clause for date range.",
152
+ "expected_sql": "SELECT order_id, customer_id, order_date FROM orders WHERE order_date BETWEEN '2023-01-01' AND '2023-01-05';"
153
+ },
154
+ {
155
+ "id": "q20",
156
+ "title": "Most ordered product category",
157
+ "difficulty": "Intermediate",
158
+ "description": "Find the category with the highest number of orders.",
159
+ "hint": "Join sales and orders, group by category, and use LIMIT.",
160
+ "expected_sql": "SELECT s.category, COUNT(o.order_id) AS order_count FROM sales s JOIN orders o ON s.id = o.product_id GROUP BY s.category ORDER BY order_count DESC LIMIT 1;"
161
+ },
162
+ {
163
+ "id": "q21",
164
+ "title": "Total revenue per customer",
165
+ "difficulty": "Advanced",
166
+ "description": "Calculate the total revenue (sale_amount * quantity) generated by each customer.",
167
+ "hint": "Join sales and orders, multiply sale_amount by quantity, and group by customer_id.",
168
+ "expected_sql": "SELECT o.customer_id, SUM(s.sale_amount * o.quantity) AS total_revenue FROM orders o JOIN sales s ON o.product_id = s.id GROUP BY o.customer_id;"
169
+ },
170
+ {
171
+ "id": "q22",
172
+ "title": "Most expensive ordered product",
173
+ "difficulty": "Advanced",
174
+ "description": "Find the product with the highest sale amount that was ordered.",
175
+ "hint": "Join sales and orders, use ORDER BY and LIMIT.",
176
+ "expected_sql": "SELECT s.product_name, s.sale_amount FROM sales s JOIN orders o ON s.id = o.product_id ORDER BY s.sale_amount DESC LIMIT 1;"
177
+ },
178
+ {
179
+ "id": "q23",
180
+ "title": "Daily revenue trend",
181
+ "difficulty": "Advanced",
182
+ "description": "Show the total revenue (sale_amount * quantity) per day for all orders.",
183
+ "hint": "Join tables, group by order_date, and calculate revenue.",
184
+ "expected_sql": "SELECT o.order_date, SUM(s.sale_amount * o.quantity) AS daily_revenue FROM orders o JOIN sales s ON o.product_id = s.id GROUP BY o.order_date;"
185
+ },
186
+ {
187
+ "id": "q24",
188
+ "title": "Customers with high-value orders",
189
+ "difficulty": "Advanced",
190
+ "description": "Identify customers whose total order value exceeds 2000.",
191
+ "hint": "Join tables, calculate total value, and use HAVING clause.",
192
+ "expected_sql": "SELECT o.customer_id, SUM(s.sale_amount * o.quantity) AS total_value FROM orders o JOIN sales s ON o.product_id = s.id GROUP BY o.customer_id HAVING total_value > 2000;"
193
+ },
194
+ {
195
+ "id": "q25",
196
+ "title": "Products ordered by multiple customers",
197
+ "difficulty": "Advanced",
198
+ "description": "Find products that have been ordered by more than one distinct customer.",
199
+ "hint": "Join sales and orders, use COUNT and DISTINCT on customer_id, and group by product.",
200
+ "expected_sql": "SELECT s.product_name, COUNT(DISTINCT o.customer_id) AS customer_count FROM sales s JOIN orders o ON s.id = o.product_id GROUP BY s.product_name HAVING customer_count > 1;"
201
+ },
202
+ {
203
+ "id": "q26",
204
+ "title": "Revenue contribution by category",
205
+ "difficulty": "Advanced",
206
+ "description": "Calculate the total revenue (sale_amount * quantity) for each product category.",
207
+ "hint": "Join tables, multiply sale_amount by quantity, and group by category.",
208
+ "expected_sql": "SELECT s.category, SUM(s.sale_amount * o.quantity) AS total_revenue FROM sales s JOIN orders o ON s.id = o.product_id GROUP BY s.category;"
209
+ },
210
+ {
211
+ "id": "q27",
212
+ "title": "Earliest and latest order per customer",
213
+ "difficulty": "Advanced",
214
+ "description": "Show the earliest and latest order dates for each customer.",
215
+ "hint": "Use MIN and MAX on order_date, grouped by customer_id.",
216
+ "expected_sql": "SELECT customer_id, MIN(order_date) AS earliest_order, MAX(order_date) AS latest_order FROM orders GROUP BY customer_id;"
217
+ },
218
+ {
219
+ "id": "q28",
220
+ "title": "Products with high total quantity",
221
+ "difficulty": "Advanced",
222
+ "description": "Find products with a total ordered quantity greater than 2.",
223
+ "hint": "Join sales and orders, group by product, and use HAVING clause.",
224
+ "expected_sql": "SELECT s.product_name, SUM(o.quantity) AS total_quantity FROM sales s JOIN orders o ON s.id = o.product_id GROUP BY s.product_name HAVING total_quantity > 2;"
225
+ },
226
+ {
227
+ "id": "q29",
228
+ "title": "Customers who ordered all electronics",
229
+ "difficulty": "Advanced",
230
+ "description": "List customers who have ordered every product in the Electronics category.",
231
+ "hint": "Count distinct Electronics products per customer and compare with total Electronics products.",
232
+ "expected_sql": "SELECT o.customer_id FROM orders o JOIN sales s ON o.product_id = s.id WHERE s.category = 'Electronics' GROUP BY o.customer_id HAVING COUNT(DISTINCT s.id) = (SELECT COUNT(*) FROM sales WHERE category = 'Electronics');"
233
+ },
234
+ {
235
+ "id": "q30",
236
+ "title": "Most frequent customer by product",
237
+ "difficulty": "Advanced",
238
+ "description": "Find the customer who ordered each product the most times.",
239
+ "hint": "Join tables, group by product and customer, use subquery to find max orders.",
240
+ "expected_sql": "SELECT s.product_name, o.customer_id, COUNT(o.order_id) AS order_count FROM sales s JOIN orders o ON s.id = o.product_id GROUP BY s.product_name, o.customer_id HAVING COUNT(o.order_id) = (SELECT MAX(order_count) FROM (SELECT COUNT(order_id) AS order_count FROM orders o2 WHERE o2.product_id = s.id GROUP BY o2.customer_id) AS counts);"
241
+ }
242
+ ]
app/questions/hospital.json ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "q1",
4
+ "title": "List female patients",
5
+ "difficulty": "Beginner",
6
+ "description": "Retrieve the names and contact details of all female patients.",
7
+ "hint": "Use a WHERE clause to filter by gender.",
8
+ "expected_sql": "SELECT name, contact FROM patients WHERE gender = 'Female';"
9
+ },
10
+ {
11
+ "id": "q2",
12
+ "title": "Find appointments on specific date",
13
+ "difficulty": "Beginner",
14
+ "description": "Show all appointments scheduled on July 10, 2023.",
15
+ "hint": "Use a WHERE clause with date comparison.",
16
+ "expected_sql": "SELECT appointment_id, patient_id, doctor_id, appointment_date FROM appointments WHERE appointment_date = '2023-07-10';"
17
+ },
18
+ {
19
+ "id": "q3",
20
+ "title": "List cardiologists",
21
+ "difficulty": "Beginner",
22
+ "description": "Retrieve the names and contact details of all doctors with the Cardiologist specialty.",
23
+ "hint": "Use a WHERE clause to filter by specialty.",
24
+ "expected_sql": "SELECT name, contact FROM doctors WHERE specialty = 'Cardiologist';"
25
+ },
26
+ {
27
+ "id": "q4",
28
+ "title": "Patients born before 1995",
29
+ "difficulty": "Beginner",
30
+ "description": "Show the names and dates of birth of patients born before January 1, 1995.",
31
+ "hint": "Use a WHERE clause with date comparison on dob.",
32
+ "expected_sql": "SELECT name, dob FROM patients WHERE dob < '1995-01-01';"
33
+ },
34
+ {
35
+ "id": "q5",
36
+ "title": "List unique diagnoses",
37
+ "difficulty": "Beginner",
38
+ "description": "Retrieve all unique diagnoses from the appointments table.",
39
+ "hint": "Use DISTINCT to avoid duplicate diagnoses.",
40
+ "expected_sql": "SELECT DISTINCT diagnosis FROM appointments;"
41
+ },
42
+ {
43
+ "id": "q6",
44
+ "title": "Appointments for doctor ID 1",
45
+ "difficulty": "Beginner",
46
+ "description": "Show all appointments for the doctor with ID 1.",
47
+ "hint": "Use a WHERE clause to filter by doctor_id.",
48
+ "expected_sql": "SELECT appointment_id, patient_id, appointment_date FROM appointments WHERE doctor_id = 1;"
49
+ },
50
+ {
51
+ "id": "q7",
52
+ "title": "List patient names and genders",
53
+ "difficulty": "Beginner",
54
+ "description": "Show all patient names and their genders.",
55
+ "hint": "Select name and gender from the patients table.",
56
+ "expected_sql": "SELECT name, gender FROM patients;"
57
+ },
58
+ {
59
+ "id": "q8",
60
+ "title": "Appointments in August 2023",
61
+ "difficulty": "Beginner",
62
+ "description": "Retrieve all appointments scheduled in August 2023.",
63
+ "hint": "Use a WHERE clause with LIKE for appointment_date.",
64
+ "expected_sql": "SELECT appointment_id, patient_id, doctor_id, appointment_date FROM appointments WHERE appointment_date LIKE '2023-08%';"
65
+ },
66
+ {
67
+ "id": "q9",
68
+ "title": "List doctors and specialties",
69
+ "difficulty": "Beginner",
70
+ "description": "Show all doctor names and their specialties.",
71
+ "hint": "Select name and specialty from the doctors table.",
72
+ "expected_sql": "SELECT name, specialty FROM doctors;"
73
+ },
74
+ {
75
+ "id": "q10",
76
+ "title": "Patients with Routine Checkup",
77
+ "difficulty": "Beginner",
78
+ "description": "Retrieve the names of patients with a diagnosis of 'Routine Checkup'.",
79
+ "hint": "Join patients and appointments, filter by diagnosis.",
80
+ "expected_sql": "SELECT p.name FROM patients p JOIN appointments a ON p.patient_id = a.patient_id WHERE a.diagnosis = 'Routine Checkup';"
81
+ },
82
+ {
83
+ "id": "q11",
84
+ "title": "Appointments per doctor",
85
+ "difficulty": "Intermediate",
86
+ "description": "Show the number of appointments for each doctor, including those with zero appointments.",
87
+ "hint": "Use a LEFT JOIN and GROUP BY doctor name.",
88
+ "expected_sql": "SELECT d.name, COUNT(a.appointment_id) AS appointment_count FROM doctors d LEFT JOIN appointments a ON d.doctor_id = a.doctor_id GROUP BY d.name;"
89
+ },
90
+ {
91
+ "id": "q12",
92
+ "title": "Patients with multiple appointments",
93
+ "difficulty": "Intermediate",
94
+ "description": "Find patients who have more than one appointment.",
95
+ "hint": "Join patients and appointments, use GROUP BY and HAVING clause.",
96
+ "expected_sql": "SELECT p.name, COUNT(a.appointment_id) AS appointment_count FROM patients p JOIN appointments a ON p.patient_id = a.patient_id GROUP BY p.name HAVING COUNT(a.appointment_id) > 1;"
97
+ },
98
+ {
99
+ "id": "q13",
100
+ "title": "Appointments by doctor specialty",
101
+ "difficulty": "Intermediate",
102
+ "description": "Count the number of appointments for each doctor specialty.",
103
+ "hint": "Join doctors and appointments, then GROUP BY specialty.",
104
+ "expected_sql": "SELECT d.specialty, COUNT(a.appointment_id) AS total_appointments FROM doctors d JOIN appointments a ON d.doctor_id = a.doctor_id GROUP BY d.specialty;"
105
+ },
106
+ {
107
+ "id": "q14",
108
+ "title": "Patient and doctor appointment details",
109
+ "difficulty": "Intermediate",
110
+ "description": "Show patient names, doctor names, and appointment dates for all appointments.",
111
+ "hint": "Join patients, doctors, and appointments tables.",
112
+ "expected_sql": "SELECT p.name AS patient_name, d.name AS doctor_name, a.appointment_date FROM appointments a JOIN patients p ON a.patient_id = p.patient_id JOIN doctors d ON a.doctor_id = d.doctor_id;"
113
+ },
114
+ {
115
+ "id": "q15",
116
+ "title": "Diagnoses by patient gender",
117
+ "difficulty": "Intermediate",
118
+ "description": "Count the number of appointments for each diagnosis, grouped by patient gender.",
119
+ "hint": "Join patients and appointments, GROUP BY gender and diagnosis.",
120
+ "expected_sql": "SELECT p.gender, a.diagnosis, COUNT(a.appointment_id) AS appointment_count FROM patients p JOIN appointments a ON p.patient_id = a.patient_id GROUP BY p.gender, a.diagnosis;"
121
+ },
122
+ {
123
+ "id": "q16",
124
+ "title": "Doctors with no appointments",
125
+ "difficulty": "Intermediate",
126
+ "description": "List doctors who have not had any appointments.",
127
+ "hint": "Use a LEFT JOIN and check for NULL in the appointments table.",
128
+ "expected_sql": "SELECT d.name FROM doctors d LEFT JOIN appointments a ON d.doctor_id = a.doctor_id WHERE a.appointment_id IS NULL;"
129
+ },
130
+ {
131
+ "id": "q17",
132
+ "title": "Patients seen by multiple doctors",
133
+ "difficulty": "Intermediate",
134
+ "description": "Find patients who have appointments with more than one distinct doctor.",
135
+ "hint": "Join patients and appointments, group by patient, and use HAVING clause.",
136
+ "expected_sql": "SELECT p.name, COUNT(DISTINCT a.doctor_id) AS doctor_count FROM patients p JOIN appointments a ON p.patient_id = a.patient_id GROUP BY p.name HAVING COUNT(DISTINCT a.doctor_id) > 1;"
137
+ },
138
+ {
139
+ "id": "q18",
140
+ "title": "Appointments in July 2023",
141
+ "difficulty": "Intermediate",
142
+ "description": "List all appointments between July 1, 2023, and July 31, 2023.",
143
+ "hint": "Use a BETWEEN clause for the date range.",
144
+ "expected_sql": "SELECT appointment_id, patient_id, doctor_id, appointment_date FROM appointments WHERE appointment_date BETWEEN '2023-07-01' AND '2023-07-31';"
145
+ },
146
+ {
147
+ "id": "q19",
148
+ "title": "Most common diagnosis",
149
+ "difficulty": "Intermediate",
150
+ "description": "Find the diagnosis with the highest number of appointments.",
151
+ "hint": "Group by diagnosis, count appointments, and use LIMIT.",
152
+ "expected_sql": "SELECT diagnosis, COUNT(appointment_id) AS diagnosis_count FROM appointments GROUP BY diagnosis ORDER BY diagnosis_count DESC LIMIT 1;"
153
+ },
154
+ {
155
+ "id": "q20",
156
+ "title": "Unique patients by specialty",
157
+ "difficulty": "Intermediate",
158
+ "description": "Show the number of unique patients seen by each doctor specialty.",
159
+ "hint": "Join doctors and appointments, use COUNT and DISTINCT, group by specialty.",
160
+ "expected_sql": "SELECT d.specialty, COUNT(DISTINCT a.patient_id) AS unique_patients FROM doctors d JOIN appointments a ON d.doctor_id = a.doctor_id GROUP BY d.specialty;"
161
+ },
162
+ {
163
+ "id": "q21",
164
+ "title": "Patient age at appointment",
165
+ "difficulty": "Advanced",
166
+ "description": "Calculate the age of each patient at the time of their appointment.",
167
+ "hint": "Join patients and appointments, use JULIANDAY for age calculation.",
168
+ "expected_sql": "SELECT p.name, a.appointment_date, ROUND((JULIANDAY(a.appointment_date) - JULIANDAY(p.dob)) / 365.25, 1) AS age FROM patients p JOIN appointments a ON p.patient_id = a.patient_id;"
169
+ },
170
+ {
171
+ "id": "q22",
172
+ "title": "Most active doctor",
173
+ "difficulty": "Advanced",
174
+ "description": "Find the doctor with the highest number of appointments.",
175
+ "hint": "Join doctors and appointments, group by doctor name, and use LIMIT.",
176
+ "expected_sql": "SELECT d.name, COUNT(a.appointment_id) AS appointment_count FROM doctors d JOIN appointments a ON d.doctor_id = a.doctor_id GROUP BY d.name ORDER BY appointment_count DESC LIMIT 1;"
177
+ },
178
+ {
179
+ "id": "q23",
180
+ "title": "Patients seen by Cardiologist",
181
+ "difficulty": "Advanced",
182
+ "description": "List all patients who have seen a Cardiologist, including their diagnoses.",
183
+ "hint": "Join all tables and filter by doctor specialty.",
184
+ "expected_sql": "SELECT DISTINCT p.name, a.diagnosis FROM patients p JOIN appointments a ON p.patient_id = a.patient_id JOIN doctors d ON a.doctor_id = d.doctor_id WHERE d.specialty = 'Cardiologist';"
185
+ },
186
+ {
187
+ "id": "q24",
188
+ "title": "Diagnosis frequency by doctor",
189
+ "difficulty": "Advanced",
190
+ "description": "Show the number of times each diagnosis was made by each doctor.",
191
+ "hint": "Join doctors and appointments, group by doctor name and diagnosis.",
192
+ "expected_sql": "SELECT d.name, a.diagnosis, COUNT(a.appointment_id) AS diagnosis_count FROM doctors d JOIN appointments a ON d.doctor_id = a.doctor_id GROUP BY d.name, a.diagnosis;"
193
+ },
194
+ {
195
+ "id": "q25",
196
+ "title": "Appointments per month",
197
+ "difficulty": "Advanced",
198
+ "description": "Show the number of appointments per month in 2023.",
199
+ "hint": "Use STRFTIME to extract the month and GROUP BY.",
200
+ "expected_sql": "SELECT STRFTIME('%Y-%m', appointment_date) AS month, COUNT(appointment_id) AS appointment_count FROM appointments GROUP BY month;"
201
+ },
202
+ {
203
+ "id": "q26",
204
+ "title": "Youngest patient per doctor",
205
+ "difficulty": "Advanced",
206
+ "description": "Find the youngest patient seen by each doctor.",
207
+ "hint": "Join tables, calculate age using JULIANDAY, and group by doctor.",
208
+ "expected_sql": "SELECT d.name, p.name AS patient_name, MIN(ROUND((JULIANDAY(a.appointment_date) - JULIANDAY(p.dob)) / 365.25, 1)) AS age FROM doctors d JOIN appointments a ON d.doctor_id = a.doctor_id JOIN patients p ON a.patient_id = p.patient_id GROUP BY d.name;"
209
+ },
210
+ {
211
+ "id": "q27",
212
+ "title": "Patients with multiple specialties",
213
+ "difficulty": "Advanced",
214
+ "description": "List patients who have seen doctors from more than one specialty.",
215
+ "hint": "Join tables, count distinct specialties, and use HAVING clause.",
216
+ "expected_sql": "SELECT p.name, COUNT(DISTINCT d.specialty) AS specialty_count FROM patients p JOIN appointments a ON p.patient_id = a.patient_id JOIN doctors d ON a.doctor_id = d.doctor_id GROUP BY p.name HAVING COUNT(DISTINCT d.specialty) > 1;"
217
+ },
218
+ {
219
+ "id": "q28",
220
+ "title": "Unique diagnoses per doctor",
221
+ "difficulty": "Advanced",
222
+ "description": "Show the number of unique diagnoses made by each doctor.",
223
+ "hint": "Join doctors and appointments, use COUNT and DISTINCT, group by doctor name.",
224
+ "expected_sql": "SELECT d.name, COUNT(DISTINCT a.diagnosis) AS unique_diagnoses FROM doctors d JOIN appointments a ON d.doctor_id = a.doctor_id GROUP BY d.name;"
225
+ },
226
+ {
227
+ "id": "q29",
228
+ "title": "Patients not seen by Neurologist",
229
+ "difficulty": "Advanced",
230
+ "description": "List patients who have never been seen by a Neurologist.",
231
+ "hint": "Use NOT IN or LEFT JOIN to exclude patients with Neurologist appointments.",
232
+ "expected_sql": "SELECT p.name FROM patients p WHERE p.patient_id NOT IN (SELECT a.patient_id FROM appointments a JOIN doctors d ON a.doctor_id = d.doctor_id WHERE d.specialty = 'Neurologist');"
233
+ },
234
+ {
235
+ "id": "q30",
236
+ "title": "Earliest and latest appointment per patient",
237
+ "difficulty": "Advanced",
238
+ "description": "Show the earliest and latest appointment dates for each patient with appointments.",
239
+ "hint": "Join patients and appointments, use MIN and MAX on appointment_date.",
240
+ "expected_sql": "SELECT p.name, MIN(a.appointment_date) AS earliest_appointment, MAX(a.appointment_date) AS latest_appointment FROM patients p JOIN appointments a ON p.patient_id = a.patient_id GROUP BY p.name;"
241
+ }
242
+ ]
app/questions/inventory.json ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "q1",
4
+ "title": "List electronics products",
5
+ "difficulty": "Beginner",
6
+ "description": "Retrieve the names and prices of all products in the Electronics category.",
7
+ "hint": "Use a WHERE clause to filter by category.",
8
+ "expected_sql": "SELECT product_name, price FROM inventory WHERE category = 'Electronics';"
9
+ },
10
+ {
11
+ "id": "q2",
12
+ "title": "Find products with low stock",
13
+ "difficulty": "Beginner",
14
+ "description": "Show products with stock less than 10 units.",
15
+ "hint": "Use a WHERE clause to filter by stock.",
16
+ "expected_sql": "SELECT product_name, stock FROM inventory WHERE stock < 10;"
17
+ },
18
+ {
19
+ "id": "q3",
20
+ "title": "List restocks for product ID 1",
21
+ "difficulty": "Beginner",
22
+ "description": "Retrieve all restock details for the product with ID 1.",
23
+ "hint": "Use a WHERE clause to filter by product_id.",
24
+ "expected_sql": "SELECT restock_id, restock_date, quantity, supplier FROM restocks WHERE product_id = 1;"
25
+ },
26
+ {
27
+ "id": "q4",
28
+ "title": "Find products by price range",
29
+ "difficulty": "Beginner",
30
+ "description": "Show products with prices between 50 and 500, inclusive.",
31
+ "hint": "Use a BETWEEN clause for the price range.",
32
+ "expected_sql": "SELECT product_name, price FROM inventory WHERE price BETWEEN 50 AND 500;"
33
+ },
34
+ {
35
+ "id": "q5",
36
+ "title": "List unique suppliers",
37
+ "difficulty": "Beginner",
38
+ "description": "Retrieve all unique suppliers from the restocks table.",
39
+ "hint": "Use DISTINCT to avoid duplicate suppliers.",
40
+ "expected_sql": "SELECT DISTINCT supplier FROM restocks;"
41
+ },
42
+ {
43
+ "id": "q6",
44
+ "title": "Products with no restocks",
45
+ "difficulty": "Beginner",
46
+ "description": "List products that have never been restocked.",
47
+ "hint": "Use a LEFT JOIN and check for NULL in the restocks table.",
48
+ "expected_sql": "SELECT i.product_name FROM inventory i LEFT JOIN restocks r ON i.product_id = r.product_id WHERE r.restock_id IS NULL;"
49
+ },
50
+ {
51
+ "id": "q7",
52
+ "title": "Restocks in January 2023",
53
+ "difficulty": "Beginner",
54
+ "description": "Show all restocks that occurred in January 2023.",
55
+ "hint": "Use a WHERE clause with LIKE for restock_date.",
56
+ "expected_sql": "SELECT restock_id, product_id, restock_date, quantity FROM restocks WHERE restock_date LIKE '2023-01%';"
57
+ },
58
+ {
59
+ "id": "q8",
60
+ "title": "List products and categories",
61
+ "difficulty": "Beginner",
62
+ "description": "Show all product names and their categories.",
63
+ "hint": "Select product_name and category from the inventory table.",
64
+ "expected_sql": "SELECT product_name, category FROM inventory;"
65
+ },
66
+ {
67
+ "id": "q9",
68
+ "title": "Find high-priced products",
69
+ "difficulty": "Beginner",
70
+ "description": "Retrieve products with a price greater than 1000.",
71
+ "hint": "Use a WHERE clause to filter by price.",
72
+ "expected_sql": "SELECT product_name, price FROM inventory WHERE price > 1000;"
73
+ },
74
+ {
75
+ "id": "q10",
76
+ "title": "List restock quantities",
77
+ "difficulty": "Beginner",
78
+ "description": "Show the quantity restocked for each restock event.",
79
+ "hint": "Select restock_id and quantity from the restocks table.",
80
+ "expected_sql": "SELECT restock_id, quantity FROM restocks;"
81
+ },
82
+ {
83
+ "id": "q11",
84
+ "title": "Total stock per category",
85
+ "difficulty": "Intermediate",
86
+ "description": "Calculate the total stock for each product category.",
87
+ "hint": "Use GROUP BY on category and SUM on stock.",
88
+ "expected_sql": "SELECT category, SUM(stock) AS total_stock FROM inventory GROUP BY category;"
89
+ },
90
+ {
91
+ "id": "q12",
92
+ "title": "Average product price by category",
93
+ "difficulty": "Intermediate",
94
+ "description": "Find the average price of products in each category.",
95
+ "hint": "Use GROUP BY on category and AVG on price.",
96
+ "expected_sql": "SELECT category, AVG(price) AS avg_price FROM inventory GROUP BY category;"
97
+ },
98
+ {
99
+ "id": "q13",
100
+ "title": "Restocks by supplier",
101
+ "difficulty": "Intermediate",
102
+ "description": "Count the number of restock events for each supplier.",
103
+ "hint": "Use GROUP BY on supplier and COUNT.",
104
+ "expected_sql": "SELECT supplier, COUNT(restock_id) AS restock_count FROM restocks GROUP BY supplier;"
105
+ },
106
+ {
107
+ "id": "q14",
108
+ "title": "Total restock quantity per product",
109
+ "difficulty": "Intermediate",
110
+ "description": "Show the total quantity restocked for each product.",
111
+ "hint": "Join inventory and restocks, then GROUP BY product_name.",
112
+ "expected_sql": "SELECT i.product_name, SUM(r.quantity) AS total_restocked FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name;"
113
+ },
114
+ {
115
+ "id": "q15",
116
+ "title": "Products with multiple restocks",
117
+ "difficulty": "Intermediate",
118
+ "description": "List products that have been restocked more than once.",
119
+ "hint": "Use GROUP BY on product_name and HAVING clause.",
120
+ "expected_sql": "SELECT i.product_name, COUNT(r.restock_id) AS restock_count FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name HAVING COUNT(r.restock_id) > 1;"
121
+ },
122
+ {
123
+ "id": "q16",
124
+ "title": "Total inventory value by category",
125
+ "difficulty": "Intermediate",
126
+ "description": "Calculate the total value of inventory (price * stock) for each category.",
127
+ "hint": "Use GROUP BY on category and SUM on price * stock.",
128
+ "expected_sql": "SELECT category, SUM(price * stock) AS total_value FROM inventory GROUP BY category;"
129
+ },
130
+ {
131
+ "id": "q17",
132
+ "title": "Restock details with products",
133
+ "difficulty": "Intermediate",
134
+ "description": "Show restock details including product names, quantities, and suppliers.",
135
+ "hint": "Join inventory and restocks tables.",
136
+ "expected_sql": "SELECT r.restock_id, i.product_name, r.restock_date, r.quantity, r.supplier FROM inventory i JOIN restocks r ON i.product_id = r.product_id;"
137
+ },
138
+ {
139
+ "id": "q18",
140
+ "title": "Products with high stock",
141
+ "difficulty": "Intermediate",
142
+ "description": "List products with stock greater than 20, ordered by stock descending.",
143
+ "hint": "Use WHERE and ORDER BY clauses.",
144
+ "expected_sql": "SELECT product_name, stock FROM inventory WHERE stock > 20 ORDER BY stock DESC;"
145
+ },
146
+ {
147
+ "id": "q19",
148
+ "title": "Suppliers with large restocks",
149
+ "difficulty": "Intermediate",
150
+ "description": "Find suppliers who have restocked quantities greater than 10 in a single restock event.",
151
+ "hint": "Use WHERE clause on quantity and select DISTINCT supplier.",
152
+ "expected_sql": "SELECT DISTINCT supplier FROM restocks WHERE quantity > 10;"
153
+ },
154
+ {
155
+ "id": "q20",
156
+ "title": "Restock frequency by product",
157
+ "difficulty": "Intermediate",
158
+ "description": "Show the number of restock events for each product, including those with zero restocks.",
159
+ "hint": "Use a LEFT JOIN and GROUP BY product_name.",
160
+ "expected_sql": "SELECT i.product_name, COUNT(r.restock_id) AS restock_count FROM inventory i LEFT JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name;"
161
+ },
162
+ {
163
+ "id": "q21",
164
+ "title": "Most expensive restocked product",
165
+ "difficulty": "Advanced",
166
+ "description": "Find the product with the highest price that has been restocked.",
167
+ "hint": "Join inventory and restocks, use ORDER BY and LIMIT.",
168
+ "expected_sql": "SELECT i.product_name, i.price FROM inventory i JOIN restocks r ON i.product_id = r.product_id ORDER BY i.price DESC LIMIT 1;"
169
+ },
170
+ {
171
+ "id": "q22",
172
+ "title": "Total restock value per supplier",
173
+ "difficulty": "Advanced",
174
+ "description": "Calculate the total value of restocks (price * quantity) for each supplier.",
175
+ "hint": "Join tables, multiply price by quantity, and group by supplier.",
176
+ "expected_sql": "SELECT r.supplier, SUM(i.price * r.quantity) AS total_restock_value FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY r.supplier;"
177
+ },
178
+ {
179
+ "id": "q23",
180
+ "title": "Latest restock per product",
181
+ "difficulty": "Advanced",
182
+ "description": "Show the most recent restock date for each product that has been restocked.",
183
+ "hint": "Use GROUP BY on product_name and MAX on restock_date.",
184
+ "expected_sql": "SELECT i.product_name, MAX(r.restock_date) AS latest_restock FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name;"
185
+ },
186
+ {
187
+ "id": "q24",
188
+ "title": "Low stock high-value products",
189
+ "difficulty": "Advanced",
190
+ "description": "Identify products with stock less than 10 and price greater than 500.",
191
+ "hint": "Use multiple conditions in the WHERE clause.",
192
+ "expected_sql": "SELECT product_name, price, stock FROM inventory WHERE stock < 10 AND price > 500;"
193
+ },
194
+ {
195
+ "id": "q25",
196
+ "title": "Restock trend by month",
197
+ "difficulty": "Advanced",
198
+ "description": "Show the total quantity restocked per month in 2023.",
199
+ "hint": "Use STRFTIME to extract the month and GROUP BY.",
200
+ "expected_sql": "SELECT STRFTIME('%Y-%m', restock_date) AS month, SUM(quantity) AS total_quantity FROM restocks GROUP BY month;"
201
+ },
202
+ {
203
+ "id": "q26",
204
+ "title": "Products with high restock value",
205
+ "difficulty": "Advanced",
206
+ "description": "Find products where the total restock value (price * quantity) exceeds 5000.",
207
+ "hint": "Join tables, calculate value, and use HAVING clause.",
208
+ "expected_sql": "SELECT i.product_name, SUM(i.price * r.quantity) AS total_restock_value FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name HAVING SUM(i.price * r.quantity) > 5000;"
209
+ },
210
+ {
211
+ "id": "q27",
212
+ "title": "Supplier product diversity",
213
+ "difficulty": "Advanced",
214
+ "description": "Count the number of unique products each supplier has restocked.",
215
+ "hint": "Use COUNT and DISTINCT on product_id, grouped by supplier.",
216
+ "expected_sql": "SELECT r.supplier, COUNT(DISTINCT r.product_id) AS unique_products FROM restocks r GROUP BY r.supplier;"
217
+ },
218
+ {
219
+ "id": "q28",
220
+ "title": "Overstocked products",
221
+ "difficulty": "Advanced",
222
+ "description": "Identify products where the total restocked quantity exceeds the current stock by more than 10.",
223
+ "hint": "Join tables, compare SUM(quantity) with stock, and use HAVING.",
224
+ "expected_sql": "SELECT i.product_name, i.stock, SUM(r.quantity) AS total_restocked FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name, i.stock HAVING SUM(r.quantity) > i.stock + 10;"
225
+ },
226
+ {
227
+ "id": "q29",
228
+ "title": "Suppliers restocking all electronics",
229
+ "difficulty": "Advanced",
230
+ "description": "List suppliers who have restocked every product in the Electronics category.",
231
+ "hint": "Count distinct Electronics products per supplier and compare with total Electronics products.",
232
+ "expected_sql": "SELECT r.supplier FROM restocks r JOIN inventory i ON r.product_id = i.product_id WHERE i.category = 'Electronics' GROUP BY r.supplier HAVING COUNT(DISTINCT r.product_id) = (SELECT COUNT(*) FROM inventory WHERE category = 'Electronics');"
233
+ },
234
+ {
235
+ "id": "q30",
236
+ "title": "Earliest and latest restock per supplier",
237
+ "difficulty": "Advanced",
238
+ "description": "Show the earliest and latest restock dates for each supplier.",
239
+ "hint": "Use MIN and MAX on restock_date, grouped by supplier.",
240
+ "expected_sql": "SELECT supplier, MIN(restock_date) AS earliest_restock, MAX(restock_date) AS latest_restock FROM restocks GROUP BY supplier;"
241
+ }
242
+ ]
app/questions/library.json ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "q1",
4
+ "title": "List all science fiction books",
5
+ "difficulty": "Beginner",
6
+ "description": "Retrieve the titles and authors of all books in the Science Fiction genre.",
7
+ "hint": "Use a WHERE clause to filter by genre.",
8
+ "expected_sql": "SELECT title, author FROM books WHERE genre = 'Science Fiction';"
9
+ },
10
+ {
11
+ "id": "q2",
12
+ "title": "Find books with low availability",
13
+ "difficulty": "Beginner",
14
+ "description": "Show books with fewer than 2 available copies.",
15
+ "hint": "Use a WHERE clause to filter by available_copies.",
16
+ "expected_sql": "SELECT title, available_copies FROM books WHERE available_copies < 2;"
17
+ },
18
+ {
19
+ "id": "q3",
20
+ "title": "List loans for user ID 101",
21
+ "difficulty": "Beginner",
22
+ "description": "Retrieve all loan details for the user with ID 101.",
23
+ "hint": "Use a WHERE clause to filter by user_id.",
24
+ "expected_sql": "SELECT loan_id, book_id, issue_date, due_date FROM loans WHERE user_id = 101;"
25
+ },
26
+ {
27
+ "id": "q4",
28
+ "title": "Find books published after 2000",
29
+ "difficulty": "Beginner",
30
+ "description": "Show titles and publication years of books published after 2000.",
31
+ "hint": "Use a WHERE clause with year comparison.",
32
+ "expected_sql": "SELECT title, year FROM books WHERE year > 2000;"
33
+ },
34
+ {
35
+ "id": "q5",
36
+ "title": "List unique genres",
37
+ "difficulty": "Beginner",
38
+ "description": "Retrieve all unique genres from the books table.",
39
+ "hint": "Use DISTINCT to avoid duplicate genres.",
40
+ "expected_sql": "SELECT DISTINCT genre FROM books;"
41
+ },
42
+ {
43
+ "id": "q6",
44
+ "title": "Find overdue loans",
45
+ "difficulty": "Beginner",
46
+ "description": "Show loans that are overdue as of '2023-03-01' (due_date before '2023-03-01' and not returned).",
47
+ "hint": "Use a WHERE clause to check due_date and NULL return_date.",
48
+ "expected_sql": "SELECT loan_id, book_id, due_date FROM loans WHERE due_date < '2023-03-01' AND return_date IS NULL;"
49
+ },
50
+ {
51
+ "id": "q7",
52
+ "title": "List users joined after 2020",
53
+ "difficulty": "Beginner",
54
+ "description": "Show names and emails of users who joined after December 31, 2020.",
55
+ "hint": "Use a WHERE clause with membership_date.",
56
+ "expected_sql": "SELECT name, email FROM users WHERE membership_date > '2020-12-31';"
57
+ },
58
+ {
59
+ "id": "q8",
60
+ "title": "Books by George Orwell",
61
+ "difficulty": "Beginner",
62
+ "description": "Retrieve all books written by George Orwell.",
63
+ "hint": "Use a WHERE clause to filter by author.",
64
+ "expected_sql": "SELECT title, genre FROM books WHERE author = 'George Orwell';"
65
+ },
66
+ {
67
+ "id": "q9",
68
+ "title": "List active loans",
69
+ "difficulty": "Beginner",
70
+ "description": "Show all loans that have not been returned.",
71
+ "hint": "Use a WHERE clause to check for NULL return_date.",
72
+ "expected_sql": "SELECT loan_id, book_id, user_id, issue_date FROM loans WHERE return_date IS NULL;"
73
+ },
74
+ {
75
+ "id": "q10",
76
+ "title": "List books by publication year",
77
+ "difficulty": "Beginner",
78
+ "description": "Show all book titles and their publication years, ordered by year ascending.",
79
+ "hint": "Use ORDER BY clause on year.",
80
+ "expected_sql": "SELECT title, year FROM books ORDER BY year ASC;"
81
+ },
82
+ {
83
+ "id": "q11",
84
+ "title": "Count loans per book",
85
+ "difficulty": "Intermediate",
86
+ "description": "Show the number of loans for each book, including books with zero loans.",
87
+ "hint": "Use a LEFT JOIN and GROUP BY book title.",
88
+ "expected_sql": "SELECT b.title, COUNT(l.loan_id) AS loan_count FROM books b LEFT JOIN loans l ON b.book_id = l.book_id GROUP BY b.title;"
89
+ },
90
+ {
91
+ "id": "q12",
92
+ "title": "Total loans per user",
93
+ "difficulty": "Intermediate",
94
+ "description": "Calculate the total number of loans for each user, including users with zero loans.",
95
+ "hint": "Use a LEFT JOIN and GROUP BY user name.",
96
+ "expected_sql": "SELECT u.name, COUNT(l.loan_id) AS total_loans FROM users u LEFT JOIN loans l ON u.user_id = l.user_id GROUP BY u.name;"
97
+ },
98
+ {
99
+ "id": "q13",
100
+ "title": "Books by genre count",
101
+ "difficulty": "Intermediate",
102
+ "description": "Count the number of books in each genre.",
103
+ "hint": "Use GROUP BY on genre and COUNT.",
104
+ "expected_sql": "SELECT genre, COUNT(book_id) AS book_count FROM books GROUP BY genre;"
105
+ },
106
+ {
107
+ "id": "q14",
108
+ "title": "Active loans by genre",
109
+ "difficulty": "Intermediate",
110
+ "description": "Show the number of active loans (not returned) for each book genre.",
111
+ "hint": "Join books and loans, filter by NULL return_date, and GROUP BY genre.",
112
+ "expected_sql": "SELECT b.genre, COUNT(l.loan_id) AS active_loans FROM books b JOIN loans l ON b.book_id = l.book_id WHERE l.return_date IS NULL GROUP BY b.genre;"
113
+ },
114
+ {
115
+ "id": "q15",
116
+ "title": "Users with multiple loans",
117
+ "difficulty": "Intermediate",
118
+ "description": "Find users who have taken out more than one loan.",
119
+ "hint": "Use GROUP BY on user name and HAVING clause.",
120
+ "expected_sql": "SELECT u.name, COUNT(l.loan_id) AS loan_count FROM users u JOIN loans l ON u.user_id = l.user_id GROUP BY u.name HAVING COUNT(l.loan_id) > 1;"
121
+ },
122
+ {
123
+ "id": "q16",
124
+ "title": "Books never loaned",
125
+ "difficulty": "Intermediate",
126
+ "description": "List books that have never been loaned.",
127
+ "hint": "Use a LEFT JOIN and check for NULL in the loans table.",
128
+ "expected_sql": "SELECT b.title FROM books b LEFT JOIN loans l ON b.book_id = l.book_id WHERE l.loan_id IS NULL;"
129
+ },
130
+ {
131
+ "id": "q17",
132
+ "title": "Loan details with book and user",
133
+ "difficulty": "Intermediate",
134
+ "description": "Show loan details including book title, user name, and issue date for all loans.",
135
+ "hint": "Join books, users, and loans tables.",
136
+ "expected_sql": "SELECT l.loan_id, b.title, u.name, l.issue_date FROM loans l JOIN books b ON l.book_id = b.book_id JOIN users u ON l.user_id = u.user_id;"
137
+ },
138
+ {
139
+ "id": "q18",
140
+ "title": "Books loaned in 2022",
141
+ "difficulty": "Intermediate",
142
+ "description": "List all books that were loaned in 2022.",
143
+ "hint": "Use a WHERE clause with LIKE on issue_date and JOIN with books.",
144
+ "expected_sql": "SELECT DISTINCT b.title FROM books b JOIN loans l ON b.book_id = l.book_id WHERE l.issue_date LIKE '2022%';"
145
+ },
146
+ {
147
+ "id": "q19",
148
+ "title": "Average loan duration for returned books",
149
+ "difficulty": "Intermediate",
150
+ "description": "Calculate the average number of days for loans that have been returned.",
151
+ "hint": "Use JULIANDAY to calculate date difference and AVG.",
152
+ "expected_sql": "SELECT AVG(JULIANDAY(return_date) - JULIANDAY(issue_date)) AS avg_loan_days FROM loans WHERE return_date IS NOT NULL;"
153
+ },
154
+ {
155
+ "id": "q20",
156
+ "title": "Most popular author by loans",
157
+ "difficulty": "Intermediate",
158
+ "description": "Find the author with the most loans.",
159
+ "hint": "Join books and loans, GROUP BY author, and use LIMIT.",
160
+ "expected_sql": "SELECT b.author, COUNT(l.loan_id) AS loan_count FROM books b JOIN loans l ON b.book_id = l.book_id GROUP BY b.author ORDER BY loan_count DESC LIMIT 1;"
161
+ },
162
+ {
163
+ "id": "q21",
164
+ "title": "Overdue loans with user details",
165
+ "difficulty": "Advanced",
166
+ "description": "Show user names, book titles, and days overdue for loans not returned by '2023-03-01'.",
167
+ "hint": "Join tables, filter for overdue loans, and calculate days overdue.",
168
+ "expected_sql": "SELECT u.name, b.title, ROUND(JULIANDAY('2023-03-01') - JULIANDAY(l.due_date), 1) AS days_overdue FROM loans l JOIN books b ON l.book_id = b.book_id JOIN users u ON l.user_id = u.user_id WHERE l.due_date < '2023-03-01' AND l.return_date IS NULL;"
169
+ },
170
+ {
171
+ "id": "q22",
172
+ "title": "Most active user by loan duration",
173
+ "difficulty": "Advanced",
174
+ "description": "Find the user with the highest total loan duration for returned books.",
175
+ "hint": "Join users and loans, calculate duration, GROUP BY user, and use LIMIT.",
176
+ "expected_sql": "SELECT u.name, SUM(JULIANDAY(l.return_date) - JULIANDAY(l.issue_date)) AS total_loan_days FROM users u JOIN loans l ON u.user_id = l.user_id WHERE l.return_date IS NOT NULL GROUP BY u.name ORDER BY total_loan_days DESC LIMIT 1;"
177
+ },
178
+ {
179
+ "id": "q23",
180
+ "title": "Books with high demand",
181
+ "difficulty": "Advanced",
182
+ "description": "Identify books with more loans than their available copies.",
183
+ "hint": "Join books and loans, GROUP BY book, and use HAVING clause.",
184
+ "expected_sql": "SELECT b.title, COUNT(l.loan_id) AS loan_count, b.available_copies FROM books b JOIN loans l ON b.book_id = l.book_id GROUP BY b.title, b.available_copies HAVING COUNT(l.loan_id) > b.available_copies;"
185
+ },
186
+ {
187
+ "id": "q24",
188
+ "title": "Longest overdue loan",
189
+ "difficulty": "Advanced",
190
+ "description": "Find the loan with the longest overdue period as of '2023-03-01'.",
191
+ "hint": "Calculate days overdue, filter for overdue loans, and use LIMIT.",
192
+ "expected_sql": "SELECT l.loan_id, b.title, ROUND(JULIANDAY('2023-03-01') - JULIANDAY(l.due_date), 1) AS days_overdue FROM loans l JOIN books b ON l.book_id = b.book_id WHERE l.return_date IS NULL AND l.due_date < '2023-03-01' ORDER BY days_overdue DESC LIMIT 1;"
193
+ },
194
+ {
195
+ "id": "q25",
196
+ "title": "Loan activity by month",
197
+ "difficulty": "Advanced",
198
+ "description": "Show the number of loans issued per month in 2022 and 2023.",
199
+ "hint": "Use STRFTIME to extract the month and GROUP BY.",
200
+ "expected_sql": "SELECT STRFTIME('%Y-%m', issue_date) AS month, COUNT(loan_id) AS loan_count FROM loans GROUP BY month;"
201
+ },
202
+ {
203
+ "id": "q26",
204
+ "title": "Users with no returns",
205
+ "difficulty": "Advanced",
206
+ "description": "List users who have loans but no returned books.",
207
+ "hint": "Use JOIN and check for NULL return_date with HAVING clause.",
208
+ "expected_sql": "SELECT u.name FROM users u JOIN loans l ON u.user_id = l.user_id GROUP BY u.name HAVING COUNT(l.return_date) = 0;"
209
+ },
210
+ {
211
+ "id": "q27",
212
+ "title": "Books loaned by multiple users",
213
+ "difficulty": "Advanced",
214
+ "description": "Find books that have been loaned by more than one distinct user.",
215
+ "hint": "Use COUNT and DISTINCT on user_id, GROUP BY book title.",
216
+ "expected_sql": "SELECT b.title, COUNT(DISTINCT l.user_id) AS user_count FROM books b JOIN loans l ON b.book_id = l.book_id GROUP BY b.title HAVING COUNT(DISTINCT l.user_id) > 1;"
217
+ },
218
+ {
219
+ "id": "q28",
220
+ "title": "Average loan duration by genre",
221
+ "difficulty": "Advanced",
222
+ "description": "Calculate the average loan duration for returned books by genre.",
223
+ "hint": "Join books and loans, calculate duration, and GROUP BY genre.",
224
+ "expected_sql": "SELECT b.genre, AVG(JULIANDAY(l.return_date) - JULIANDAY(l.issue_date)) AS avg_loan_days FROM books b JOIN loans l ON b.book_id = l.book_id WHERE l.return_date IS NOT NULL GROUP BY b.genre;"
225
+ },
226
+ {
227
+ "id": "q29",
228
+ "title": "Users borrowing all science fiction books",
229
+ "difficulty": "Advanced",
230
+ "description": "List users who have borrowed every book in the Science Fiction genre.",
231
+ "hint": "Count distinct Science Fiction books per user and compare with total Science Fiction books.",
232
+ "expected_sql": "SELECT u.name FROM users u JOIN loans l ON u.user_id = l.user_id JOIN books b ON l.book_id = b.book_id WHERE b.genre = 'Science Fiction' GROUP BY u.name HAVING COUNT(DISTINCT b.book_id) = (SELECT COUNT(*) FROM books WHERE genre = 'Science Fiction');"
233
+ },
234
+ {
235
+ "id": "q30",
236
+ "title": "Earliest and latest loan per book",
237
+ "difficulty": "Advanced",
238
+ "description": "Show the earliest and latest issue dates for each book that has been loaned.",
239
+ "hint": "Use MIN and MAX on issue_date, GROUP BY book title.",
240
+ "expected_sql": "SELECT b.title, MIN(l.issue_date) AS earliest_loan, MAX(l.issue_date) AS latest_loan FROM books b JOIN loans l ON b.book_id = l.book_id GROUP BY b.title;"
241
+ }
242
+ ]
app/questions/restaurant.json ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "q1",
4
+ "title": "List all main course items",
5
+ "difficulty": "Beginner",
6
+ "description": "Retrieve the names and prices of all menu items in the Main Course category.",
7
+ "hint": "Use a WHERE clause to filter by category.",
8
+ "expected_sql": "SELECT name, price FROM menu_items WHERE category = 'Main Course';"
9
+ },
10
+ {
11
+ "id": "q2",
12
+ "title": "Find orders by customer ID 1",
13
+ "difficulty": "Beginner",
14
+ "description": "Show all orders placed by the customer with ID 1.",
15
+ "hint": "Use a WHERE clause to filter by customer_id.",
16
+ "expected_sql": "SELECT order_id, order_date, total_amount FROM orders WHERE customer_id = 1;"
17
+ },
18
+ {
19
+ "id": "q3",
20
+ "title": "List all beverages",
21
+ "difficulty": "Beginner",
22
+ "description": "Retrieve the names and prices of all items in the Beverage category.",
23
+ "hint": "Use a WHERE clause to filter by category.",
24
+ "expected_sql": "SELECT name, price FROM menu_items WHERE category = 'Beverage';"
25
+ },
26
+ {
27
+ "id": "q4",
28
+ "title": "Find orders on June 11, 2023",
29
+ "difficulty": "Beginner",
30
+ "description": "Show all orders placed on June 11, 2023.",
31
+ "hint": "Use a WHERE clause with date comparison.",
32
+ "expected_sql": "SELECT order_id, customer_id, total_amount FROM orders WHERE order_date = '2023-06-11';"
33
+ },
34
+ {
35
+ "id": "q5",
36
+ "title": "List unique customer emails",
37
+ "difficulty": "Beginner",
38
+ "description": "Retrieve all unique customer email addresses.",
39
+ "hint": "Use DISTINCT to avoid duplicate emails.",
40
+ "expected_sql": "SELECT DISTINCT email FROM customers;"
41
+ },
42
+ {
43
+ "id": "q6",
44
+ "title": "Items in order ID 3",
45
+ "difficulty": "Beginner",
46
+ "description": "Show the menu items and quantities for order ID 3.",
47
+ "hint": "Join order_items with menu_items and filter by order_id.",
48
+ "expected_sql": "SELECT m.name, oi.quantity FROM order_items oi JOIN menu_items m ON oi.item_id = m.item_id WHERE oi.order_id = 3;"
49
+ },
50
+ {
51
+ "id": "q7",
52
+ "title": "List customer contact details",
53
+ "difficulty": "Beginner",
54
+ "description": "Show names and contact numbers of all customers.",
55
+ "hint": "Select name and contact from the customers table.",
56
+ "expected_sql": "SELECT name, contact FROM customers;"
57
+ },
58
+ {
59
+ "id": "q8",
60
+ "title": "Find high-priced items",
61
+ "difficulty": "Beginner",
62
+ "description": "Retrieve menu items with a price greater than 150.",
63
+ "hint": "Use a WHERE clause to filter by price.",
64
+ "expected_sql": "SELECT name, price FROM menu_items WHERE price > 150;"
65
+ },
66
+ {
67
+ "id": "q9",
68
+ "title": "List all order items",
69
+ "difficulty": "Beginner",
70
+ "description": "Show all order items with their quantities.",
71
+ "hint": "Select from the order_items table.",
72
+ "expected_sql": "SELECT order_item_id, order_id, item_id, quantity FROM order_items;"
73
+ },
74
+ {
75
+ "id": "q10",
76
+ "title": "Find orders above 300",
77
+ "difficulty": "Beginner",
78
+ "description": "Show orders with a total amount greater than 300.",
79
+ "hint": "Use a WHERE clause to filter by total_amount.",
80
+ "expected_sql": "SELECT order_id, customer_id, total_amount FROM orders WHERE total_amount > 300;"
81
+ },
82
+ {
83
+ "id": "q11",
84
+ "title": "Count orders per customer",
85
+ "difficulty": "Intermediate",
86
+ "description": "Show the number of orders placed by each customer, including those with zero orders.",
87
+ "hint": "Use a LEFT JOIN and GROUP BY customer name.",
88
+ "expected_sql": "SELECT c.name, COUNT(o.order_id) AS order_count FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.name;"
89
+ },
90
+ {
91
+ "id": "q12",
92
+ "title": "Total items ordered per category",
93
+ "difficulty": "Intermediate",
94
+ "description": "Calculate the total quantity of items ordered for each menu item category.",
95
+ "hint": "Join menu_items and order_items, then GROUP BY category.",
96
+ "expected_sql": "SELECT m.category, SUM(oi.quantity) AS total_quantity FROM menu_items m JOIN order_items oi ON m.item_id = oi.item_id GROUP BY m.category;"
97
+ },
98
+ {
99
+ "id": "q13",
100
+ "title": "Average order amount",
101
+ "difficulty": "Intermediate",
102
+ "description": "Calculate the average total amount of all orders.",
103
+ "hint": "Use AVG on total_amount in the orders table.",
104
+ "expected_sql": "SELECT AVG(total_amount) AS avg_order_amount FROM orders;"
105
+ },
106
+ {
107
+ "id": "q14",
108
+ "title": "Most ordered menu item",
109
+ "difficulty": "Intermediate",
110
+ "description": "Identify the menu item with the highest total quantity ordered.",
111
+ "hint": "Join order_items and menu_items, GROUP BY item name, and use LIMIT.",
112
+ "expected_sql": "SELECT m.name, SUM(oi.quantity) AS total_quantity FROM menu_items m JOIN order_items oi ON m.item_id = oi.item_id GROUP BY m.name ORDER BY total_quantity DESC LIMIT 1;"
113
+ },
114
+ {
115
+ "id": "q15",
116
+ "title": "Customers with multiple orders",
117
+ "difficulty": "Intermediate",
118
+ "description": "Find customers who have placed more than one order.",
119
+ "hint": "Use GROUP BY on customer name and HAVING clause.",
120
+ "expected_sql": "SELECT c.name, COUNT(o.order_id) AS order_count FROM customers c JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.name HAVING COUNT(o.order_id) > 1;"
121
+ },
122
+ {
123
+ "id": "q16",
124
+ "title": "Menu items never ordered",
125
+ "difficulty": "Intermediate",
126
+ "description": "List menu items that have never been ordered.",
127
+ "hint": "Use a LEFT JOIN and check for NULL in the order_items table.",
128
+ "expected_sql": "SELECT m.name FROM menu_items m LEFT JOIN order_items oi ON m.item_id = oi.item_id WHERE oi.order_item_id IS NULL;"
129
+ },
130
+ {
131
+ "id": "q17",
132
+ "title": "Order details with items",
133
+ "difficulty": "Intermediate",
134
+ "description": "Show order details including customer name and menu items for each order.",
135
+ "hint": "Join orders, customers, order_items, and menu_items tables.",
136
+ "expected_sql": "SELECT o.order_id, c.name AS customer_name, m.name AS item_name, oi.quantity FROM orders o JOIN customers c ON o.customer_id = c.customer_id JOIN order_items oi ON o.order_id = oi.order_id JOIN menu_items m ON oi.item_id = m.item_id;"
137
+ },
138
+ {
139
+ "id": "q18",
140
+ "title": "Total revenue per customer",
141
+ "difficulty": "Intermediate",
142
+ "description": "Calculate the total amount spent by each customer based on order totals.",
143
+ "hint": "Use GROUP BY on customer name and SUM on total_amount.",
144
+ "expected_sql": "SELECT c.name, SUM(o.total_amount) AS total_spent FROM customers c JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.name;"
145
+ },
146
+ {
147
+ "id": "q19",
148
+ "title": "Orders with multiple items",
149
+ "difficulty": "Intermediate",
150
+ "description": "List orders that include more than one type of menu item.",
151
+ "hint": "Use GROUP BY on order_id and HAVING clause on COUNT of item_id.",
152
+ "expected_sql": "SELECT o.order_id, COUNT(oi.item_id) AS item_count FROM orders o JOIN order_items oi ON o.order_id = oi.order_id GROUP BY o.order_id HAVING COUNT(oi.item_id) > 1;"
153
+ },
154
+ {
155
+ "id": "q20",
156
+ "title": "Items ordered with high quantity",
157
+ "difficulty": "Intermediate",
158
+ "description": "Show menu items with a total ordered quantity greater than 2.",
159
+ "hint": "Join order_items and menu_items, GROUP BY item name, and use HAVING.",
160
+ "expected_sql": "SELECT m.name, SUM(oi.quantity) AS total_quantity FROM menu_items m JOIN order_items oi ON m.item_id = oi.item_id GROUP BY m.name HAVING SUM(oi.quantity) > 2;"
161
+ },
162
+ {
163
+ "id": "q21",
164
+ "title": "Customer order value analysis",
165
+ "difficulty": "Advanced",
166
+ "description": "Calculate the total value of orders (sum of price * quantity) for each customer.",
167
+ "hint": "Join all tables, multiply price by quantity, and GROUP BY customer name.",
168
+ "expected_sql": "SELECT c.name, SUM(m.price * oi.quantity) AS total_order_value FROM customers c JOIN orders o ON c.customer_id = o.customer_id JOIN order_items oi ON o.order_id = oi.order_id JOIN menu_items m ON oi.item_id = m.item_id GROUP BY c.name;"
169
+ },
170
+ {
171
+ "id": "q22",
172
+ "title": "Most expensive order",
173
+ "difficulty": "Advanced",
174
+ "description": "Identify the order with the highest calculated total based on item prices and quantities.",
175
+ "hint": "Join orders, order_items, and menu_items, calculate total, and use LIMIT.",
176
+ "expected_sql": "SELECT o.order_id, SUM(m.price * oi.quantity) AS calculated_total FROM orders o JOIN order_items oi ON o.order_id = oi.order_id JOIN menu_items m ON oi.item_id = m.item_id GROUP BY o.order_id ORDER BY calculated_total DESC LIMIT 1;"
177
+ },
178
+ {
179
+ "id": "q23",
180
+ "title": "Customers ordering all main courses",
181
+ "difficulty": "Advanced",
182
+ "description": "Find customers who have ordered every item in the Main Course category.",
183
+ "hint": "Count distinct Main Course items per customer and compare with total Main Course items.",
184
+ "expected_sql": "SELECT c.name FROM customers c JOIN orders o ON c.customer_id = o.customer_id JOIN order_items oi ON o.order_id = oi.order_id JOIN menu_items m ON oi.item_id = m.item_id WHERE m.category = 'Main Course' GROUP BY c.name HAVING COUNT(DISTINCT m.item_id) = (SELECT COUNT(*) FROM menu_items WHERE category = 'Main Course');"
185
+ },
186
+ {
187
+ "id": "q24",
188
+ "title": "Average item price per order",
189
+ "difficulty": "Advanced",
190
+ "description": "Calculate the average price of items in each order.",
191
+ "hint": "Join order_items and menu_items, GROUP BY order_id, and use AVG.",
192
+ "expected_sql": "SELECT o.order_id, AVG(m.price) AS avg_item_price FROM orders o JOIN order_items oi ON o.order_id = oi.order_id JOIN menu_items m ON oi.item_id = m.item_id GROUP BY o.order_id;"
193
+ },
194
+ {
195
+ "id": "q25",
196
+ "title": "Order frequency by date",
197
+ "difficulty": "Advanced",
198
+ "description": "Show the number of orders placed on each date in June 2023.",
199
+ "hint": "Use GROUP BY on order_date and COUNT.",
200
+ "expected_sql": "SELECT order_date, COUNT(order_id) AS order_count FROM orders WHERE order_date LIKE '2023-06%' GROUP BY order_date;"
201
+ },
202
+ {
203
+ "id": "q26",
204
+ "title": "Customers who ordered Margherita Pizza",
205
+ "difficulty": "Advanced",
206
+ "description": "List customers who have ordered a Margherita Pizza.",
207
+ "hint": "Join all tables and filter by item name.",
208
+ "expected_sql": "SELECT DISTINCT c.name FROM customers c JOIN orders o ON c.customer_id = o.customer_id JOIN order_items oi ON o.order_id = oi.order_id JOIN menu_items m ON oi.item_id = m.item_id WHERE m.name = 'Margherita Pizza';"
209
+ },
210
+ {
211
+ "id": "q27",
212
+ "title": "Category revenue contribution",
213
+ "difficulty": "Advanced",
214
+ "description": "Calculate the total revenue generated by each menu item category.",
215
+ "hint": "Join menu_items and order_items, multiply price by quantity, and GROUP BY category.",
216
+ "expected_sql": "SELECT m.category, SUM(m.price * oi.quantity) AS total_revenue FROM menu_items m JOIN order_items oi ON m.item_id = oi.item_id GROUP BY m.category;"
217
+ },
218
+ {
219
+ "id": "q28",
220
+ "title": "Orders with high item diversity",
221
+ "difficulty": "Advanced",
222
+ "description": "Find orders that include items from more than one category.",
223
+ "hint": "Join tables, GROUP BY order_id, and COUNT distinct categories.",
224
+ "expected_sql": "SELECT o.order_id, COUNT(DISTINCT m.category) AS category_count FROM orders o JOIN order_items oi ON o.order_id = oi.order_id JOIN menu_items m ON oi.item_id = m.item_id GROUP BY o.order_id HAVING COUNT(DISTINCT m.category) > 1;"
225
+ },
226
+ {
227
+ "id": "q29",
228
+ "title": "Customer spending rank",
229
+ "difficulty": "Advanced",
230
+ "description": "Rank customers by their total spending (sum of order totals).",
231
+ "hint": "Use GROUP BY on customer name, SUM, and ORDER BY.",
232
+ "expected_sql": "SELECT c.name, SUM(o.total_amount) AS total_spent FROM customers c JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.name ORDER BY total_spent DESC;"
233
+ },
234
+ {
235
+ "id": "q30",
236
+ "title": "Items with high revenue contribution",
237
+ "difficulty": "Advanced",
238
+ "description": "Identify menu items contributing more than 200 in total revenue (price * quantity).",
239
+ "hint": "Join menu_items and order_items, calculate revenue, and use HAVING.",
240
+ "expected_sql": "SELECT m.name, SUM(m.price * oi.quantity) AS total_revenue FROM menu_items m JOIN order_items oi ON m.item_id = oi.item_id GROUP BY m.name HAVING SUM(m.price * oi.quantity) > 200;"
241
+ }
242
+ ]
app/questions/retail.json ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "q1",
4
+ "title": "List electronics products",
5
+ "difficulty": "Beginner",
6
+ "description": "Retrieve the names and prices of all products in the Electronics category.",
7
+ "hint": "Use a WHERE clause to filter by category.",
8
+ "expected_sql": "SELECT product_name, price FROM inventory WHERE category = 'Electronics';"
9
+ },
10
+ {
11
+ "id": "q2",
12
+ "title": "Find low stock products",
13
+ "difficulty": "Beginner",
14
+ "description": "Show products with stock below their reorder level.",
15
+ "hint": "Use a WHERE clause to compare stock and reorder_level.",
16
+ "expected_sql": "SELECT product_name, stock, reorder_level FROM inventory WHERE stock < reorder_level;"
17
+ },
18
+ {
19
+ "id": "q3",
20
+ "title": "List restocks for product ID 1",
21
+ "difficulty": "Beginner",
22
+ "description": "Retrieve all restock details for the product with ID 1.",
23
+ "hint": "Use a WHERE clause to filter by product_id in the restocks table.",
24
+ "expected_sql": "SELECT restock_id, restock_date, quantity, supplier FROM restocks WHERE product_id = 1;"
25
+ },
26
+ {
27
+ "id": "q4",
28
+ "title": "Find products by price range",
29
+ "difficulty": "Beginner",
30
+ "description": "Show products with prices between 100 and 500, inclusive.",
31
+ "hint": "Use a BETWEEN clause for the price range.",
32
+ "expected_sql": "SELECT product_name, price FROM inventory WHERE price BETWEEN 100 AND 500;"
33
+ },
34
+ {
35
+ "id": "q5",
36
+ "title": "List unique suppliers",
37
+ "difficulty": "Beginner",
38
+ "description": "Retrieve all unique suppliers from the restocks table.",
39
+ "hint": "Use DISTINCT to avoid duplicate suppliers.",
40
+ "expected_sql": "SELECT DISTINCT supplier FROM restocks;"
41
+ },
42
+ {
43
+ "id": "q6",
44
+ "title": "Find restocks in 2023",
45
+ "difficulty": "Beginner",
46
+ "description": "Show all restocks that occurred in 2023.",
47
+ "hint": "Use a WHERE clause with LIKE for restock_date.",
48
+ "expected_sql": "SELECT restock_id, product_id, restock_date, quantity FROM restocks WHERE restock_date LIKE '2023%';"
49
+ },
50
+ {
51
+ "id": "q7",
52
+ "title": "List accessories products",
53
+ "difficulty": "Beginner",
54
+ "description": "Retrieve product names and stock for all items in the Accessories category.",
55
+ "hint": "Use a WHERE clause to filter by category.",
56
+ "expected_sql": "SELECT product_name, stock FROM inventory WHERE category = 'Accessories';"
57
+ },
58
+ {
59
+ "id": "q8",
60
+ "title": "Find high stock products",
61
+ "difficulty": "Beginner",
62
+ "description": "Show products with stock greater than 20.",
63
+ "hint": "Use a WHERE clause to filter by stock.",
64
+ "expected_sql": "SELECT product_name, stock FROM inventory WHERE stock > 20;"
65
+ },
66
+ {
67
+ "id": "q9",
68
+ "title": "List restock quantities",
69
+ "difficulty": "Beginner",
70
+ "description": "Show the quantity restocked for each restock event.",
71
+ "hint": "Select restock_id and quantity from the restocks table.",
72
+ "expected_sql": "SELECT restock_id, quantity FROM restocks;"
73
+ },
74
+ {
75
+ "id": "q10",
76
+ "title": "Find products with high reorder level",
77
+ "difficulty": "Beginner",
78
+ "description": "Retrieve products with a reorder level greater than 5.",
79
+ "hint": "Use a WHERE clause to filter by reorder_level.",
80
+ "expected_sql": "SELECT product_name, reorder_level FROM inventory WHERE reorder_level > 5;"
81
+ },
82
+ {
83
+ "id": "q11",
84
+ "title": "Total stock per category",
85
+ "difficulty": "Intermediate",
86
+ "description": "Calculate the total stock for each product category.",
87
+ "hint": "Use GROUP BY on category and SUM on stock.",
88
+ "expected_sql": "SELECT category, SUM(stock) AS total_stock FROM inventory GROUP BY category;"
89
+ },
90
+ {
91
+ "id": "q12",
92
+ "title": "Average price per category",
93
+ "difficulty": "Intermediate",
94
+ "description": "Calculate the average price of products in each category.",
95
+ "hint": "Use GROUP BY on category and AVG on price.",
96
+ "expected_sql": "SELECT category, AVG(price) AS avg_price FROM inventory GROUP BY category;"
97
+ },
98
+ {
99
+ "id": "q13",
100
+ "title": "Count restocks per supplier",
101
+ "difficulty": "Intermediate",
102
+ "description": "Show the number of restock events for each supplier.",
103
+ "hint": "Use GROUP BY on supplier and COUNT.",
104
+ "expected_sql": "SELECT supplier, COUNT(restock_id) AS restock_count FROM restocks GROUP BY supplier;"
105
+ },
106
+ {
107
+ "id": "q14",
108
+ "title": "Total restock quantity per product",
109
+ "difficulty": "Intermediate",
110
+ "description": "Calculate the total quantity restocked for each product.",
111
+ "hint": "Join inventory and restocks, then GROUP BY product_name.",
112
+ "expected_sql": "SELECT i.product_name, SUM(r.quantity) AS total_restocked FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name;"
113
+ },
114
+ {
115
+ "id": "q15",
116
+ "title": "Products with multiple restocks",
117
+ "difficulty": "Intermediate",
118
+ "description": "List products that have been restocked more than once.",
119
+ "hint": "Use GROUP BY on product_name and HAVING clause.",
120
+ "expected_sql": "SELECT i.product_name, COUNT(r.restock_id) AS restock_count FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name HAVING COUNT(r.restock_id) > 1;"
121
+ },
122
+ {
123
+ "id": "q16",
124
+ "title": "Total inventory value",
125
+ "difficulty": "Intermediate",
126
+ "description": "Calculate the total value of inventory (price * stock) for each category.",
127
+ "hint": "Use GROUP BY on category and SUM on price * stock.",
128
+ "expected_sql": "SELECT category, SUM(price * stock) AS total_value FROM inventory GROUP BY category;"
129
+ },
130
+ {
131
+ "id": "q17",
132
+ "title": "Restock details with product names",
133
+ "difficulty": "Intermediate",
134
+ "description": "Show restock details including product names, quantities, and suppliers.",
135
+ "hint": "Join inventory and restocks tables.",
136
+ "expected_sql": "SELECT r.restock_id, i.product_name, r.restock_date, r.quantity, r.supplier FROM inventory i JOIN restocks r ON i.product_id = r.product_id;"
137
+ },
138
+ {
139
+ "id": "q18",
140
+ "title": "Products needing restock",
141
+ "difficulty": "Intermediate",
142
+ "description": "List products with stock at or below their reorder level, ordered by stock ascending.",
143
+ "hint": "Use WHERE and ORDER BY clauses.",
144
+ "expected_sql": "SELECT product_name, stock, reorder_level FROM inventory WHERE stock <= reorder_level ORDER BY stock ASC;"
145
+ },
146
+ {
147
+ "id": "q19",
148
+ "title": "Suppliers with large restocks",
149
+ "difficulty": "Intermediate",
150
+ "description": "Find suppliers who have restocked quantities greater than 20 in a single restock event.",
151
+ "hint": "Use WHERE clause on quantity and select DISTINCT supplier.",
152
+ "expected_sql": "SELECT DISTINCT supplier FROM restocks WHERE quantity > 20;"
153
+ },
154
+ {
155
+ "id": "q20",
156
+ "title": "Restock frequency by product",
157
+ "difficulty": "Intermediate",
158
+ "description": "Show the number of restock events for each product, including those with zero restocks.",
159
+ "hint": "Use a LEFT JOIN and GROUP BY product_name.",
160
+ "expected_sql": "SELECT i.product_name, COUNT(r.restock_id) AS restock_count FROM inventory i LEFT JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name;"
161
+ },
162
+ {
163
+ "id": "q21",
164
+ "title": "Most expensive restocked product",
165
+ "difficulty": "Advanced",
166
+ "description": "Find the product with the highest price that has been restocked.",
167
+ "hint": "Join inventory and restocks, use ORDER BY and LIMIT.",
168
+ "expected_sql": "SELECT i.product_name, i.price FROM inventory i JOIN restocks r ON i.product_id = r.product_id ORDER BY i.price DESC LIMIT 1;"
169
+ },
170
+ {
171
+ "id": "q22",
172
+ "title": "Total restock value per supplier",
173
+ "difficulty": "Advanced",
174
+ "description": "Calculate the total value of restocks (price * quantity) for each supplier.",
175
+ "hint": "Join tables, multiply price by quantity, and GROUP BY supplier.",
176
+ "expected_sql": "SELECT r.supplier, SUM(i.price * r.quantity) AS total_restock_value FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY r.supplier;"
177
+ },
178
+ {
179
+ "id": "q23",
180
+ "title": "Latest restock per product",
181
+ "difficulty": "Advanced",
182
+ "description": "Show the most recent restock date for each product that has been restocked.",
183
+ "hint": "Use GROUP BY on product_name and MAX on restock_date.",
184
+ "expected_sql": "SELECT i.product_name, MAX(r.restock_date) AS latest_restock FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name;"
185
+ },
186
+ {
187
+ "id": "q24",
188
+ "title": "Suppliers restocking all electronics",
189
+ "difficulty": "Advanced",
190
+ "description": "List suppliers who have restocked every product in the Electronics category.",
191
+ "hint": "Count distinct Electronics products per supplier and compare with total Electronics products.",
192
+ "expected_sql": "SELECT r.supplier FROM restocks r JOIN inventory i ON r.product_id = i.product_id WHERE i.category = 'Electronics' GROUP BY r.supplier HAVING COUNT(DISTINCT r.product_id) = (SELECT COUNT(*) FROM inventory WHERE category = 'Electronics');"
193
+ },
194
+ {
195
+ "id": "q25",
196
+ "title": "Restock trend by month",
197
+ "difficulty": "Advanced",
198
+ "description": "Show the total quantity restocked per month in 2023.",
199
+ "hint": "Use STRFTIME to extract the month and GROUP BY.",
200
+ "expected_sql": "SELECT STRFTIME('%Y-%m', restock_date) AS month, SUM(quantity) AS total_quantity FROM restocks WHERE restock_date LIKE '2023%' GROUP BY month;"
201
+ },
202
+ {
203
+ "id": "q26",
204
+ "title": "Products with high restock value",
205
+ "difficulty": "Advanced",
206
+ "description": "Find products where the total restock value (price * quantity) exceeds 5000.",
207
+ "hint": "Join tables, calculate value, and use HAVING clause.",
208
+ "expected_sql": "SELECT i.product_name, SUM(i.price * r.quantity) AS total_restock_value FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name HAVING SUM(i.price * r.quantity) > 5000;"
209
+ },
210
+ {
211
+ "id": "q27",
212
+ "title": "Supplier product diversity",
213
+ "difficulty": "Advanced",
214
+ "description": "Count the number of unique products each supplier has restocked.",
215
+ "hint": "Use COUNT and DISTINCT on product_id, GROUP BY supplier.",
216
+ "expected_sql": "SELECT r.supplier, COUNT(DISTINCT r.product_id) AS unique_products FROM restocks r GROUP BY r.supplier;"
217
+ },
218
+ {
219
+ "id": "q28",
220
+ "title": "Overstocked products",
221
+ "difficulty": "Advanced",
222
+ "description": "Identify products where the total restocked quantity exceeds the current stock by more than 20.",
223
+ "hint": "Join tables, compare SUM(quantity) with stock, and use HAVING.",
224
+ "expected_sql": "SELECT i.product_name, i.stock, SUM(r.quantity) AS total_restocked FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY i.product_name, i.stock HAVING SUM(r.quantity) > i.stock + 20;"
225
+ },
226
+ {
227
+ "id": "q29",
228
+ "title": "Restock value by category",
229
+ "difficulty": "Advanced",
230
+ "description": "Calculate the total restock value (price * quantity) for each product category.",
231
+ "hint": "Join tables, multiply price by quantity, and GROUP BY category.",
232
+ "expected_sql": "SELECT i.category, SUM(i.price * r.quantity) AS total_restock_value FROM inventory i JOIN restocks r ON i.product_id = r.product_id GROUP BY i.category;"
233
+ },
234
+ {
235
+ "id": "q30",
236
+ "title": "Earliest and latest restock per supplier",
237
+ "difficulty": "Advanced",
238
+ "description": "Show the earliest and latest restock dates for each supplier.",
239
+ "hint": "Use MIN and MAX on restock_date, GROUP BY supplier.",
240
+ "expected_sql": "SELECT supplier, MIN(restock_date) AS earliest_restock, MAX(restock_date) AS latest_restock FROM restocks GROUP BY supplier;"
241
+ }
242
+ ]
app/schemas.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+ class RunQueryRequest(BaseModel):
4
+ query: str
5
+
6
+ class ValidateQueryRequest(BaseModel):
7
+ user_query: str
8
+ expected_query: str
app/schemas/banking.sql ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- Customers table
2
+ CREATE TABLE customers (
3
+ customer_id INTEGER PRIMARY KEY,
4
+ name TEXT NOT NULL,
5
+ age INTEGER,
6
+ city TEXT,
7
+ email TEXT
8
+ );
9
+
10
+ -- Accounts table
11
+ CREATE TABLE accounts (
12
+ account_id INTEGER PRIMARY KEY,
13
+ customer_id INTEGER,
14
+ account_type TEXT CHECK(account_type IN ('Savings', 'Current', 'Loan')),
15
+ balance REAL,
16
+ opened_on DATE,
17
+ FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
18
+ );
19
+
20
+ -- Sample data: customers
21
+ INSERT INTO customers (customer_id, name, age, city, email) VALUES (1, 'John Doe', 30, 'New York', 'john.doe@example.com');
22
+ INSERT INTO customers (customer_id, name, age, city, email) VALUES (2, 'Jane Smith', 25, 'Los Angeles', 'jane.smith@example.com');
23
+ INSERT INTO customers (customer_id, name, age, city, email) VALUES (3, 'Michael Johnson', 45, 'Chicago', 'michael.j@example.com');
24
+ INSERT INTO customers (customer_id, name, age, city, email) VALUES (4, 'Emily Davis', 28, 'Houston', 'emily.davis@example.com');
25
+ INSERT INTO customers (customer_id, name, age, city, email) VALUES (5, 'David Wilson', 35, 'Phoenix', 'david.w@example.com');
26
+
27
+ -- Sample data: accounts
28
+ INSERT INTO accounts (account_id, customer_id, account_type, balance, opened_on) VALUES (1, 1, 'Savings', 1000.50, '2023-01-15');
29
+ INSERT INTO accounts (account_id, customer_id, account_type, balance, opened_on) VALUES (2, 2, 'Savings', 500.75, '2022-11-20');
30
+ INSERT INTO accounts (account_id, customer_id, account_type, balance, opened_on) VALUES (3, 3, 'Current', 2500.00, '2023-03-10');
31
+ INSERT INTO accounts (account_id, customer_id, account_type, balance, opened_on) VALUES (4, 4, 'Loan', -10000.00, '2021-06-01');
32
+ INSERT INTO accounts (account_id, customer_id, account_type, balance, opened_on) VALUES (5, 5, 'Savings', 350.00, '2024-04-25');
33
+ INSERT INTO accounts (account_id, customer_id, account_type, balance, opened_on) VALUES (6, 1, 'Loan', -5000.00, '2022-05-10');
34
+ INSERT INTO accounts (account_id, customer_id, account_type, balance, opened_on) VALUES (7, 2, 'Current', 700.00, '2023-08-12');
app/schemas/college.sql ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- Students table
2
+ CREATE TABLE students (
3
+ student_id INTEGER PRIMARY KEY,
4
+ name TEXT NOT NULL,
5
+ department TEXT,
6
+ year INTEGER,
7
+ email TEXT
8
+ );
9
+
10
+ -- Courses table
11
+ CREATE TABLE courses (
12
+ course_id INTEGER PRIMARY KEY,
13
+ name TEXT NOT NULL,
14
+ department TEXT,
15
+ credits INTEGER,
16
+ instructor TEXT
17
+ );
18
+
19
+ -- Enrollments table
20
+ CREATE TABLE enrollments (
21
+ enrollment_id INTEGER PRIMARY KEY,
22
+ student_id INTEGER,
23
+ course_id INTEGER,
24
+ semester TEXT,
25
+ grade TEXT,
26
+ FOREIGN KEY (student_id) REFERENCES students(student_id),
27
+ FOREIGN KEY (course_id) REFERENCES courses(course_id)
28
+ );
29
+
30
+
31
+ INSERT INTO students (student_id, name, department, year, email) VALUES
32
+ (1, 'Rahul Sharma', 'CSE', 2, 'rahul.s@univ.edu'),
33
+ (2, 'Anjali Mehta', 'ECE', 3, 'anjali.m@univ.edu'),
34
+ (3, 'Soham Verma', 'ME', 2, 'soham.v@univ.edu'),
35
+ (4, 'Pooja Singh', 'CSE', 1, 'pooja.s@univ.edu'),
36
+ (5, 'Karan Patel', 'EEE', 4, 'karan.p@univ.edu');
37
+
38
+ INSERT INTO courses (course_id, name, department, credits, instructor) VALUES
39
+ (101, 'Data Structures', 'CSE', 4, 'Dr. Anil Kapoor'),
40
+ (102, 'Digital Electronics', 'ECE', 3, 'Dr. Priya Nair'),
41
+ (103, 'Thermodynamics', 'ME', 4, 'Dr. Ramesh Rao'),
42
+ (104, 'Operating Systems', 'CSE', 4, 'Dr. Neha Malhotra'),
43
+ (105, 'Power Systems', 'EEE', 3, 'Dr. Amit Kumar');
44
+
45
+ INSERT INTO enrollments (enrollment_id, student_id, course_id, semester, grade) VALUES
46
+ (1, 1, 101, 'Spring 2023', 'A'),
47
+ (2, 1, 104, 'Spring 2023', 'B+'),
48
+ (3, 2, 102, 'Spring 2023', 'A-'),
49
+ (4, 3, 103, 'Spring 2023', 'B'),
50
+ (5, 4, 101, 'Spring 2023', 'A'),
51
+ (6, 4, 104, 'Spring 2023', 'A-'),
52
+ (7, 5, 105, 'Spring 2023', 'B+');
app/schemas/e_commerce.sql ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- Sales Table
2
+ CREATE TABLE sales (
3
+ id INTEGER PRIMARY KEY,
4
+ product_name TEXT NOT NULL,
5
+ category TEXT,
6
+ sale_amount REAL NOT NULL,
7
+ sale_date DATE
8
+ );
9
+
10
+ -- Orders Table
11
+ CREATE TABLE orders (
12
+ order_id INTEGER PRIMARY KEY,
13
+ customer_id INTEGER,
14
+ product_id INTEGER,
15
+ quantity INTEGER DEFAULT 1,
16
+ order_date DATE,
17
+ FOREIGN KEY (product_id) REFERENCES sales(id)
18
+ );
19
+
20
+
21
+ INSERT INTO sales (id, product_name, category, sale_amount, sale_date) VALUES
22
+ (1, 'Laptop', 'Electronics', 1200.0, '2023-01-01'),
23
+ (2, 'Phone', 'Electronics', 800.0, '2023-01-02'),
24
+ (3, 'Laptop', 'Electronics', 1500.0, '2023-01-03'),
25
+ (4, 'Tablet', 'Electronics', 600.0, '2023-01-04'),
26
+ (5, 'Phone', 'Electronics', 900.0, '2023-01-05'),
27
+ (6, 'Monitor', 'Accessories', 400.0, '2023-01-06'),
28
+ (7, 'Keyboard', 'Accessories', 100.0, '2023-01-07'),
29
+ (8, 'Mouse', 'Accessories', 50.0, '2023-01-08'),
30
+ (9, 'Charger', 'Accessories', 70.0, '2023-01-09'),
31
+ (10, 'Headphones', 'Accessories', 200.0, '2023-01-10');
32
+
33
+
34
+ INSERT INTO orders (order_id, customer_id, product_id, quantity, order_date) VALUES
35
+ (1, 2001, 1, 1, '2023-01-01'),
36
+ (2, 2002, 2, 2, '2023-01-02'),
37
+ (3, 2001, 3, 1, '2023-01-03'),
38
+ (4, 2003, 4, 1, '2023-01-04'),
39
+ (5, 2001, 5, 1, '2023-01-05'),
40
+ (6, 2004, 6, 1, '2023-01-06'),
41
+ (7, 2002, 7, 3, '2023-01-07'),
42
+ (8, 2003, 8, 2, '2023-01-08'),
43
+ (9, 2005, 9, 1, '2023-01-09'),
44
+ (10, 2004, 10, 1, '2023-01-10');
app/schemas/hospital.sql ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- Patients Table
2
+ CREATE TABLE patients (
3
+ patient_id INTEGER PRIMARY KEY,
4
+ name TEXT NOT NULL,
5
+ gender TEXT CHECK (gender IN ('Male', 'Female', 'Other')),
6
+ dob DATE,
7
+ contact TEXT
8
+ );
9
+
10
+ -- Doctors Table
11
+ CREATE TABLE doctors (
12
+ doctor_id INTEGER PRIMARY KEY,
13
+ name TEXT NOT NULL,
14
+ specialty TEXT,
15
+ contact TEXT
16
+ );
17
+
18
+ -- Appointments Table
19
+ CREATE TABLE appointments (
20
+ appointment_id INTEGER PRIMARY KEY,
21
+ patient_id INTEGER,
22
+ doctor_id INTEGER,
23
+ appointment_date DATE,
24
+ diagnosis TEXT,
25
+ FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
26
+ FOREIGN KEY (doctor_id) REFERENCES doctors(doctor_id)
27
+ );
28
+
29
+
30
+ INSERT INTO patients VALUES
31
+ (1, 'Aarushi Verma', 'Female', '1995-08-20', '9999888877'),
32
+ (2, 'Rohan Mehra', 'Male', '1990-03-15', '9999123456'),
33
+ (3, 'Simran Kaur', 'Female', '1985-11-05', '8888445566'),
34
+ (4, 'Arjun Kapoor', 'Male', '2000-01-25', '7777333222'),
35
+ (5, 'Priya Das', 'Female', '1998-07-14', '9999000011');
36
+
37
+ INSERT INTO doctors VALUES
38
+ (1, 'Dr. Rakesh Sharma', 'Cardiologist', '8888777766'),
39
+ (2, 'Dr. Meena Iyer', 'Dermatologist', '8888123456'),
40
+ (3, 'Dr. Alok Mishra', 'Neurologist', '7777888899'),
41
+ (4, 'Dr. Nandini Rao', 'Pediatrician', '9999333311');
42
+
43
+ INSERT INTO appointments VALUES
44
+ (1, 1, 1, '2023-06-20', 'Routine Checkup'),
45
+ (2, 2, 2, '2023-06-25', 'Skin Rash'),
46
+ (3, 3, 1, '2023-07-02', 'Chest Pain'),
47
+ (4, 4, 3, '2023-07-10', 'Headache & Dizziness'),
48
+ (5, 5, 4, '2023-07-15', 'Child Vaccination'),
49
+ (6, 1, 3, '2023-08-01', 'Memory Loss'),
50
+ (7, 2, 1, '2023-08-05', 'High Blood Pressure');
app/schemas/inventory.sql ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- Inventory Table
2
+ CREATE TABLE inventory (
3
+ product_id INTEGER PRIMARY KEY,
4
+ product_name TEXT NOT NULL,
5
+ category TEXT NOT NULL,
6
+ price REAL NOT NULL,
7
+ stock INTEGER NOT NULL CHECK (stock >= 0)
8
+ );
9
+
10
+ -- Restocks Table
11
+ CREATE TABLE restocks (
12
+ restock_id INTEGER PRIMARY KEY,
13
+ product_id INTEGER,
14
+ restock_date DATE NOT NULL,
15
+ quantity INTEGER NOT NULL CHECK (quantity > 0),
16
+ supplier TEXT,
17
+ FOREIGN KEY (product_id) REFERENCES inventory(product_id)
18
+ );
19
+
20
+
21
+ INSERT INTO inventory (product_id, product_name, category, price, stock) VALUES
22
+ (1, 'Laptop', 'Electronics', 1200.00, 10),
23
+ (2, 'Smartphone', 'Electronics', 800.00, 15),
24
+ (3, 'Printer', 'Peripherals', 300.00, 5),
25
+ (4, 'Desk Chair', 'Furniture', 150.00, 20),
26
+ (5, 'Monitor', 'Peripherals', 400.00, 8),
27
+ (6, 'Mouse', 'Accessories', 40.00, 50),
28
+ (7, 'Keyboard', 'Accessories', 60.00, 30),
29
+ (8, 'Router', 'Networking', 90.00, 12),
30
+ (9, 'External HDD', 'Storage', 110.00, 7),
31
+ (10, 'USB-C Hub', 'Accessories', 25.00, 40);
32
+
33
+ INSERT INTO restocks (restock_id, product_id, restock_date, quantity, supplier) VALUES
34
+ (1, 1, '2023-01-01', 5, 'TechSupplier Inc.'),
35
+ (2, 2, '2023-01-05', 10, 'MobileMart Ltd.'),
36
+ (3, 3, '2023-01-10', 3, 'PrintPerfect'),
37
+ (4, 4, '2023-01-12', 15, 'OfficeFurnish Co.'),
38
+ (5, 5, '2023-01-15', 6, 'PeripheralWorks'),
39
+ (6, 6, '2023-01-17', 25, 'AccsGlobal'),
40
+ (7, 7, '2023-01-18', 10, 'AccsGlobal'),
41
+ (8, 8, '2023-01-20', 8, 'NetConnect'),
42
+ (9, 9, '2023-01-21', 5, 'StorageHouse'),
43
+ (10, 10, '2023-01-22', 20, 'TechSupplier Inc.'),
44
+ (11, 1, '2023-02-01', 3, 'TechSupplier Inc.'),
45
+ (12, 2, '2023-02-03', 7, 'MobileMart Ltd.');
46
+
app/schemas/library.sql ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- Books table
2
+ CREATE TABLE books (
3
+ book_id INTEGER PRIMARY KEY,
4
+ title TEXT NOT NULL,
5
+ author TEXT NOT NULL,
6
+ genre TEXT,
7
+ year INTEGER,
8
+ available_copies INTEGER DEFAULT 1
9
+ );
10
+
11
+ -- Users table (added for more realistic modeling)
12
+ CREATE TABLE users (
13
+ user_id INTEGER PRIMARY KEY,
14
+ name TEXT NOT NULL,
15
+ email TEXT,
16
+ membership_date DATE
17
+ );
18
+
19
+ -- Loans table
20
+ CREATE TABLE loans (
21
+ loan_id INTEGER PRIMARY KEY,
22
+ book_id INTEGER,
23
+ user_id INTEGER,
24
+ issue_date DATE,
25
+ due_date DATE,
26
+ return_date DATE,
27
+ FOREIGN KEY (book_id) REFERENCES books(book_id),
28
+ FOREIGN KEY (user_id) REFERENCES users(user_id)
29
+ );
30
+
31
+ INSERT INTO books (book_id, title, author, genre, year, available_copies) VALUES
32
+ (1, 'The Great Gatsby', 'F. Scott Fitzgerald', 'Fiction', 1925, 2),
33
+ (2, '1984', 'George Orwell', 'Dystopian', 1949, 3),
34
+ (3, 'Dune', 'Frank Herbert', 'Science Fiction', 1965, 4),
35
+ (4, 'Project Hail Mary', 'Andy Weir', 'Science Fiction', 2021, 1),
36
+ (5, 'Klara and the Sun', 'Kazuo Ishiguro', 'Science Fiction', 2021, 2),
37
+ (6, 'The Martian', 'Andy Weir', 'Science Fiction', 2011, 2),
38
+ (7, 'Animal Farm', 'George Orwell', 'Political Satire', 1945, 3);
39
+
40
+
41
+ INSERT INTO users (user_id, name, email, membership_date) VALUES
42
+ (101, 'Alice Johnson', 'alice.j@example.com', '2020-06-15'),
43
+ (102, 'Bob Smith', 'bob.smith@example.com', '2019-08-22'),
44
+ (103, 'Charlie Rose', 'charlie.r@example.com', '2021-01-10'),
45
+ (104, 'Diana Prince', 'diana.p@example.com', '2022-05-04'),
46
+ (105, 'Ethan Hunt', 'ethan.h@example.com', '2023-02-01');
47
+
48
+
49
+ INSERT INTO loans (loan_id, book_id, user_id, issue_date, due_date, return_date) VALUES
50
+ (1, 1, 101, '2022-11-30', '2022-12-15', '2022-12-12'),
51
+ (2, 2, 102, '2022-12-20', '2023-01-10', NULL),
52
+ (3, 3, 103, '2022-11-01', '2022-11-30', '2022-11-28'),
53
+ (4, 4, 104, '2023-01-10', '2023-02-01', NULL),
54
+ (5, 5, 105, '2023-02-10', '2023-03-01', NULL),
55
+ (6, 2, 101, '2023-01-15', '2023-02-05', '2023-02-04');
app/schemas/restaurant.sql ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- Menu Items Table
2
+ CREATE TABLE menu_items (
3
+ item_id INTEGER PRIMARY KEY,
4
+ name TEXT NOT NULL,
5
+ category TEXT,
6
+ price REAL NOT NULL
7
+ );
8
+
9
+ -- Customers Table
10
+ CREATE TABLE customers (
11
+ customer_id INTEGER PRIMARY KEY,
12
+ name TEXT NOT NULL,
13
+ contact TEXT,
14
+ email TEXT
15
+ );
16
+
17
+ -- Orders Table
18
+ CREATE TABLE orders (
19
+ order_id INTEGER PRIMARY KEY,
20
+ customer_id INTEGER,
21
+ order_date DATE,
22
+ total_amount REAL,
23
+ FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
24
+ );
25
+
26
+ -- Order Items Table
27
+ CREATE TABLE order_items (
28
+ order_item_id INTEGER PRIMARY KEY,
29
+ order_id INTEGER,
30
+ item_id INTEGER,
31
+ quantity INTEGER,
32
+ FOREIGN KEY (order_id) REFERENCES orders(order_id),
33
+ FOREIGN KEY (item_id) REFERENCES menu_items(item_id)
34
+ );
35
+
36
+
37
+ INSERT INTO menu_items VALUES
38
+ (1, 'Margherita Pizza', 'Main Course', 250.0),
39
+ (2, 'Paneer Tikka', 'Appetizer', 180.0),
40
+ (3, 'Chocolate Brownie', 'Dessert', 120.0),
41
+ (4, 'Cold Coffee', 'Beverage', 90.0),
42
+ (5, 'Veg Burger', 'Main Course', 150.0),
43
+ (6, 'French Fries', 'Appetizer', 100.0),
44
+ (7, 'Lemonade', 'Beverage', 60.0);
45
+
46
+ INSERT INTO customers VALUES
47
+ (1, 'Aman Gupta', '9876543210', 'aman.g@example.com'),
48
+ (2, 'Neha Sharma', '9876501234', 'neha.s@example.com'),
49
+ (3, 'Rohit Mehra', '9876567890', 'rohit.m@example.com');
50
+
51
+ INSERT INTO orders VALUES
52
+ (1, 1, '2023-06-10', 500.0),
53
+ (2, 2, '2023-06-11', 360.0),
54
+ (3, 1, '2023-06-15', 420.0),
55
+ (4, 3, '2023-06-17', 270.0);
56
+
57
+ INSERT INTO order_items VALUES
58
+ (1, 1, 1, 2), -- 2 Margherita Pizzas
59
+ (2, 2, 2, 1), -- 1 Paneer Tikka
60
+ (3, 2, 4, 2), -- 2 Cold Coffees
61
+ (4, 3, 5, 1), -- 1 Veg Burger
62
+ (5, 3, 3, 2), -- 2 Brownies
63
+ (6, 3, 4, 1), -- 1 Cold Coffee
64
+ (7, 4, 6, 2), -- 2 French Fries
65
+ (8, 4, 7, 1); -- 1 Lemonade
app/schemas/retail.sql ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- Product inventory table
2
+ CREATE TABLE inventory (
3
+ product_id INTEGER PRIMARY KEY,
4
+ product_name TEXT NOT NULL,
5
+ category TEXT,
6
+ price REAL NOT NULL,
7
+ stock INTEGER NOT NULL,
8
+ reorder_level INTEGER DEFAULT 5
9
+ );
10
+
11
+ -- Restocks table
12
+ CREATE TABLE restocks (
13
+ restock_id INTEGER PRIMARY KEY,
14
+ product_id INTEGER,
15
+ restock_date DATE,
16
+ quantity INTEGER NOT NULL,
17
+ supplier TEXT,
18
+ FOREIGN KEY (product_id) REFERENCES inventory(product_id)
19
+ );
20
+
21
+ INSERT INTO inventory (product_id, product_name, category, price, stock, reorder_level) VALUES
22
+ (1, 'Laptop', 'Electronics', 1200.0, 5, 3),
23
+ (2, 'Phone', 'Electronics', 800.0, 20, 10),
24
+ (3, 'Tablet', 'Electronics', 600.0, 8, 5),
25
+ (4, 'Monitor', 'Accessories', 300.0, 15, 7),
26
+ (5, 'Keyboard', 'Accessories', 100.0, 30, 10),
27
+ (6, 'Mouse', 'Accessories', 50.0, 50, 20),
28
+ (7, 'Headphones', 'Accessories', 150.0, 25, 10),
29
+ (8, 'Webcam', 'Accessories', 250.0, 4, 5),
30
+ (9, 'Charger', 'Electronics', 60.0, 12, 8);
31
+
32
+ INSERT INTO restocks (restock_id, product_id, restock_date, quantity, supplier) VALUES
33
+ (1, 1, '2023-01-01', 10, 'TechWorld Supplies'),
34
+ (2, 2, '2023-01-02', 20, 'GadgetMart Inc.'),
35
+ (3, 3, '2022-12-15', 15, 'ElectroParts Ltd.'),
36
+ (4, 4, '2023-01-03', 30, 'DisplayX Pvt. Ltd.'),
37
+ (5, 5, '2023-01-10', 40, 'KeyTech Solutions'),
38
+ (6, 6, '2023-01-20', 50, 'Mousey Traders'),
39
+ (7, 7, '2023-02-01', 25, 'AudioGear Distributors'),
40
+ (8, 1, '2023-03-01', 5, 'TechWorld Supplies'),
41
+ (9, 8, '2023-03-15', 10, 'VisionTech Systems'),
42
+ (10, 9, '2023-04-01', 12, 'ElectroParts Ltd.');
app/static/ace.js ADDED
The diff for this file is too large to render. See raw diff
 
app/static/favicon.png ADDED
app/static/index.html ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>SQL Practice Platform</title>
7
+ <link rel="icon" href="/static/favicon.png" type="image/x-icon" />
8
+ <script src="https://cdn.tailwindcss.com/3.3.3"></script>
9
+ <script
10
+ src="/static/ace.js"
11
+ id="ace-script"
12
+ onerror="this.nextElementSibling.src='https://cdnjs.cloudflare.com/ajax/libs/ace-builds/1.35.0/ace.js';"
13
+ ></script>
14
+ <script src="" id="ace-fallback"></script>
15
+ <link href="/static/style.css" rel="stylesheet" />
16
+ <style>
17
+ #schemaInfo {
18
+ max-height: calc(100vh - 200px);
19
+ overflow-y: auto;
20
+ }
21
+ </style>
22
+ </head>
23
+ <body class="bg-gray-100 min-h-screen">
24
+ <div class="max-w-full mx-auto p-4">
25
+ <h1 class="text-3xl font-bold mb-6 text-center text-blue-600">
26
+ SQL Practice Platform
27
+ </h1>
28
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 h-[calc(100vh-100px)]">
29
+ <div class="bg-white p-4 rounded-lg shadow-md">
30
+ <h2 class="text-xl font-semibold mb-4 text-green-600">Database</h2>
31
+ <select
32
+ id="domainSelect"
33
+ class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4"
34
+ >
35
+ <option value="">Select Database</option>
36
+ </select>
37
+ <button
38
+ id="loadSchemaBtn"
39
+ class="w-full bg-blue-600 text-white p-2 rounded-md hover:bg-blue-700 disabled:bg-gray-400 mb-4"
40
+ disabled
41
+ >
42
+ Loading...
43
+ </button>
44
+ <button
45
+ id="showSchemaBtn"
46
+ class="w-full bg-gray-500 text-white p-2 rounded-md hover:bg-gray-600 mb-4 hidden"
47
+ >
48
+ Show Schema
49
+ </button>
50
+ <button
51
+ id="showTableBtn"
52
+ class="w-full bg-gray-500 text-white p-2 rounded-md hover:bg-gray-600 mb-4 hidden"
53
+ >
54
+ Show Table
55
+ </button>
56
+ <div
57
+ id="schemaInfo"
58
+ class="text-sm overflow-auto max-h-60 hidden"
59
+ ></div>
60
+ </div>
61
+ <div class="bg-white p-4 rounded-lg shadow-md flex flex-col">
62
+ <h2 class="text-xl font-semibold mb-4 text-purple-600">SQL Editor</h2>
63
+ <div id="sqlEditor" class="w-full h-48 border rounded-md mb-4"></div>
64
+ <div class="flex space-x-4 mb-4">
65
+ <button
66
+ id="runQueryBtn"
67
+ class="bg-green-600 text-white p-2 rounded-md hover:bg-green-700 disabled:bg-gray-400"
68
+ >
69
+ Run
70
+ </button>
71
+ </div>
72
+ <h2 class="text-xl font-semibold mb-4 text-purple-600">Results</h2>
73
+ <div id="results" class="text-sm overflow-auto flex-grow"></div>
74
+ <div id="errorMessage" class="text-red-500 mt-2"></div>
75
+ </div>
76
+ <div class="bg-white p-4 rounded-lg shadow-md flex flex-col">
77
+ <h2 class="text-xl font-semibold mb-4 text-yellow-600">
78
+ Practice Question
79
+ </h2>
80
+ <select
81
+ id="difficultySelect"
82
+ class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4"
83
+ disabled
84
+ >
85
+ <option value="">Select Difficulty</option>
86
+ <option value="Beginner">Beginner</option>
87
+ <option value="Intermediate">Intermediate</option>
88
+ <option value="Advanced">Advanced</option>
89
+ </select>
90
+ <div id="questionDetails" class="text-sm mb-4"></div>
91
+ <div
92
+ class="flex justify-between mb-4"
93
+ style="display: none"
94
+ id="navButtons"
95
+ >
96
+ <button
97
+ id="prevBtn"
98
+ class="bg-gray-500 text-white p-2 rounded-md hover:bg-gray-600"
99
+ >
100
+ <<
101
+ </button>
102
+ <button
103
+ id="nextBtn"
104
+ class="bg-gray-500 text-white p-2 rounded-md hover:bg-gray-600"
105
+ >
106
+ >>
107
+ </button>
108
+ </div>
109
+ <button
110
+ id="hintBtn"
111
+ class="w-full bg-gray-500 text-white p-2 rounded-md hover:bg-gray-600 mb-4"
112
+ style="display: none"
113
+ >
114
+ Show Hint
115
+ </button>
116
+ <button
117
+ id="solutionBtn"
118
+ class="w-full bg-gray-400 text-white p-2 rounded-md hover:bg-gray-500 mb-4"
119
+ style="display: none"
120
+ >
121
+ Show Solution
122
+ </button>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ <script src="/static/script.js"></script>
127
+ </body>
128
+ </html>
app/static/script.js ADDED
@@ -0,0 +1,603 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let sessionId,
2
+ editor,
3
+ currentQuestions = [],
4
+ currentQuestionIndex = 0,
5
+ isSchemaVisible,
6
+ isTableVisible,
7
+ isHintVisible,
8
+ isSolutionVisible;
9
+
10
+ async function init() {
11
+ await new Promise((r) =>
12
+ document.readyState === "complete"
13
+ ? r()
14
+ : window.addEventListener("load", r)
15
+ );
16
+ const sqlEditor = document.getElementById("sqlEditor");
17
+ if (!sqlEditor) throw new Error("SQL Editor element not found");
18
+ await new Promise((r) => setTimeout(r, 100));
19
+ if (typeof ace === "undefined")
20
+ throw new Error("Ace Editor library not loaded");
21
+ editor = ace.edit("sqlEditor");
22
+ editor.setTheme("ace/theme/monokai");
23
+ editor.session.setMode("ace/mode/sql");
24
+ editor.setOptions({ enableBasicAutocompletion: true, fontSize: "12px" });
25
+ editor.setValue("SELECT * FROM customers;");
26
+ const response = await fetch("/api/session", { method: "POST" });
27
+ if (!response.ok) throw new Error("Failed to create session");
28
+ sessionId = (await response.json()).session_id;
29
+ const domainResponse = await fetch("/api/databases");
30
+ if (!domainResponse.ok) throw new Error("Failed to fetch databases");
31
+ const domains = (await domainResponse.json()).databases;
32
+ const domainSelect = document.getElementById("domainSelect");
33
+ domainSelect.innerHTML = '<option value="">Select Database</option>';
34
+ domains.forEach((domain) => {
35
+ const option = document.createElement("option");
36
+ option.value = domain;
37
+ option.textContent = domain.charAt(0).toUpperCase() + domain.slice(1);
38
+ domainSelect.appendChild(option);
39
+ });
40
+ document
41
+ .getElementById("loadSchemaBtn")
42
+ .addEventListener("click", loadDomain);
43
+ document
44
+ .getElementById("showSchemaBtn")
45
+ .addEventListener("click", showSchema);
46
+ document.getElementById("showTableBtn").addEventListener("click", showTable);
47
+ document
48
+ .getElementById("difficultySelect")
49
+ .addEventListener("change", loadQuestions);
50
+ document.getElementById("hintBtn").addEventListener("click", toggleHint);
51
+ document
52
+ .getElementById("solutionBtn")
53
+ .addEventListener("click", toggleSolution);
54
+ document.getElementById("prevBtn").addEventListener("click", prevQuestion);
55
+ document.getElementById("nextBtn").addEventListener("click", nextQuestion);
56
+ document.getElementById("runQueryBtn").addEventListener("click", runQuery);
57
+ }
58
+
59
+ async function loadDomain() {
60
+ const domain = document.getElementById("domainSelect").value;
61
+ if (!domain) {
62
+ showError("Please select a database.");
63
+ return;
64
+ }
65
+ const loadBtn = document.getElementById("loadSchemaBtn");
66
+ loadBtn.disabled = true;
67
+ loadBtn.textContent = "Loading...";
68
+ try {
69
+ const response = await fetch(`/api/load-schema/${domain}`, {
70
+ method: "POST",
71
+ headers: { "session-id": sessionId },
72
+ });
73
+ if (!response.ok)
74
+ throw new Error(
75
+ (await response.json()).detail || "Failed to load database"
76
+ );
77
+ await response.json();
78
+ document.getElementById("difficultySelect").disabled = false;
79
+ document.getElementById("showSchemaBtn").classList.remove("hidden");
80
+ document.getElementById("showTableBtn").classList.remove("hidden");
81
+ document.getElementById("schemaInfo").classList.add("hidden");
82
+ document.getElementById("questionDetails").innerHTML = "";
83
+ document.getElementById("hintBtn").style.display = "block";
84
+ document.getElementById("solutionBtn").style.display = "block";
85
+ currentQuestions = [];
86
+ currentQuestionIndex = 0;
87
+ } catch (e) {
88
+ showError(`Failed to load database: ${e.message}.`);
89
+ } finally {
90
+ loadBtn.disabled = false;
91
+ loadBtn.textContent = "Load Database";
92
+ }
93
+ }
94
+
95
+ async function showSchema() {
96
+ const domain = document.getElementById("domainSelect").value;
97
+ if (!domain) return;
98
+ const schemaInfo = document.getElementById("schemaInfo");
99
+ if (isTableVisible) {
100
+ isTableVisible = false;
101
+ schemaInfo.classList.add("hidden");
102
+ }
103
+ schemaInfo.classList.toggle("hidden");
104
+ isSchemaVisible = !schemaInfo.classList.contains("hidden");
105
+ if (isSchemaVisible) {
106
+ const schemaResponse = await fetch(`/api/schema/${domain}`, {
107
+ headers: { "session-id": sessionId },
108
+ });
109
+ if (!schemaResponse.ok)
110
+ throw new Error(
111
+ (await schemaResponse.json()).detail || "Failed to load schema"
112
+ );
113
+ const schema = (await schemaResponse.json()).schema;
114
+ let schemaHtml = '<div class="grid grid-cols-1 md:grid-cols-2 gap-4">';
115
+ for (const [table, columns] of Object.entries(schema)) {
116
+ schemaHtml += `<div class="bg-gray-100 p-2 rounded"><h3 class="font-semibold">${table}</h3><ul class="list-disc ml-4">`;
117
+ columns.forEach(
118
+ (col) => (schemaHtml += `<li>${col.name} (${col.type})</li>`)
119
+ );
120
+ schemaHtml += "</ul></div>";
121
+ }
122
+ schemaInfo.innerHTML = schemaHtml;
123
+ }
124
+ }
125
+
126
+ async function showTable() {
127
+ const domain = document.getElementById("domainSelect").value;
128
+ if (!domain) return;
129
+ const schemaInfo = document.getElementById("schemaInfo");
130
+ if (isSchemaVisible) {
131
+ isSchemaVisible = false;
132
+ schemaInfo.classList.add("hidden");
133
+ }
134
+ schemaInfo.classList.toggle("hidden");
135
+ isTableVisible = !schemaInfo.classList.contains("hidden");
136
+ if (isTableVisible) {
137
+ const sampleResponse = await fetch(`/api/sample-data/${domain}`, {
138
+ headers: { "session-id": sessionId },
139
+ });
140
+ if (!sampleResponse.ok)
141
+ throw new Error(
142
+ (await sampleResponse.json()).detail || "Failed to load sample data"
143
+ );
144
+ const sampleData = (await sampleResponse.json()).sample_data;
145
+ let tableHtml = '<div class="grid grid-cols-1 gap-4">';
146
+ for (const [table, data] of Object.entries(sampleData)) {
147
+ tableHtml += `<div class="bg-gray-50 p-2 rounded"><h4 class="font-semibold">${table}</h4>`;
148
+ tableHtml += '<table class="w-full mt-2"><tr>';
149
+ data.columns.forEach(
150
+ (col) => (tableHtml += `<th class="border p-1">${col}</th>`)
151
+ );
152
+ tableHtml += "</tr>";
153
+ data.rows.forEach((row) => {
154
+ tableHtml += "<tr>";
155
+ data.columns.forEach(
156
+ (col) =>
157
+ (tableHtml += `<td class="border p-1">${row[col] || "NULL"}</td>`)
158
+ );
159
+ tableHtml += "</tr>";
160
+ });
161
+ tableHtml += "</table></div>";
162
+ }
163
+ schemaInfo.innerHTML = tableHtml;
164
+ }
165
+ }
166
+
167
+ async function loadQuestions() {
168
+ const domain = document.getElementById("domainSelect").value;
169
+ const difficulty = document.getElementById("difficultySelect").value;
170
+ if (!domain) {
171
+ document.getElementById("difficultySelect").value = "";
172
+ alert("Please select and load a database first");
173
+ return;
174
+ }
175
+ if (!difficulty) {
176
+ document.getElementById("questionDetails").innerHTML = "";
177
+ document.getElementById("hintBtn").style.display = "none";
178
+ document.getElementById("solutionBtn").style.display = "none";
179
+ document.getElementById("navButtons").style.display = "none";
180
+ currentQuestions = [];
181
+ currentQuestionIndex = 0;
182
+ return;
183
+ }
184
+ const questionResponse = await fetch(
185
+ `/api/questions/${domain}?difficulty=${difficulty}`
186
+ );
187
+ if (!questionResponse.ok)
188
+ throw new Error(
189
+ (await questionResponse.json()).detail || "Failed to load questions"
190
+ );
191
+ currentQuestions = await questionResponse.json();
192
+ if (currentQuestions.length > 0) {
193
+ currentQuestionIndex = 0;
194
+ updateQuestionDisplay();
195
+ document.getElementById("hintBtn").style.display = "block";
196
+ document.getElementById("solutionBtn").style.display = "block";
197
+ document.getElementById("navButtons").style.display = "flex";
198
+ } else {
199
+ document.getElementById("questionDetails").innerHTML =
200
+ "<p>No questions available for this difficulty.</p>";
201
+ document.getElementById("hintBtn").style.display = "none";
202
+ document.getElementById("solutionBtn").style.display = "none";
203
+ document.getElementById("navButtons").style.display = "none";
204
+ currentQuestions = [];
205
+ currentQuestionIndex = 0;
206
+ }
207
+ }
208
+
209
+ function updateQuestionDisplay() {
210
+ const questionDetails = document.getElementById("questionDetails");
211
+ if (
212
+ currentQuestions.length &&
213
+ currentQuestionIndex >= 0 &&
214
+ currentQuestionIndex < currentQuestions.length
215
+ ) {
216
+ const question = currentQuestions[currentQuestionIndex];
217
+ questionDetails.innerHTML = `<p id="questionText"><strong>Practice Question:</strong> ${
218
+ question.description || "No question available."
219
+ }</p>`;
220
+ } else {
221
+ questionDetails.innerHTML =
222
+ '<p id="questionText">No questions available.</p>';
223
+ }
224
+ }
225
+
226
+ function prevQuestion() {
227
+ if (currentQuestions.length && currentQuestionIndex > 0) {
228
+ currentQuestionIndex--;
229
+ updateQuestionDisplay();
230
+ updateHintSolutionDisplay();
231
+ }
232
+ }
233
+ function nextQuestion() {
234
+ if (
235
+ currentQuestions.length &&
236
+ currentQuestionIndex < currentQuestions.length - 1
237
+ ) {
238
+ currentQuestionIndex++;
239
+ updateQuestionDisplay();
240
+ updateHintSolutionDisplay();
241
+ }
242
+ }
243
+
244
+ function updateHintSolutionDisplay() {
245
+ if (isHintVisible) toggleHint();
246
+ if (isSolutionVisible) toggleSolution();
247
+ }
248
+
249
+ function toggleHint() {
250
+ const question = currentQuestions[currentQuestionIndex];
251
+ const hintBtn = document.getElementById("hintBtn");
252
+ const questionDetails = document.getElementById("questionDetails");
253
+ if (isSolutionVisible) toggleSolution();
254
+ if (question && question.hint) {
255
+ if (hintBtn.textContent === "Show Hint") {
256
+ questionDetails.innerHTML += `<p><strong>Hint:</strong> ${question.hint}</p>`;
257
+ hintBtn.textContent = "Hide Hint";
258
+ isHintVisible = true;
259
+ } else {
260
+ questionDetails.innerHTML = questionDetails.innerHTML.replace(
261
+ `<p><strong>Hint:</strong> ${question.hint}</p>`,
262
+ ""
263
+ );
264
+ hintBtn.textContent = "Show Hint";
265
+ isHintVisible = false;
266
+ }
267
+ } else {
268
+ if (hintBtn.textContent === "Show Hint") {
269
+ questionDetails.innerHTML +=
270
+ '<p class="text-black">No hint available.</p>';
271
+ hintBtn.textContent = "Hide Hint";
272
+ isHintVisible = true;
273
+ } else {
274
+ questionDetails.innerHTML = questionDetails.innerHTML.replace(
275
+ '<p class="text-black">No hint available.</p>',
276
+ ""
277
+ );
278
+ hintBtn.textContent = "Show Hint";
279
+ isHintVisible = false;
280
+ }
281
+ }
282
+ }
283
+
284
+ function toggleSolution() {
285
+ const question = currentQuestions[currentQuestionIndex];
286
+ const solutionBtn = document.getElementById("solutionBtn");
287
+ const questionDetails = document.getElementById("questionDetails");
288
+ if (isHintVisible) toggleHint();
289
+ if (question && question.expected_sql) {
290
+ if (solutionBtn.textContent === "Show Solution") {
291
+ questionDetails.innerHTML += `<p><strong>Solution:</strong> <code>${question.expected_sql}</code></p>`;
292
+ solutionBtn.textContent = "Hide Solution";
293
+ isSolutionVisible = true;
294
+ } else {
295
+ questionDetails.innerHTML = questionDetails.innerHTML.replace(
296
+ `<p><strong>Solution:</strong> <code>${question.expected_sql}</code></p>`,
297
+ ""
298
+ );
299
+ solutionBtn.textContent = "Show Solution";
300
+ isSolutionVisible = false;
301
+ }
302
+ } else {
303
+ if (solutionBtn.textContent === "Show Solution") {
304
+ questionDetails.innerHTML +=
305
+ '<p class="text-black">No solution available.</p>';
306
+ solutionBtn.textContent = "Hide Solution";
307
+ isSolutionVisible = true;
308
+ } else {
309
+ questionDetails.innerHTML = questionDetails.innerHTML.replace(
310
+ '<p class="text-black">No solution available.</p>',
311
+ ""
312
+ );
313
+ solutionBtn.textContent = "Show Solution";
314
+ isSolutionVisible = false;
315
+ }
316
+ }
317
+ }
318
+
319
+ async function runQuery() {
320
+ const runBtn = document.getElementById("runQueryBtn");
321
+ const resultsDiv = document.getElementById("results");
322
+ let resultMessage =
323
+ document.getElementById("resultMessage") || document.createElement("span");
324
+ if (!resultMessage.id) {
325
+ resultMessage.id = "resultMessage";
326
+ runBtn.parentNode.appendChild(resultMessage);
327
+ }
328
+ runBtn.disabled = true;
329
+ runBtn.textContent = "Running...";
330
+ resultMessage.textContent = "";
331
+ try {
332
+ if (!editor) throw new Error("Editor not initialized. Refresh the page.");
333
+ let query = editor.getValue().trim().toLowerCase();
334
+ if (!query) throw new Error("Please enter a query.");
335
+ const domain = document.getElementById("domainSelect").value;
336
+ if (!domain) throw new Error("Please load a database first.");
337
+ const schemaResponse = await fetch(`/api/schema/${domain}`, {
338
+ headers: { "session-id": sessionId },
339
+ });
340
+ if (!schemaResponse.ok)
341
+ throw new Error("Failed to load schema for table validation");
342
+ const schema = (await schemaResponse.json()).schema;
343
+ const validTables = Object.keys(schema).map((t) => t.toLowerCase());
344
+ const tableNames = extractTableNames(query);
345
+ if (tableNames.some((table) => !validTables.includes(table)))
346
+ throw new Error(
347
+ `Invalid table name. Use only: ${validTables.join(", ")}`
348
+ );
349
+ const response = await fetch("/api/run-query", {
350
+ method: "POST",
351
+ headers: { "Content-Type": "application/json", "session-id": sessionId },
352
+ body: JSON.stringify({ query }),
353
+ });
354
+ if (!response.ok) {
355
+ const errorText = await response.text();
356
+ throw new Error(errorText || "Server error occurred.");
357
+ }
358
+ const result = await response.json();
359
+ if (result.columns) {
360
+ let html = `<table class="w-full border-collapse"><tr>${result.columns
361
+ .map((col) => `<th class="border p-2">${col}</th>`)
362
+ .join("")}</tr>`;
363
+ html += result.rows
364
+ .map(
365
+ (row) =>
366
+ `<tr>${result.columns
367
+ .map((col) => `<td class="border p-2">${row[col] || "NULL"}</td>`)
368
+ .join("")}</tr>`
369
+ )
370
+ .join("");
371
+ html += "</table>";
372
+ resultsDiv.innerHTML = html;
373
+ } else {
374
+ resultsDiv.innerHTML = "<p>No results</p>";
375
+ }
376
+ if (
377
+ currentQuestions.length &&
378
+ currentQuestionIndex >= 0 &&
379
+ currentQuestionIndex < currentQuestions.length
380
+ )
381
+ await validateQuery(query, result, resultsDiv);
382
+ else {
383
+ resultMessage.textContent = "Select a question first";
384
+ resultMessage.className = "text-red-500 ml-4";
385
+ }
386
+ } catch (e) {
387
+ resultsDiv.innerHTML = "";
388
+ resultMessage.textContent = e.message.includes("Internal Server Error")
389
+ ? "Server error: Please check the query or try again later."
390
+ : e.message;
391
+ resultMessage.className = "text-red-500 ml-4";
392
+ } finally {
393
+ runBtn.disabled = false;
394
+ runBtn.textContent = "Run";
395
+ }
396
+ }
397
+
398
+ function extractTableNames(query) {
399
+ const tables = new Set();
400
+ const tokens = query.replace(/(\s+)/g, " ").split(" ");
401
+ let inSubquery = false,
402
+ inOpenQuery = false,
403
+ inValues = false;
404
+
405
+ for (let i = 0; i < tokens.length; i++) {
406
+ const token = tokens[i].toLowerCase();
407
+ if (token === "(" && !inSubquery && !inValues) {
408
+ if (i > 0 && tokens[i - 1].toLowerCase() === "values") inValues = true;
409
+ else inSubquery = true;
410
+ }
411
+ if (token === ")" && (inSubquery || inValues)) {
412
+ if (
413
+ inValues &&
414
+ i + 1 < tokens.length &&
415
+ tokens[i + 1].toLowerCase() === "as"
416
+ )
417
+ inValues = false;
418
+ else if (inSubquery) inSubquery = false;
419
+ }
420
+ if (token === "openquery" && i + 1 < tokens.length && tokens[i + 1] === "(")
421
+ inOpenQuery = true;
422
+ if (token === ")" && inOpenQuery) inOpenQuery = false;
423
+ if (inOpenQuery) continue;
424
+
425
+ if (
426
+ [
427
+ "from",
428
+ "join",
429
+ "update",
430
+ "delete",
431
+ "insert",
432
+ "into",
433
+ "using",
434
+ "apply",
435
+ "pivot",
436
+ "table",
437
+ ].includes(token)
438
+ ) {
439
+ let nextToken = tokens[i + 1]
440
+ ? tokens[i + 1].replace(/[,;)]/g, "").toLowerCase()
441
+ : "";
442
+ if (
443
+ nextToken &&
444
+ ![
445
+ "select",
446
+ "where",
447
+ "on",
448
+ "order",
449
+ "group",
450
+ "having",
451
+ "as",
452
+ "(",
453
+ ].includes(nextToken)
454
+ ) {
455
+ if (i + 2 < tokens.length && tokens[i + 2].toLowerCase() === "as")
456
+ nextToken = nextToken;
457
+ else if (
458
+ !["left", "right", "inner", "outer", "cross", "full"].includes(
459
+ nextToken
460
+ )
461
+ )
462
+ tables.add(nextToken);
463
+ }
464
+ i++;
465
+ } else if (
466
+ token === "merge" &&
467
+ i + 1 < tokens.length &&
468
+ tokens[i + 1].toLowerCase() === "into"
469
+ ) {
470
+ let nextToken = tokens[i + 2]
471
+ ? tokens[i + 2].replace(/[,;)]/g, "").toLowerCase()
472
+ : "";
473
+ if (nextToken && !["using", "select", "where"].includes(nextToken))
474
+ tables.add(nextToken);
475
+ i += 2;
476
+ while (i + 1 < tokens.length && tokens[i + 1].toLowerCase() !== "using")
477
+ i++;
478
+ if (i + 2 < tokens.length) {
479
+ nextToken = tokens[i + 2].replace(/[,;)]/g, "").toLowerCase();
480
+ if (nextToken && !["select", "where"].includes(nextToken))
481
+ tables.add(nextToken);
482
+ }
483
+ } else if (
484
+ token === "select" &&
485
+ i + 1 < tokens.length &&
486
+ tokens[i + 1].toLowerCase() === "into"
487
+ ) {
488
+ let nextToken = tokens[i + 2]
489
+ ? tokens[i + 2].replace(/[,;)]/g, "").toLowerCase()
490
+ : "";
491
+ if (nextToken && !["from", "select"].includes(nextToken))
492
+ tables.add(nextToken);
493
+ i += 2;
494
+ while (i + 1 < tokens.length && tokens[i + 1].toLowerCase() !== "from")
495
+ i++;
496
+ if (i + 2 < tokens.length) {
497
+ nextToken = tokens[i + 2].replace(/[,;)]/g, "").toLowerCase();
498
+ if (nextToken && !["where", "join"].includes(nextToken))
499
+ tables.add(nextToken);
500
+ }
501
+ } else if (token === "with") {
502
+ let cteStart = i + 1;
503
+ while (i + 1 < tokens.length && tokens[i + 1].toLowerCase() !== "as") i++;
504
+ if (i + 2 < tokens.length && tokens[i + 2] === "(") {
505
+ let bracketCount = 1,
506
+ subqueryStart = i + 2;
507
+ while (i + 1 < tokens.length && bracketCount > 0) {
508
+ i++;
509
+ if (tokens[i] === "(") bracketCount++;
510
+ if (tokens[i] === ")") bracketCount--;
511
+ }
512
+ const subquery = tokens.slice(subqueryStart, i).join(" ");
513
+ tables.add(
514
+ ...extractTableNames(subquery).filter((t) => !tables.has(t))
515
+ );
516
+ }
517
+ } else if (
518
+ token === "values" &&
519
+ i + 1 < tokens.length &&
520
+ tokens[i + 1] === "("
521
+ ) {
522
+ let aliasStart = i + 1;
523
+ while (i + 1 < tokens.length && tokens[i + 1] !== "as") i++;
524
+ if (i + 2 < tokens.length) {
525
+ let alias = tokens[i + 2].replace(/[,;)]/g, "").toLowerCase();
526
+ if (alias) tables.add(alias);
527
+ }
528
+ } else if (["exists", "in"].includes(token)) {
529
+ let subqueryStart = i + 1;
530
+ while (i + 1 < tokens.length && tokens[i + 1] !== ")") i++;
531
+ if (i > subqueryStart) {
532
+ const subquery = tokens.slice(subqueryStart, i + 1).join(" ");
533
+ tables.add(
534
+ ...extractTableNames(subquery).filter((t) => !tables.has(t))
535
+ );
536
+ }
537
+ }
538
+ }
539
+ return Array.from(tables);
540
+ }
541
+
542
+ async function validateQuery(query, runResult, resultsDiv) {
543
+ const question = currentQuestions[currentQuestionIndex];
544
+ if (!question || !question.expected_sql) {
545
+ showError("No question or expected SQL available for validation.");
546
+ return;
547
+ }
548
+ const response = await fetch("/api/validate", {
549
+ method: "POST",
550
+ headers: { "Content-Type": "application/json", "session-id": sessionId },
551
+ body: JSON.stringify({
552
+ user_query: query,
553
+ expected_query: question.expected_sql,
554
+ }),
555
+ });
556
+ if (!response.ok)
557
+ throw new Error((await response.json()).detail || "Failed to validate");
558
+ const result = await response.json();
559
+ const questionText = document.getElementById("questionText");
560
+ const resultMessage = document.getElementById("resultMessage");
561
+ if (result.valid) {
562
+ questionText.classList.remove("text-red-500");
563
+ questionText.classList.add("text-green-500");
564
+ resultMessage.textContent = "Correct answer!";
565
+ resultMessage.className = "text-green-500 ml-4";
566
+ if (runResult.columns) {
567
+ let html = `<table class="w-full border-collapse"><tr>${runResult.columns
568
+ .map((col) => `<th class="border p-2">${col}</th>`)
569
+ .join("")}</tr>`;
570
+ html += runResult.rows
571
+ .map(
572
+ (row) =>
573
+ `<tr>${runResult.columns
574
+ .map((col) => `<td class="border p-2">${row[col] || "NULL"}</td>`)
575
+ .join("")}</tr>`
576
+ )
577
+ .join("");
578
+ html += "</table>";
579
+ resultsDiv.innerHTML = html;
580
+ } else {
581
+ resultsDiv.innerHTML = "<p>No results</p>";
582
+ }
583
+ } else {
584
+ questionText.classList.remove("text-green-500");
585
+ questionText.classList.add("text-red-500");
586
+ resultMessage.textContent = "Incorrect answer!";
587
+ resultMessage.className = "text-red-500 ml-4";
588
+ resultsDiv.innerHTML = "";
589
+ }
590
+ }
591
+
592
+ function showError(message) {
593
+ let errorMessage =
594
+ document.getElementById("errorMessage") || document.createElement("span");
595
+ if (!errorMessage.id) {
596
+ errorMessage.id = "errorMessage";
597
+ document.getElementById("runQueryBtn").parentNode.appendChild(errorMessage);
598
+ }
599
+ errorMessage.textContent = message;
600
+ errorMessage.className = "text-red-500 mt-2";
601
+ }
602
+
603
+ window.onload = init;
app/static/style.css ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: Arial, sans-serif;
3
+ }
4
+ #results table {
5
+ border-collapse: collapse;
6
+ width: 100%;
7
+ }
8
+ #results th,
9
+ #results td {
10
+ border: 1px solid #ddd;
11
+ padding: 8px;
12
+ text-align: left;
13
+ }
14
+ #results th {
15
+ background-color: #f2f2f2;
16
+ }
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ fastapi==0.110.0
2
+ uvicorn
3
+ pydantic
4
+ sqlparse