01
Relational databases and SELECT basics
The vocabulary of relational databases
A relational database keeps data in tables (grids of rows and columns) and links tables to each other through key values. Theory uses different names for the same things, so it's worth learning the pairs.
| Theory (relational model) | In a table | What it means |
|---|---|---|
| Relation | Table | A set of rows that share the same structure |
| Tuple | Row | One instance |
| Attribute | Column | One property |
| Degree | Number of columns | How many attributes there are |
| Cardinality | Number of rows | How many tuples there are |
| Domain | Range of values for a column | The set of values an attribute is allowed to take |
Relations have a few properties. No two tuples can be exactly the same, there's no order among tuples or among attributes, and every value is atomic — it can't be broken down any further.
Keys come in several kinds, too.
| Key | Condition |
|---|---|
| Super key | Uniqueness only — any combination of columns that can tell rows apart |
| Candidate key | Uniqueness + minimality — a candidate for the primary key |
| Primary key (PK) | The candidate key picked to represent the table. No NULLs, no duplicates |
| Alternate key | The candidate keys that weren't picked as the primary key |
| Foreign key (FK) | A column that references another table's primary key |
The rules that keep data correct at all times are called integrity constraints. The classic ones are entity integrity (a primary key can't be NULL or duplicated), referential integrity (a foreign key value must exist in the table it references, or be NULL) and domain integrity (a column value must stay inside its defined domain).
The theoretical operations behind relational databases are called relational algebra. Here's how each part of SQL lines up with them.
| Type | Operation | Symbol | In SQL |
|---|---|---|---|
| Pure relational operators | SELECT (selection) | σ | WHERE — picks the rows that match a condition |
| PROJECT (projection) | π | SELECT list — picks the columns you want | |
| JOIN | ⋈ | JOIN — attaches two relations on a shared value | |
| DIVISION | ÷ | Keeps only rows that have every value on the dividing side (no direct SQL syntax) | |
| Set operators | Union / intersection / difference | ∪ / ∩ / − | UNION / INTERSECT / MINUS (EXCEPT) |
| Cartesian product | × | CROSS JOIN |
The four kinds of SQL statements
| Kind | Commands | What they do |
|---|---|---|
| DML (manipulation) | SELECT, INSERT, UPDATE, DELETE, MERGE | Query, add, change and remove data. SELECT on its own is sometimes called DQL |
| DDL (definition) | CREATE, ALTER, DROP, RENAME, TRUNCATE | Create, change and remove structures such as tables |
| DCL (control) | GRANT, REVOKE | Give and take away privileges |
| TCL (transaction control) | COMMIT, ROLLBACK, SAVEPOINT | Make changes final or undo them |
SELECT and WHERE
Here's the skeleton of a SELECT.
SELECT [DISTINCT] column [AS alias], ... FROM table WHERE condition ORDER BY column [ASC | DESC];
- DISTINCT removes duplicate rows from the result (a row counts as a duplicate only if the whole SELECT list matches)
- An alias is attached with AS; if it contains spaces or special characters, wrap it in double quotes
- To join strings together, Oracle uses || and SQL Server uses + (both have the CONCAT function)
These are the operators you'll use in WHERE.
| Operator | Meaning | Watch out |
|---|---|---|
| =, <>, !=, ^=, >, <, >=, <= | Comparison | <>, != and ^= all mean "not equal" |
| BETWEEN a AND b | From a up to b | Both ends are included. Put the smaller value first |
| IN (a, b, c) | Equal to one of the values in the list | If a NOT IN list contains a NULL, no rows come back at all |
| LIKE 'A%' | Pattern | % is any run of zero or more characters, _ is exactly one character. Use ESCAPE to search for % itself |
| IS NULL / IS NOT NULL | Is it NULL? | = NULL is always unknown, so it returns no rows |
When you chain several conditions, precedence matters. According to the Oracle docs, the order is parentheses → comparison operators → NOT → AND → OR. AND comes before OR, so A OR B AND C is evaluated as A OR (B AND C). If you're ever unsure, add parentheses — it's the safe bet.
ORDER BY: sorting, and where NULLs end up
ORDER BY sorts in ascending order (ASC) by default, and you can sort by a column name, an alias or a position number in the SELECT list (1, 2 …). The tricky part is NULL. Oracle treats NULL as the largest value, so in ascending order it goes last; SQL Server treats it as the smallest, so it goes first. In Oracle you can place NULLs yourself with NULLS FIRST or NULLS LAST.
The order a SELECT is processed in
A SELECT statement isn't processed in the order it's written. Once you know the real order, questions like “Why can't I use an alias in WHERE?” and “Why can't I put SUM in WHERE?” answer themselves. Step through the figure below.
SELECT DEPT_CODE, SUM(SALARY) AS TOTAL▶FROM EMPWHERE SALARY >= 260GROUP BY DEPT_CODEHAVING SUM(SALARY) >= 280ORDER BY TOTAL DESC;
EMP
| EMP_NO | NAME | DEPT_CODE | SALARY |
|---|---|---|---|
| 101 | Ava | D10 | 300 |
| 102 | Ben | D10 | 250 |
| 103 | Chris | D20 | 400 |
| 104 | Dana | D20 | 350 |
| 105 | Eli | NULL | 280 |
| 106 | Fay | D30 | 260 |
Step 1 — FROM
- Processing order
- FROM
Step through it. The order it's written in (SELECT on top) isn't the order it's processed in.
FROM comes first. It pulls in all 6 rows of the EMP table. SELECT is written at the top, but it's actually handled near the very end.
To sum up: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. An alias is created in SELECT, so only the ORDER BY that comes after it can use it.
If a question says… → pick this
- “Number of columns / rows in a relation”Degree / cardinality
- “Satisfies both uniqueness and minimality”Candidate key
- “A foreign key must be a value in the referenced table, or NULL”Referential integrity
- “The relational operation that picks only the columns you want”PROJECT (π)
- “GRANT, REVOKE”DCL
- “BETWEEN 100 AND 200”Includes 100 and 200
- “A NULL in a NOT IN list”0 rows
- “Where NULL goes in an Oracle ascending sort”Last (first in SQL Server)
- “Why WHERE can't use a SELECT alias”WHERE is processed before SELECT
02
Functions, GROUP BY and HAVING
You've pulled columns out with SELECT. Now it's time to reshape values (functions) and to bundle rows up and count them (GROUP BY).
Single-row functions: one value back per row
Functions come in two kinds. Single-row functions return one result for each row, and multi-row functions (aggregate functions) bundle many rows into a single result. You can use single-row functions anywhere — SELECT, WHERE, ORDER BY — and you can nest one function inside another.
| Kind | Oracle | SQL Server | Example and result |
|---|---|---|---|
| Character | LOWER, UPPER | LOWER, UPPER | UPPER('sql') → 'SQL' |
| SUBSTR | SUBSTRING | SUBSTR('DATABASE', 3, 4) → 'TABA' | |
| LENGTH | LEN | LENGTH('SQL') → 3 | |
| LTRIM, RTRIM, TRIM | LTRIM, RTRIM, TRIM | TRIM(' A ') → 'A' | |
| CONCAT, || | CONCAT, + | 'A' || 'B' → 'AB' | |
| Number | ROUND, TRUNC | ROUND | ROUND(38.567, 1) → 38.6 / TRUNC(38.567, 1) → 38.5 |
| CEIL, FLOOR | CEILING, FLOOR | CEIL(3.1) → 4 / FLOOR(-3.1) → -4 | |
| MOD | % | MOD(7, 3) → 1 | |
| Date | SYSDATE | GETDATE() | The current date and time |
| EXTRACT | DATEPART | EXTRACT(YEAR FROM HIRE_DATE) |
In Oracle, adding a number to a date counts in days: HIRE_DATE + 1 is the next day, and HIRE_DATE + 1/24 is an hour later. One more thing: Oracle treats an empty string (‘’) as NULL, while SQL Server keeps empty strings and NULL apart.
Type conversion and CASE
There are two ways to change a data type: explicit conversion, where you call a function yourself (Oracle: TO_CHAR, TO_NUMBER, TO_DATE; SQL Server: CAST, CONVERT), and implicit conversion, where the database does it for you. Implicit conversion is handy, but it can convert things in ways you didn't expect or stop an index from being used, so it's a good habit to convert explicitly.
To return different values depending on a condition, use CASE. There's the simple CASE, which compares against a single value, and the searched CASE, which takes conditions. Oracle also has the DECODE function, which does the same job.
SELECT NAME,
CASE WHEN SALARY >= 350 THEN 'High'
WHEN SALARY >= 280 THEN 'Mid'
ELSE 'Low' END AS GRADE, -- without ELSE you get NULL
DECODE(DEPT_CODE, 'D10', 'Sales', 'D20', 'Dev', 'Other') AS DEPT_NAME
FROM EMP;Functions for handling NULL
| Function | Returns | Example |
|---|---|---|
| NVL(a, b) / ISNULL(a, b) | b if a is NULL, otherwise a | NVL(BONUS, 0) |
| NVL2(a, b, c) | b if a isn't NULL, c if it is (Oracle) | NVL2(BONUS, 'Yes', 'No') |
| NULLIF(a, b) | NULL if a and b are equal, otherwise a | NULLIF(10, 10) → NULL |
| COALESCE(a, b, c, ...) | The first value that isn't NULL | COALESCE(NULL, NULL, 3) → 3 |
Aggregate functions and NULL
Aggregate functions like COUNT, SUM, AVG, MAX and MIN bundle many rows into a single value. There's one rule to remember: every one of them except COUNT(*) ignores NULLs. Try switching functions in the figure below.
EMP
| NAME | SALARY | BONUS |
|---|---|---|
| Ava | 300 | 50 |
| Ben | 250 | NULL |
| Chris | 400 | NULL |
| Dana | 350 | 30 |
| Eli | 280 | NULL |
| Fay | 260 | 20 |
COUNT(*) = 6
- Rows used
- 6 / 6
Switch functions. Green rows went into the calculation; struck-through rows were left out.
COUNT(*) counts the rows themselves. A row whose BONUS is NULL is still a row, so the answer is 6.
Put DISTINCT inside an aggregate function and each duplicate value is counted only once (COUNT(DISTINCT DEPT_CODE)). If no rows match at all, COUNT returns 0, while SUM, AVG, MAX and MIN return NULL.
GROUP BY and HAVING
GROUP BY bundles rows that share the same value and aggregates each group. Once rows are grouped, each group becomes a single line, and that brings some rules with it.
- Any column in the SELECT list that isn't inside an aggregate function must also be in GROUP BY
- Don't use SELECT aliases in GROUP BY — given the processing order, the alias doesn't exist yet (the latest Oracle 23ai allows it, but exams go by the traditional rule)
- NULLs form a group of their own
- GROUP BY doesn't guarantee any order — if you need one, use ORDER BY
To put a condition on groups, use HAVING. WHERE filters rows before they're grouped; HAVING filters groups after they're formed. That's why conditions on aggregate functions can only go in HAVING. If a condition doesn't need an aggregate function, filtering it early in WHERE is the better choice, since it leaves fewer rows to process.
SELECT DEPT_CODE, COUNT(*) AS HEADCOUNT, AVG(SALARY) AS AVG_SAL FROM EMP WHERE SALARY >= 250 -- filter rows first GROUP BY DEPT_CODE HAVING COUNT(*) >= 2; -- then filter the groups
If a question says… → pick this
- “SUBSTR('DATABASE', 3, 4)”'TABA'
- “An empty string in Oracle”Treated as NULL
- “NULL if a and b are equal”NULLIF(a, b)
- “The first value that isn't NULL”COALESCE
- “The aggregate function that counts NULLs”Only COUNT(*)
- “AVG(BONUS) vs AVG(NVL(BONUS, 0))”They divide by a different number of rows
- “The clause for conditions on aggregate functions”HAVING
- “A column not in GROUP BY in the SELECT list”Error unless it's wrapped in an aggregate function
Pro only
That’s the free part of this guide
The other 5 chapters are open to Pro subscribers. Each one continues with 3D visuals and “if a question says…” tips, and the last chapter lets you check yourself with a short quiz.
- 03Joins
- 04Subqueries, views and set operators
- 05Group functions, window functions and Top N
- 06Self joins, hierarchical queries, PIVOT and regular expressions
- 07Data management statements: DML, TCL, DDL and DCL
Advertisement
PasslyExam
You have read the concept. Now test it on exam questions
PasslyExam offers original practice questions, explanations and full mock exams built around each certification’s exam guide. The explanations cover why the right option is right and why the look-alike options are wrong. Sign up to practice for free every day.
Exams that cover this concept