Data AnalyticsFreshers and students

SQL in 30 days for a final-year student: a day-by-day plan

PK
Pankit Kumar
Sr. Data Scientist at Parexel (a Goldman Sachs–backed company) · 27 September 2026 · 5 min read
In this article (7 sections)

Related programme: the Data Analytics with Generative AI course (3–4 months; ₹90,000 (inclusive of GST) · EMI from ₹4,000/month).

The short answer

You can learn the SQL that entry-level data tests check in 30 days at one hour a day, if every session is spent writing queries. The plan below uses SQLite, which ships with Python, and moves from single-table queries (days 1–7) to aggregation (8–13), joins (14–19), subqueries and CTEs (20–24) and window functions (25–28), with two days of timed revision. Each week ends with a checkpoint query you should be able to write without help.

Set up the practice database once

Run this Python script to create college.db, a synthetic database of students, courses and enrolments. It uses only the standard library. Every checkpoint below runs against it.

python
import sqlite3, random

random.seed(7)
con = sqlite3.connect("college.db")
cur = con.cursor()
cur.executescript("""
DROP TABLE IF EXISTS students; DROP TABLE IF EXISTS courses; DROP TABLE IF EXISTS enrolments;
CREATE TABLE students (student_id INTEGER PRIMARY KEY, name TEXT, branch TEXT, city TEXT, join_year INTEGER);
CREATE TABLE courses (course_id TEXT PRIMARY KEY, title TEXT, credits INTEGER);
CREATE TABLE enrolments (student_id INTEGER, course_id TEXT, semester INTEGER, marks INTEGER);
""")
branches = ["CSE", "ECE", "ME", "CE", "BCom"]
cities = ["Noida", "Ghaziabad", "Delhi", "Meerut", None]
cur.executemany("INSERT INTO students VALUES (?,?,?,?,?)",
    [(i, f"Student{i:03d}", random.choice(branches), random.choice(cities), random.choice([2023, 2024]))
     for i in range(1, 121)])
cur.executemany("INSERT INTO courses VALUES (?,?,?)",
    [("DB101", "Databases", 4), ("ST201", "Statistics", 3), ("PY101", "Python", 4), ("EC105", "Economics", 3)])
rows = []
for sid in range(1, 121):
    for cid in random.sample(["DB101", "ST201", "PY101", "EC105"], k=random.randint(1, 4)):
        marks = random.randint(25, 98) if random.random() > 0.05 else None   # a few missing marks
        rows.append((sid, cid, random.choice([5, 6]), marks))
cur.executemany("INSERT INTO enrolments VALUES (?,?,?,?)", rows)
con.commit()
print(cur.execute("SELECT COUNT(*) FROM students").fetchone()[0], "students,",
      cur.execute("SELECT COUNT(*) FROM enrolments").fetchone()[0], "enrolments")
con.close()

Open it in DB Browser for SQLite, or query it from Python with sqlite3.connect("college.db"). The deliberate NULL cities and missing marks are there so you meet the traps early.

Week 1 (days 1–7): one table, precisely

DayTopicPractise
1SELECT, column aliasesList names and branches; rename columns
2WHERE with =, <>, >, BETWEENStudents who joined in 2024
3AND, OR, parenthesesECE or ME students from Noida — check the brackets
4IN, LIKE, wildcardsNames starting with "Student00"
5NULL handling: IS NULL, COALESCEStudents with no city; replace NULL with "Unknown"
6ORDER BY, LIMIT, DISTINCTDistinct branches, sorted
7Checkpoint and revisionWrite the query below from memory

Checkpoint 1: list students from Noida or Delhi who joined in 2024, sorted by name.

sql
SELECT student_id, name, city
FROM students
WHERE city IN ('Noida', 'Delhi') AND join_year = 2024
ORDER BY name;

Week 2 (days 8–13): counting correctly

DayTopic
8COUNT(*), COUNT(column), SUM, AVG, MIN, MAX
9GROUP BY one column
10GROUP BY two columns
11HAVING versus WHERE
12CASE WHEN for buckets (distinction, pass, fail)
13Checkpoint

Checkpoint 2: average marks per course, only for courses with at least 50 marked enrolments. Notice that AVG and COUNT(marks) both ignore NULLs.

sql
SELECT course_id, COUNT(marks) AS marked, ROUND(AVG(marks), 1) AS avg_marks
FROM enrolments
GROUP BY course_id
HAVING COUNT(marks) >= 50
ORDER BY avg_marks DESC;

Week 3 (days 14–19): joins without losing rows

DayTopic
14INNER JOIN on one key
15LEFT JOIN and finding unmatched rows (WHERE right.key IS NULL)
16Joining three tables
17Row-count checks before and after every join
18Join plus GROUP BY
19Checkpoint

On day 17, make it a habit: count rows in each table, then count rows after the join, and explain the difference before you trust any total.

Checkpoint 3: credit-weighted average marks per branch.

sql
SELECT s.branch,
       ROUND(SUM(e.marks * c.credits) * 1.0 / SUM(c.credits), 1) AS weighted_avg
FROM enrolments e
JOIN students s ON s.student_id = e.student_id
JOIN courses  c ON c.course_id  = e.course_id
WHERE e.marks IS NOT NULL
GROUP BY s.branch
ORDER BY weighted_avg DESC;

The WHERE e.marks IS NOT NULL matters: without it, credits for unmarked enrolments would be counted in the denominator but contribute nothing to the numerator.

Week 4 (days 20–28): subqueries, CTEs and windows

DayTopic
20Subquery in WHERE (above-average students)
21Subquery in FROM
22CTEs with WITH
23Chaining two CTEs
24Checkpoint on CTEs
25ROW_NUMBER and RANK
26PARTITION BY
27Running totals with SUM() OVER
28Checkpoint

Checkpoint 4: the top two students by marks in each course. SQLite supports window functions from version 3.25, which current Python releases include.

sql
WITH ranked AS (
  SELECT course_id, student_id, marks,
         ROW_NUMBER() OVER (PARTITION BY course_id ORDER BY marks DESC, student_id) AS rn
  FROM enrolments
  WHERE marks IS NOT NULL
)
SELECT course_id, student_id, marks
FROM ranked
WHERE rn <= 2
ORDER BY course_id, rn;

The student_id tie-breaker makes the result repeatable when two students have equal marks. Explain in one sentence why you might use RANK instead.

Days 29–30: timed revision

Day 29: redo all four checkpoints against a 15-minute timer, then write five new questions about college.db and answer them. Day 30: take a mixed set of ten problems from a practice site, timed, and log each mistake by type (NULL, join, grouping, window). That log is your revision list for placement season.

If you miss days

Do not skip ahead. Merge two lighter days (days 4 and 6, or 25 and 26) rather than dropping joins, which is the week screeners test hardest. Missing marks and NULL cities in this database are also the kind of thing the Excel test for freshers checks, from the spreadsheet side. When you are ready to see how your SQL fits into complete analyst work — dashboards, reconciliations and written findings — look at the Data Analyst programme, or check where your resume stands with the free skill check.

Frequently asked questions

Is one hour a day enough to learn SQL in 30 days?

It is enough to reach the level most entry-level analyst tests check — filtering, grouping, joins, subqueries and basic window functions — if the hour is spent writing queries rather than watching videos. Fluency under interview pressure takes further practice after the 30 days.

Which SQL database should a student practise on?

SQLite is the easiest start because it comes with Python and needs no server. The core syntax transfers to MySQL, PostgreSQL and SQL Server, with small differences in date functions and some window-function support.

PK
Pankit Kumar
Lead Instructor, NeuraPath Academy

Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.

This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.

Explore Data Analytics with Generative AI
Counselling is free · no obligation

Not sure which programme fits?

Tell us your background and we will map it to the right entry point — including saying so when a cheaper programme is the better fit. A counsellor replies within one working day.