HackIndia Logo
HackathonsNewsSkillsGrowth
HackIndia LogoContact: +91 9910125804

General

  • Companies
  • Colleges
  • Board of Advisors
  • Brand Ambassadors
  • FAQ
  • Contact
  • Report a Bug

Hackathons

  • Upcoming Hackathons
  • Judges
  • Mentors
  • Partners
  • Participating Colleges
  • Testimonials

Resources

  • About Us
  • Branding
  • Latest News
  • Growth
  • Skills
  • Builders
  • Ecosystem
  • Forum
  • Ideas
  • Prompt Optimizer
  • Games

© 2026 HackIndia. All rights reserved.

Privacy PolicyTerms of ServiceCode of ConductSitemap

Build 2.139 · 05 Nov 2026, 10:27 UTC

    HackIndia
    HackathonsNewsInternshipsSkillsGrowthPro Perks
    ← Design SQL-backed features

    All the reading

    SQL

    8 lessons, about 34 minutes. The check's questions come from these.

    Contents
    Modeling tables and keys
    1. 1. Model one real feature
    2. 2. Use primary and foreign keys
    Querying and combining rows
    1. 3. Read data with SELECT
    2. 4. Combine data with JOINs
    3. 5. Summarise with GROUP BY
    Fixing query safety and speed
    1. 6. Find slow queries
    2. 7. Stop SQL injection
    3. 8. Ship the SQL feature repo

    Modeling tables and keys

    1. Model one real feature

    4 min

    In 30 seconds
    • Pick one feature small enough to ship in a hackathon repo.
    • Turn user actions into tables, columns, and sample rows.
    • Create the first schema and seed files for your SQL feature.

    Start with a feature

    A database project is easier when you model one real feature, not a full app. Pick something a user can do and a recruiter can understand in 30 seconds.

    Good examples: track mess payments, save internship applications, list hostel repair requests, manage club event registrations, or record UPI expense splits.

    Key point

    One idea

    Model the data your feature must remember. Do not start with screens, buttons, or a full startup idea.

    A table stores one kind of thing. A row stores one example of that thing. A column stores one fact about it.

    Your to-do

    Choose the scope

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    Worked example

    Feature: hostel students submit repair requests. The first version only needs one table.

    You are not solving login, admin dashboards, or notifications yet. You are modelling the core data.

    Feature: Hostel repair requests
    
    User action:
    A student reports a problem in their room.
    
    Facts to store:
    - student_name
    - hostel_name
    - room_number
    - issue_title
    - issue_description
    - status
    - created_at
    
    Table:
    repair_requests
    
    Sample row:
    Asha Nair | Ganga Hostel | B-204 | Fan not working | Fan stopped last night | open | 2026-09-20

    2. Use primary and foreign keys

    4 min

    In 30 seconds
    • Choose primary keys for each table in your feature.
    • Connect tables with foreign keys that point to real rows.
    • Add key constraints to schema.sql so bad data fails early.

    Why keys matter

    A table without keys is easy to fill and hard to trust. You can insert duplicate users, orphan orders, or reviews for products that do not exist.

    Keys turn your feature idea from loose rows into rules the database can enforce. This is what makes your repo look serious.

    Key point

    Remember this

    A primary key identifies one row in its own table. A foreign key stores the primary key of another table to show a real relationship.

    Your to-do

    Pick primary keys

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Querying and combining rows

    3. Read data with SELECT

    4 min

    In 30 seconds
    • Pick only the columns your feature needs with SELECT.
    • Filter and sort rows so screens load useful data first.
    • Save sample read queries in your repo for recruiters to run.
    Key point

    One idea

    A SELECT query is the read side of your feature. It should return the exact rows and columns your screen, API route, or CLI command needs.

    Start from the screen

    Before writing SQL, write the question your feature must answer. For a campus event app, it might be: “Show upcoming events for this college, newest first.”

    That question tells you the table, columns, filters, and order. Do not start with SELECT * unless you are quickly exploring data.

    Example

    Basic read query

    This query reads only what an event list card needs. It skips internal columns like created_at if the screen does not show them.

    -- queries/list_upcoming_events.sql
    SELECT
      id,
      title,
      venue,
      starts_at
    FROM events
    WHERE college_id = 1
      AND starts_at >= '2026-01-01 00:00:00'
    ORDER BY starts_at ASC
    LIMIT 10;

    Fixing query safety and speed

    6. Find slow queries

    5 min

    In 30 seconds
    • Spot a slow SQL query before it hurts your feature demo.
    • Use EXPLAIN to see whether the database scans or searches.
    • Fix one lookup by adding the right index and recording the result.
    Key point

    One idea

    A slow query usually reads too many rows. Your job is to prove what it reads, then reduce the work.

    Use EXPLAIN before and after a change. Do not guess from vibes or one lucky run.

    What slow means

    In a hackathon project, slow often means a page feels stuck when seed data grows. Your laptop may hide the issue when you have only 20 rows.

    Test with enough seed data to make bad queries visible. Even a few thousand rows is useful for a student project.

    Your to-do

    Find the query

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    10 questions, 7 to pass.

    Sign in to take the test
    Example

    First schema file

    Create a folder named sql. Put your first table design in sql/schema.sql.

    This is only the model. Later lessons will improve it with keys and relationships.

    CREATE TABLE repair_requests (
      student_name TEXT,
      hostel_name TEXT,
      room_number TEXT,
      issue_title TEXT,
      issue_description TEXT,
      status TEXT,
      created_at TEXT
    );
    Example

    Seed data file

    Seed data makes your repo easy to run and inspect. Add a few realistic rows, not private data from real people.

    Use simple names and Indian campus examples if they fit your feature.

    INSERT INTO repair_requests (
      student_name,
      hostel_name,
      room_number,
      issue_title,
      issue_description,
      status,
      created_at
    ) VALUES
    ('Asha Nair', 'Ganga Hostel', 'B-204', 'Fan not working', 'Fan stopped last night', 'open', '2026-09-20'),
    ('Rohan Mehta', 'Kaveri Hostel', 'A-118', 'Leaking tap', 'Water leaking near washbasin', 'open', '2026-09-20'),
    ('Iqbal Khan', 'Narmada Hostel', 'C-310', 'Tube light flickers', 'Light flickers during evening study time', 'closed', '2026-09-19');
    Common mistake

    Common mistake

    Beginners often model the whole app at once: users, payments, roles, reports, chat, and analytics. That makes the repo unfinished.

    For this track, a small finished feature beats a large half-built database. You can add more tables after the core feature works.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    Feature example

    Say you are building a campus event RSVP feature. One user can create many RSVPs. One event can have many RSVPs.

    The rsvps table should not copy the full user and event details. It should store user_id and event_id, then foreign keys check that both rows exist.

    -- schema.sql
    CREATE TABLE users (
      id INTEGER PRIMARY KEY,
      name TEXT NOT NULL,
      email TEXT NOT NULL UNIQUE
    );
    
    CREATE TABLE events (
      id INTEGER PRIMARY KEY,
      title TEXT NOT NULL,
      venue TEXT NOT NULL,
      starts_at TEXT NOT NULL
    );
    
    CREATE TABLE rsvps (
      id INTEGER PRIMARY KEY,
      user_id INTEGER NOT NULL,
      event_id INTEGER NOT NULL,
      status TEXT NOT NULL,
      created_at TEXT NOT NULL,
      FOREIGN KEY (user_id) REFERENCES users(id),
      FOREIGN KEY (event_id) REFERENCES events(id)
    );

    What this prevents

    The database now blocks an RSVP for user_id 99 if there is no user with id 99. It also blocks an RSVP for event_id 50 if that event does not exist.

    This helps during hackathons because bugs show up at insert time, not during your final demo.

    Common mistake

    Common mistake

    Do not store related data as plain text when it should be a relationship. For example, rsvps.event_title looks easy, but it breaks when the event title changes.

    Store event_id instead. Read the event title later with a JOIN in a later lesson.

    Tip

    Seed in order

    Insert parent rows before child rows. For this example, insert users and events before rsvps.

    If your seed file fails, check whether a foreign key points to a row you have not inserted yet.

    -- seed.sql
    INSERT INTO users (id, name, email) VALUES
      (1, 'Asha Rao', '[email protected]'),
      (2, 'Kabir Mehta', '[email protected]');
    
    INSERT INTO events (id, title, venue, starts_at) VALUES
      (1, 'AI Project Night', 'Lab 204', '2026-02-10 17:00');
    
    INSERT INTO rsvps (id, user_id, event_id, status, created_at) VALUES
      (1, 1, 1, 'going', '2026-02-01 12:30');
    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Your to-do

    Build a SELECT

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Tip

    Name saved queries

    Put read queries in a queries folder. Give each file a task name, not a vague name like test.sql.

    Good names help recruiters understand your feature fast: list_upcoming_events.sql, find_student_by_roll.sql, search_hostels.sql.

    Common mistake

    Avoid SELECT star

    SELECT * is okay for checking a table in your SQL client. It is not good for feature code or repo demos.

    It can expose fields you did not mean to show, like phone numbers. It can also make later schema changes break your app in confusing ways.

    Example

    Search with filters

    A search query usually has a text filter, a stable sort, and a limit. Later in this track you will make inputs safe with parameters. For today, focus on the shape of the read.

    -- queries/search_events_by_title.sql
    SELECT
      id,
      title,
      venue,
      starts_at
    FROM events
    WHERE college_id = 1
      AND title LIKE '%hack%'
    ORDER BY starts_at ASC, id ASC
    LIMIT 20;
    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    4. Combine data with JOINs

    4 min

    In 30 seconds
    • Use JOIN to read related rows from two tables in one result.
    • Choose INNER or LEFT JOIN based on missing matches.
    • Add one joined query to your SQL feature repo with a clear sample.
    Key point

    One idea

    A JOIN connects rows using a relationship, usually a foreign key pointing to a primary key. You use it when the data you need is split across tables.

    Why JOINs matter

    Your feature should not store the same detail in many places. For example, an orders table can store user_id, while the users table stores the user name and phone.

    When you need to show an order list with buyer names, you combine orders and users in the query. You do not copy the user name into every order row.

    Example

    Basic INNER JOIN

    Use INNER JOIN when you only want rows that have a match in both tables. This is good for required relationships, like an order that must belong to a user.

    -- List orders with buyer details
    SELECT
      orders.id AS order_id,
      orders.total_rupees,
      orders.status,
      users.name AS buyer_name,
      users.email AS buyer_email
    FROM orders
    INNER JOIN users
      ON orders.user_id = users.id
    ORDER BY orders.id DESC;

    Read the query

    FROM says the main table you are reading from. INNER JOIN names the second table. ON explains how the two tables match.

    Use table names before column names when both tables may have the same column, like id or created_at. Use AS to give readable names to the output.

    Example

    LEFT JOIN for missing data

    Use LEFT JOIN when you want every row from the left table, even if the joined table has no match. This helps with optional data.

    For example, not every student profile may have a submitted project yet. A LEFT JOIN can still show all students.

    -- Show all students, with project title if submitted
    SELECT
      students.id AS student_id,
      students.name,
      projects.title AS project_title
    FROM students
    LEFT JOIN projects
      ON projects.student_id = students.id
    ORDER BY students.name;
    Common mistake

    Common mistake

    Do not join tables without an ON condition. You can accidentally create too many rows, where every row from one table is paired with every row from the other.

    If your result suddenly has hundreds of rows for a small seed file, check your ON line first.

    Tip

    Use AI safely

    You can ask ChatGPT or Gemini to draft a JOIN query from your schema. Paste only your table definitions, not private data.

    Then check three things yourself: the ON condition uses the right keys, the JOIN type matches your need, and the selected columns have clear aliases.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    5. Summarise with GROUP BY

    4 min

    In 30 seconds
    • Use GROUP BY to turn many rows into one summary per group.
    • Use COUNT, SUM, AVG, MIN, and MAX for dashboard-style numbers.
    • Add a summary query and sample output to your SQL feature repo.

    Why summaries matter

    Most useful features need summary numbers. A canteen app shows orders per day. A fest app shows registrations per event. A placement tracker shows applications by status.

    GROUP BY is how SQL makes these numbers from normal rows. It helps your repo look like a real product, not only a list page.

    Key point

    One idea

    GROUP BY makes one result row for each value in a column, then aggregate functions calculate numbers inside each group.

    Every selected column must either be grouped or aggregated. If it is not, the result is unclear.

    Example

    Start with rows

    Imagine your feature stores orders for a college canteen. Each row is one order. You want totals per status for an admin screen.

    -- seed data idea
    orders
    id | student_name | status     | amount_rupees
    1  | Asha         | paid       | 80
    2  | Kabir        | paid       | 120
    3  | Meera        | pending    | 60
    4  | Rohan        | cancelled  | 100
    5  | Iqra         | paid       | 50
    Example

    Count each group

    Use COUNT(*) when you want to count rows. This is good for badges, reports, and simple admin dashboards.

    SELECT
      status,
      COUNT(*) AS order_count
    FROM orders
    GROUP BY status
    ORDER BY order_count DESC;
    
    -- possible result
    status    | order_count
    paid      | 3
    pending   | 1
    cancelled | 1
    Example

    Add useful totals

    Use SUM for money, AVG for averages, and MIN or MAX for ranges. Name your output columns clearly so your README is easy to read.

    SELECT
      status,
      COUNT(*) AS order_count,
      SUM(amount_rupees) AS total_rupees,
      AVG(amount_rupees) AS average_rupees
    FROM orders
    GROUP BY status
    ORDER BY total_rupees DESC;
    
    -- possible result
    status    | order_count | total_rupees | average_rupees
    paid      | 3           | 250          | 83.333333
    cancelled | 1           | 100          | 100
    pending   | 1           | 60           | 60
    Common mistake

    Common mistake

    Do not select a normal column that is not in GROUP BY. For example, selecting student_name while grouping only by status does not make sense. There are many students inside one status.

    If you need student-level rows, do not group. If you need status-level summaries, group by status and aggregate the rest.

    -- Avoid this
    SELECT
      status,
      student_name,
      COUNT(*) AS order_count
    FROM orders
    GROUP BY status;
    Tip

    Filter in the right place

    Use WHERE before GROUP BY to filter raw rows. For example, only orders from this month.

    Use HAVING after GROUP BY to filter summary rows. For example, only statuses with at least 2 orders.

    SELECT
      status,
      COUNT(*) AS order_count,
      SUM(amount_rupees) AS total_rupees
    FROM orders
    WHERE amount_rupees > 0
    GROUP BY status
    HAVING COUNT(*) >= 2;
    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    Use EXPLAIN

    SQLite is common for small local projects. It has EXPLAIN QUERY PLAN, which shows if the database scans a table or uses an index.

    This example finds orders for one user. Without an index on user_id, SQLite may scan the whole orders table.

    EXPLAIN QUERY PLAN
    SELECT id, total_amount, status, created_at
    FROM orders
    WHERE user_id = 42
    ORDER BY created_at DESC;
    
    -- Possible output before an index:
    -- SCAN orders
    -- USE TEMP B-TREE FOR ORDER BY
    
    CREATE INDEX idx_orders_user_created
    ON orders(user_id, created_at DESC);
    
    EXPLAIN QUERY PLAN
    SELECT id, total_amount, status, created_at
    FROM orders
    WHERE user_id = 42
    ORDER BY created_at DESC;
    
    -- Better output after the index:
    -- SEARCH orders USING INDEX idx_orders_user_created (user_id=?)
    Tip

    Read the plan

    SCAN means the database may read the whole table. SEARCH USING INDEX means it can jump closer to the rows it needs.

    For this lesson, you do not need to master every planner term. First learn to spot full scans on tables that will grow.

    Common mistake

    Do not index everything

    An index helps reads, but it also costs storage and slows inserts or updates a little. Add indexes for real lookup patterns, not every column.

    Good index candidates are columns used often in WHERE, JOIN, or ORDER BY. For example: user_id, event_id, created_at, status, or a pair like (user_id, created_at).

    Example

    Record your fix

    Recruiters cannot see your thinking unless you leave evidence. Add a short performance note to your README or docs folder.

    Keep it simple. Show the query, the problem, the index, and the changed plan.

    Performance note: orders list
    
    Feature: Show a user's recent orders.
    Problem: Query scanned the full orders table.
    Fix: Added idx_orders_user_created on orders(user_id, created_at DESC).
    Before plan: SCAN orders, USE TEMP B-TREE FOR ORDER BY.
    After plan: SEARCH orders USING INDEX idx_orders_user_created (user_id=?).
    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    7. Stop SQL injection

    4 min

    In 30 seconds
    • Use parameters so user input is treated as data, not SQL.
    • Spot unsafe string-built queries in your feature code.
    • Add safe query examples to your repo for recruiters to inspect.
    Key point

    One rule

    Never build SQL by joining strings with user input. Use placeholders and pass values separately.

    The database driver sends the SQL shape and the user values apart. So a name, email, UPI handle, or search term stays data.

    Why this matters

    SQL injection happens when user input changes the meaning of your query. A login box, search box, filter, or admin route can all become risky.

    Your internship repo should show that you know the safe habit. Recruiters may not run attacks, but they can read your query code.

    Example

    Unsafe query

    This is the pattern to remove from your project. It looks simple, but the input becomes part of the SQL command.

    // Bad: Node.js + pg
    const email = req.body.email;
    const sql = "SELECT id, name FROM users WHERE email = '" + email + "'";
    const result = await db.query(sql);
    Example

    Safe query

    Use placeholders. In PostgreSQL with the pg package, placeholders are $1, $2, and so on. Values go in an array.

    // Good: Node.js + pg
    const email = req.body.email;
    const sql = "SELECT id, name FROM users WHERE email = $1";
    const result = await db.query(sql, [email]);
    Your to-do

    Fix your feature

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Common mistake

    Common mistake

    Do not trust client-side checks. A user can skip your form and call your API directly with Postman, curl, or browser dev tools.

    Validation is still useful for clean data. But validation is not a replacement for parameters.

    Tip

    Use AI carefully

    Ask ChatGPT or Gemini to review one file for SQL injection. Paste the file and ask: “Find SQL queries where user input is joined into the SQL string. Suggest parameterised replacements.”

    Check the answer yourself. Make sure every user-controlled value is outside the SQL string and inside the values array or parameter object.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 4 done

    8. Ship the SQL feature repo

    5 min

    In 30 seconds
    • Package your SQL feature so another developer can run it locally.
    • Add schema, seed data, safe queries, and a clear README to your repo.
    • Submit a repo that proves your database work, not just your UI.
    Key point

    Repo proves the feature

    Your project is not finished when it works on your laptop. It is finished when a reviewer can clone it, set up the database, run the app, and see the feature without asking you on WhatsApp.

    For this track, your repo must show four things: schema files, seed data, parameterised queries, and a README with local run steps.

    What to ship

    Keep the feature small. A campus canteen order list, club event RSVP, hostel complaint tracker, or internship application tracker is enough.

    Recruiters and hackathon judges look for proof that you can model data, query it, and protect inputs. A neat repo beats a half-working large app.

    Your to-do

    Use this structure

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    Simple repo layout

    Use names that make sense without explanation. If someone opens your repo on a phone, they should know where the database files are.

    canteen-orders/
      README.md
      package.json
      src/
        server.js
        db.js
        orders.js
      db/
        schema.sql
        seed.sql
      .env.example
    Example

    README starter

    Your README is the demo guide. Write it for a classmate who has Node.js and a database installed, but has never seen your code.

    # Canteen Orders
    
    A small SQL-backed feature to create and view canteen orders.
    
    ## Feature
    Students can place an order with item name, quantity, and phone number.
    The app stores orders in SQL and lists recent orders.
    
    ## Tech
    Node.js, Express, PostgreSQL
    
    ## Run locally
    1. Clone the repo
    2. Run npm install
    3. Create a local database named canteen_orders
    4. Run psql -d canteen_orders -f db/schema.sql
    5. Run psql -d canteen_orders -f db/seed.sql
    6. Copy .env.example to .env and set DATABASE_URL
    7. Run npm start
    
    ## Sample query
    SELECT id, item_name, quantity, created_at
    FROM orders
    ORDER BY created_at DESC
    LIMIT 10;
    
    ## Safety
    User input is sent using parameterised queries, not string joining.
    Common mistake

    Do not hide SQL

    A common mistake is shipping only screenshots and app code. That does not prove you can design the database.

    Do not make reviewers guess your tables. Put CREATE TABLE statements in schema.sql. Put sample INSERT statements in seed.sql. Keep them runnable from a clean database.

    Tip

    Use AI carefully

    You can ask ChatGPT or Gemini to review your README and setup steps. Paste your repo layout and ask: “Can a new developer run this locally? What is missing?”

    Check the answer. AI may invent commands, database names, or files you do not have. Only keep steps you have tested yourself.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done