Based on the SQLD exam syllabusReport an error
ROWNUM = 2 and ROWNUM > 1 are not errors but return 0 rows.(+) on the side that will be filled with NULL (the deficient side).A SELECT statement starts with SELECT, but Oracle processes it in a different order. Execution-order problems are solved by recasting them as "is that name visible in this clause?"
| Written order | Processing order | What happens at this step | What is visible at this step |
|---|---|---|---|
| 1 SELECT | 5 | Computes the expressions to output and assigns aliases | Grouping columns, aggregate results |
| 2 FROM | 1 | Reads and joins the tables | All columns of the tables |
| 3 WHERE | 2 | Filters rows one by one | Table columns (no aggregates or aliases) |
| 4 GROUP BY | 3 | Groups the rows | Table columns |
| 5 HAVING | 4 | Filters the groups | Grouping columns, aggregates |
| 6 ORDER BY | 6 | Sorts the result | SELECT aliases, table columns |
This one table explains the four errors and behaviors below.
ORDER BY comes after SELECT, so the alias already exists.
SELECT ENAME, SAL * 12 AS ANNUAL FROM EMP WHERE DEPTNO = 10 ORDER BY ANNUAL DESC;ENAME ANNUAL
---------- ----------
KING 60000
CLARK 29400
MILLER 15600If you use the same alias in WHERE, it is a name that has not been created yet.
SELECT ENAME, SAL * 12 AS ANNUAL FROM EMP WHERE ANNUAL > 30000;SELECT ENAME, SAL * 12 AS ANNUAL FROM EMP WHERE ANNUAL > 30000
*
ERROR at line 1:
ORA-00904: "ANNUAL": invalid identifierWHERE SAL * 12 > 30000).GROUP BY alias and HAVING alias, so it runs without error (W4 Day 2). In versions before 23ai, it is ORA-00904.WHERE is the stage before grouping rows. The per-department average has not been computed yet.
SELECT DEPTNO, AVG(SAL) FROM EMP WHERE AVG(SAL) > 2000 GROUP BY DEPTNO;SELECT DEPTNO, AVG(SAL) FROM EMP WHERE AVG(SAL) > 2000 GROUP BY DEPTNO
*
ERROR at line 1:
ORA-00934: group function is not allowed hereMove the aggregate condition to HAVING, which filters after grouping.
SELECT DEPTNO, ROUND(AVG(SAL)) AS AVG_SAL FROM EMP GROUP BY DEPTNO HAVING AVG(SAL) > 2000 ORDER BY AVG_SAL DESC; DEPTNO AVG_SAL
---------- ----------
10 2917
20 2175When you group by department, one department becomes one row. You cannot decide which of the several employee names to show in that one row.
SELECT DEPTNO, ENAME, SUM(SAL) FROM EMP GROUP BY DEPTNO;SELECT DEPTNO, ENAME, SUM(SAL) FROM EMP GROUP BY DEPTNO
*
ERROR at line 1:
ORA-00979: "ENAME": must appear in the GROUP BY clause or be used in an aggregate functionnot a GROUP BY expression. 23ai also tells you the name of the offending column. The number is the same.Writing a condition on a grouping column rather than an aggregate in HAVING is not an error either.
SELECT DEPTNO, COUNT(*) AS CNT FROM EMP GROUP BY DEPTNO HAVING DEPTNO > 10 ORDER BY DEPTNO; DEPTNO CNT
---------- ----------
20 5
30 6WHERE DEPTNO > 10 and then grouping. The WHERE version reduces rows before grouping, and the HAVING version discards groups after everything is grouped.HAVING SAL > 1000, it is ORA-00979.| Confusing pair | WHERE | HAVING |
|---|---|---|
| What it filters | Rows | Groups |
| Aggregate functions | Cannot be used (ORA-00934) | Can be used |
| Non-aggregate conditions | Can be used | Can be used if it is a grouping column |
| Without GROUP BY | Can be used | Can be used. The whole table becomes one group |
ROWNUM is a number assigned to rows that pass WHERE. ORDER BY is last in processing order, so within the same SELECT, numbers are assigned and the cut is made first, and then sorting happens.
SELECT ROWNUM, ENAME, SAL FROM EMP WHERE ROWNUM <= 3 ORDER BY SAL DESC; ROWNUM ENAME SAL
---------- ---------- ----------
2 ALLEN 1600
3 WARD 1250
1 SMITH 800If you move the sort inside an inline view, the numbers are assigned to the sorted result.
SELECT ROWNUM AS RN, ENAME, SAL FROM (SELECT ENAME, SAL FROM EMP ORDER BY SAL DESC, ENAME) WHERE ROWNUM <= 3; RN ENAME SAL
---------- ---------- ----------
1 KING 5000
2 FORD 3000
3 SCOTT 3000With ROWNUM = 2, the first row receives number 1 and is rejected, then the next row becomes a candidate for number 1 again, so number 2 is never created (W6 Day 3).
SELECT COUNT(*) FROM EMP WHERE ROWNUM = 2; COUNT(*)
----------
0ROWNUM > 1 is also 0 rows for the same reason. The fact that it does not raise an error is where the answer choices diverge.If you list tables in FROM without a join condition, every row pairs with every other row. That is 14 rows of EMP × 4 rows of DEPT.
SELECT COUNT(*) FROM EMP, DEPT; COUNT(*)
----------
56If you join three tables but write only one condition, a product still arises with the table that has no condition. SALGRADE has 5 rows.
SELECT COUNT(*) FROM EMP E, DEPT D, SALGRADE S WHERE E.DEPTNO = D.DEPTNO; COUNT(*)
----------
70SELECT COUNT(*) FROM EMP E, DEPT D, SALGRADE S WHERE E.DEPTNO = D.DEPTNO AND E.SAL BETWEEN S.LOSAL AND S.HISAL; COUNT(*)
----------
14| Number of tables | Join conditions needed (minimum) | If one condition is missing |
|---|---|---|
| 2 | 1 | 14 × 4 = 56 rows |
| 3 | 2 | (EMP⋈DEPT 14 rows) × SALGRADE 5 rows = 70 rows |
| n | n−1 | Each missing link creates a product |
=. The n−1 is the number of conditions, and they need not be equality (a non-equi join).(+) goes on the side that will be padded with a NULL row when there is no match. If you want to keep all departments, leave DEPT as it is and put (+) on the EMP-side column.
| Side you want to keep | Expression to write | Converted to ANSI |
|---|---|---|
| All departments | D.DEPTNO = E.DEPTNO(+) | DEPT D LEFT JOIN EMP E |
| All employees | E.DEPTNO = D.DEPTNO(+) | EMP E LEFT JOIN DEPT D |
We want to attach CLERK employees to each department while still showing departments that have no CLERK. In the (+) notation, you must put (+) on every EMP-side condition for the outer join to hold.
SELECT D.DNAME, E.ENAME FROM DEPT D, EMP E WHERE D.DEPTNO = E.DEPTNO(+) AND E.JOB(+) = 'CLERK' ORDER BY D.DNAME, E.ENAME;DNAME ENAME
-------------- ----------
ACCOUNTING MILLER
OPERATIONS
RESEARCH ADAMS
RESEARCH SMITH
SALES JAMESIf you leave out one (+) on E.JOB, OPERATIONS disappears.
SELECT D.DNAME, E.ENAME FROM DEPT D, EMP E WHERE D.DEPTNO = E.DEPTNO(+) AND E.JOB = 'CLERK' ORDER BY D.DNAME, E.ENAME;DNAME ENAME
-------------- ----------
ACCOUNTING MILLER
RESEARCH ADAMS
RESEARCH SMITH
SALES JAMESNULL = 'CLERK' is not true, so it drops out. The result is the same as an inner join.LEFT JOIN EMP E ON D.DEPTNO = E.DEPTNO AND E.JOB = 'CLERK' gives the 5 rows above, and ON D.DEPTNO = E.DEPTNO WHERE E.JOB = 'CLERK' gives 4 rows. In the practice container, each produced the same rows as the two results above.| Position of the inner table (EMP) condition | When it applies | Department with no match |
|---|---|---|
WHERE condition with (+), or ON | While pairing | Remains as NULL |
WHERE condition without (+) | After the outer join finishes | Dropped (becomes the same as an inner join) |
⚠️ Pitfall: In an outer join problem, if WHERE has a condition on a column of the table that will be NULL-padded without
(+), the number of result rows is the same as an inner join even though you used an outer join. When counting rows for the answer choices, remove the NULL rows first.
NATURAL JOIN and USING merge the common column into one. The merged column belongs to neither table, so attaching E. or D. is an error (W4 Day 5).
SELECT D.DEPTNO, DNAME FROM EMP E JOIN DEPT D USING (DEPTNO) WHERE E.ENAME = 'KING';SELECT D.DEPTNO, DNAME FROM EMP E JOIN DEPT D USING (DEPTNO) WHERE E.ENAME = 'KING'
*
ERROR at line 1:
ORA-25154: column part of USING clause cannot have qualifierSELECT E.DEPTNO, DNAME FROM EMP E NATURAL JOIN DEPT D WHERE ENAME = 'KING';SELECT E.DEPTNO, DNAME FROM EMP E NATURAL JOIN DEPT D WHERE ENAME = 'KING'
*
ERROR at line 1:
ORA-25155: column used in NATURAL join cannot have qualifierIf you remove the alias, both run. You may attach an alias to E.ENAME, which is not a common column.
SELECT DEPTNO, DNAME FROM EMP E JOIN DEPT D USING (DEPTNO) WHERE E.ENAME = 'KING'; DEPTNO DNAME
---------- --------------
10 ACCOUNTING| Join method | Alias on the common column | Error number |
|---|---|---|
JOIN … ON | Must be attached | ORA-00918 if omitted |
JOIN … USING (column) | Must not be attached | ORA-25154 |
NATURAL JOIN | Must not be attached | ORA-25155 |
A subquery after a single-row operator such as = or > must return one row or fewer. The two ANALYSTs both have a salary of 3000, but there are two rows.
SELECT ENAME, SAL FROM EMP WHERE SAL = (SELECT SAL FROM EMP WHERE JOB = 'ANALYST');SELECT ENAME, SAL FROM EMP WHERE SAL = (SELECT SAL FROM EMP WHERE JOB = 'ANALYST')
*
ERROR at line 1:
ORA-01427: single-row subquery returns more than one rowWHERE SAL = (SELECT SAL FROM EMP WHERE DEPTNO = 40) was 0.IN. SAL IN (SELECT SAL FROM EMP WHERE JOB = 'ANALYST') gives the two rows FORD and SCOTT.We stack the jobs in department 10 (3 rows) and department 20 (5 rows) one above the other.
SELECT JOB FROM EMP WHERE DEPTNO = 10 UNION SELECT JOB FROM EMP WHERE DEPTNO = 20 ORDER BY JOB;JOB
---------
ANALYST
CLERK
MANAGER
PRESIDENTSELECT JOB FROM EMP WHERE DEPTNO = 10 UNION ALL SELECT JOB FROM EMP WHERE DEPTNO = 20 ORDER BY JOB;JOB
---------
ANALYST
ANALYST
CLERK
CLERK
CLERK
MANAGER
MANAGER
PRESIDENT| Confusing pair | UNION | UNION ALL |
|---|---|---|
| Duplicates | Removed across the whole result | Left as they are |
| Row count | May be less than 3 + 5 (4 here) | Always 3 + 5 = 8 |
| Duplicates within the same side | Removed too | Left as they are |
BETWEEN a AND b is >= a AND <= b. MARTIN and WARD at 1250 and ALLEN at 1600 are all included.
SELECT ENAME, SAL FROM EMP WHERE SAL BETWEEN 1250 AND 1600 ORDER BY SAL, ENAME;ENAME SAL
---------- ----------
MARTIN 1250
WARD 1250
MILLER 1300
TURNER 1500
ALLEN 1600BETWEEN 1600 AND 1250 is >= 1600 AND <= 1250, so it returns 0 rows with no error.NOT BETWEEN a AND b is < a OR > b. The boundary values are excluded from the result.% is zero or more characters, and _ is exactly one character. These are the employees whose second letter is A.
SELECT ENAME FROM EMP WHERE ENAME LIKE '_A%' ORDER BY ENAME;ENAME
----------
JAMES
MARTIN
WARDTo find _ itself, put the character designated by ESCAPE in front of it to turn off its wildcard meaning. We apply two patterns to the three values 'A_B', 'AXB', and 'AB'.
SELECT V FROM (SELECT 'A_B' AS V FROM DUAL UNION ALL SELECT 'AXB' FROM DUAL UNION ALL SELECT 'AB' FROM DUAL) WHERE V LIKE 'A_B' ORDER BY V;V
---
AXB
A_BSELECT V FROM (SELECT 'A_B' AS V FROM DUAL UNION ALL SELECT 'AXB' FROM DUAL UNION ALL SELECT 'AB' FROM DUAL) WHERE V LIKE 'A\_B' ESCAPE '\' ORDER BY V;V
---
A_B| Pattern | 'A_B' | 'AXB' | 'AB' | Reason |
|---|---|---|---|---|
'A_B' | O | O | X | _ is any single character. 'AB' has no middle character |
'A\_B' ESCAPE '\' | O | X | X | \_ is the underscore character itself |
'A%B' | O | O | O | % can also be zero characters |
\. With ESCAPE '#', you write 'A#_B'. Without an ESCAPE clause, \ is just an ordinary character.Tomorrow we will pair up and organize how the same SQL differs between Oracle and SQL Server (string concatenation, Top N, date functions, and so on).
% and _ in a LIKE pattern be searched for literally.Click a choice to reveal the answer and explanation.
Question 1
Which is the correct logical execution order of a SELECT statement?
Question 2
What is the result of SELECT COUNT(*) FROM EMP WHERE ROWNUM = 2;? (EMP has 14 rows)
Question 3
What is the result of SELECT COUNT(*) FROM DEPT D LEFT JOIN EMP E ON D.DEPTNO = E.DEPTNO WHERE E.JOB = 'CLERK';? (CLERKs are 1 in department 10, 2 in department 20, and 1 in department 30, and department 40 has no employees)
Question 4
What is the result of running SELECT ENAME FROM EMP WHERE SAL = (SELECT SAL FROM EMP WHERE JOB = 'ANALYST');? (The ANALYSTs are the two employees FORD and SCOTT, and both have a salary of 3000)
Question 5
What is the result of SELECT COUNT(*) FROM EMP WHERE SAL BETWEEN 1250 AND 1600;? (There are 2 employees with a salary of 1250 and 1 each with 1300, 1500, and 1600, and the rest are outside this range)