SQL assignment help is most useful when it explains why a query returns the rows it does. Most marks are lost to joins that drop or duplicate rows, and to aggregates grouped the wrong way.
This guide covers how SQL tasks are marked, the query patterns you will meet most, how SQL actually evaluates a query, and how to design and normalise a schema. Every example uses one small university database so you can follow along.
What SQL Assignments Are Really Testing
SQL tasks test whether your query returns exactly the right rows: no missing records from an inner join, no duplicates from a careless join, correct handling of NULLs and correct grouping. Design tasks test keys, relationships and normalisation.
A query that runs is not the same as a correct query. Always check the output against the question with a small, known dataset.
The Example Database
All the examples below use three tables. A student can enrol on many courses, and a course can have many students, so the enrolments table links them.
| Table | Columns | Key |
|---|---|---|
| students | student_id, name, year_of_study | Primary key: student_id |
| courses | course_id, title, credits | Primary key: course_id |
| enrolments | student_id, course_id, grade | Composite primary key: (student_id, course_id) |
CREATE TABLE enrolments ( student_id INT NOT NULL, course_id INT NOT NULL, grade NUMERIC(5, 2), PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES students (student_id), FOREIGN KEY (course_id) REFERENCES courses (course_id) );
The composite primary key stops a student enrolling on the same course twice. The foreign keys stop enrolments that point at students or courses that do not exist.
How SQL Evaluates a Query
SQL is written in one order but processed in another. Knowing the logical order explains many confusing errors.
| Step | Clause | What happens |
|---|---|---|
| 1 | FROM and JOIN | Tables are combined |
| 2 | WHERE | Individual rows are filtered |
| 3 | GROUP BY | Rows are grouped |
| 4 | HAVING | Groups are filtered |
| 5 | SELECT | Columns and expressions are computed |
| 6 | DISTINCT | Duplicate result rows are removed |
| 7 | ORDER BY | Results are sorted |
| 8 | LIMIT or FETCH | Rows are cut to the requested number |
This is why WHERE cannot filter on an aggregate such as AVG(grade): grouping has not happened yet. Use HAVING instead. It is also why a column alias defined in SELECT works in ORDER BY but, in standard SQL, not in WHERE.
Joins: Inner, Left and the Rows You Lose
An inner join keeps only rows that match in both tables. A left join keeps every row from the left table and fills the right side with NULLs where there is no match.
Task: list every course with the number of students enrolled, including courses with none.
SELECT c.course_id, c.title, COUNT(e.student_id) AS enrolled FROM courses AS c LEFT JOIN enrolments AS e ON e.course_id = c.course_id GROUP BY c.course_id, c.title ORDER BY enrolled DESC;
Two details matter. The LEFT JOIN keeps courses with no enrolments, and COUNT(e.student_id) counts only non-NULL values, so those courses show 0. COUNT(*) would count the NULL-filled row and wrongly show 1.
Task: find courses nobody has enrolled on.
SELECT c.title FROM courses AS c LEFT JOIN enrolments AS e ON e.course_id = c.course_id WHERE e.course_id IS NULL;
A common trap is adding a condition on the right-hand table in WHERE, such as WHERE e.grade > 60, after a LEFT JOIN. It removes the NULL rows and silently turns the left join back into an inner join. Put such conditions in the ON clause if you want to keep unmatched rows.
Aggregates with GROUP BY and HAVING
Aggregate functions such as COUNT, SUM, AVG, MIN and MAX collapse many rows into one per group. Every column in SELECT must either be inside an aggregate or listed in GROUP BY.
Task: average grade per course, for courses with at least five graded students, highest first.
SELECT c.title, COUNT(e.grade) AS graded, ROUND(AVG(e.grade), 1) AS avg_grade FROM courses AS c JOIN enrolments AS e ON e.course_id = c.course_id GROUP BY c.course_id, c.title HAVING COUNT(e.grade) >= 5 ORDER BY avg_grade DESC;
Grouping by course_id as well as title keeps two courses that happen to share a title apart. AVG ignores NULL grades, so ungraded enrolments do not drag the average down.
Subqueries and Common Table Expressions
A subquery is a query inside another query. Common table expressions (CTEs), written with WITH, do the same job but are easier to read and debug.
Task: students whose average grade is above the overall average.
WITH student_avg AS ( SELECT student_id, AVG(grade) AS avg_grade FROM enrolments GROUP BY student_id ) SELECT s.name, ROUND(sa.avg_grade, 1) AS avg_grade FROM student_avg AS sa JOIN students AS s ON s.student_id = sa.student_id WHERE sa.avg_grade > (SELECT AVG(grade) FROM enrolments);
Be careful with NOT IN and subqueries. If the subquery returns even one NULL, NOT IN returns no rows at all. NOT EXISTS, or a LEFT JOIN with IS NULL, avoids the problem.
Window Functions
Window functions calculate across related rows without collapsing them into one. They answer questions such as "rank students within each course" that GROUP BY cannot.
SELECT course_id, student_id, grade,
RANK() OVER (PARTITION BY course_id ORDER BY grade DESC) AS course_rank
FROM enrolments
WHERE grade IS NOT NULL;
RANK leaves gaps after ties (1, 1, 3), DENSE_RANK does not (1, 1, 2), and ROW_NUMBER gives every row a unique number. Pick the one the question implies.
NULLs and Three-Valued Logic
NULL means unknown or missing, not zero or an empty string. Any comparison with NULL, including NULL = NULL, gives unknown rather than true, so WHERE drops the row.
- Use IS NULL and IS NOT NULL, never = NULL.
- COUNT(*) counts rows; COUNT(column) skips NULLs.
- Aggregates such as AVG and SUM ignore NULLs.
- COALESCE(grade, 0) replaces NULL with a value, but only do this if zero is genuinely correct.
Database Design and Normalisation
Design tasks ask you to turn a description into tables, keys and relationships, usually with an entity-relationship diagram. Normalisation removes redundancy so that each fact is stored once.
| Normal form | Rule | Typical fix |
|---|---|---|
| First (1NF) | Each column holds one atomic value; no repeating groups | Split a "courses" list column into rows in a linking table |
| Second (2NF) | 1NF, and no non-key column depends on only part of a composite key | Move course title out of enrolments into courses |
| Third (3NF) | 2NF, and no non-key column depends on another non-key column | Move department phone number into a departments table |
Many-to-many relationships always need a linking table, like enrolments here. In an ERD, show each relationship's cardinality and say which side holds the foreign key.
Dialects and Safe Queries
Core SQL is standard, but databases differ in the details. Say which system your solution targets.
- Limiting rows: LIMIT in MySQL, PostgreSQL and SQLite; TOP in SQL Server; FETCH FIRST n ROWS ONLY in standard SQL, Oracle and PostgreSQL.
- Strings: use single quotes for text values, as in WHERE name = 'Ada'.
- Application code: never build queries by joining user input into a string. Use parameters to prevent SQL injection.
# Python with sqlite3: the ? placeholder is filled safely
cur.execute("SELECT name FROM students WHERE student_id = ?", (student_id,))
Common SQL Mistakes
| Mistake | Fix |
|---|---|
| Inner join drops records you needed | Use a LEFT JOIN from the table you must keep in full |
| Join on the wrong column creates duplicates | Join on the full key and check row counts |
| Aggregate in WHERE | Move the condition to HAVING |
| = NULL in a condition | Use IS NULL |
| Column in SELECT not in GROUP BY | Add it to GROUP BY or wrap it in an aggregate |
| NOT IN with a subquery containing NULLs | Use NOT EXISTS |
How STEM Donkey Helps with SQL Assignments
Send the task sheet, the schema or CREATE TABLE scripts, any sample data, and the database system you must use, such as MySQL, PostgreSQL, SQL Server, Oracle or SQLite. Deadlines run from 3 hours to 20 days.
A database writer prepares a custom solution written from scratch: tested queries, expected output, ERDs where needed and short notes explaining each query. It is checked for originality and meant for study and reference.
Choose a Standard, Master or Elite writer, see the price before you pay, and get free revisions within the original scope for 14 days. Your identity is never shared with the writer.
Want Queries That Run and Make Sense?
Send your schema, sample data and tasks. You get a custom solution with tested SQL, expected output and notes explaining each query.
Order Your SQL SolutionFree revisions within scope for 14 days · Full refund if late · Written from scratch for your order
Frequently Asked Questions
SELECT queries, joins, aggregates, subqueries, CTEs, window functions, views, stored procedures, triggers, indexes, ER diagrams and normalisation.
WHERE filters individual rows before grouping. HAVING filters groups after GROUP BY, so it can use aggregates such as COUNT or AVG.
When you must keep every row from one table even if it has no match in the other, for example courses with no students.
Usually because a join matches more rows than expected, often from joining on part of a key. Check row counts after each join before reaching for DISTINCT.
MySQL, PostgreSQL, SQL Server, Oracle and SQLite, among others. Tell us which one, as syntax and functions differ.
Yes. Send the scenario and requirements. You get an ERD, normalised tables with keys and the CREATE TABLE scripts.
Yes. Queries are run against your schema or a matching sample dataset, and the expected output is included.
Organising tables so each fact is stored in one place. It prevents update errors where changing one copy of a value leaves another copy out of date.