09 / 32 · 5 min
Asking questions: SELECT
SQL is declarative: you describe the answer, not how to find it. The clauses always run in this order, which is not the order you write them, and that single fact explains most SQL confusion.
FROM → which tables
WHERE → which rows (before grouping)
GROUP BY → collapse into groups
HAVING → which groups (after grouping)
SELECT → which columns
ORDER BY → arrange
LIMIT → how manySELECT dept_id, COUNT(*) AS students, AVG(cgpa) AS avg_cgpa
FROM students
WHERE cgpa IS NOT NULL
GROUP BY dept_id
HAVING COUNT(*) >= 10
ORDER BY avg_cgpa DESC;Two things trip everyone up.
WHERE and HAVING are not interchangeable. WHERE filters rows before grouping; HAVING filters groups after. "Departments with at least ten students" is a fact about a group, so it cannot be a WHERE.
NULL is not a value. It means unknown, so cgpa = NULL is never true: not even for a row where cgpa is null. Use IS NULL. Aggregates skip nulls entirely: AVG(cgpa) over 100 students with 10 nulls divides by 90, not 100.
Takeaway: read a query in execution order, not written order, and treat NULL as "unknown" rather than "empty".