How to Order a General Catalog Query
Intended Audience
This document is intended for SQL writers that need to understand the sql technique for ordering a query when one or more of the columns involved is nullable.
Sorting Nullable Columns in Oracle SQL
The preferred order of a summary report of classes in the catalog is:
- Course number only
- Course number with suffix
- Course number with prefix
- Course number with second suffix
1. First version
At first glance, the following query would produce the order above:
SELECT
dept_cd, crse_num, crse_suffix, crse_prefix, crse_suffix2
FROM
sisgco.catlg_crse_info_tb
WHERE
dept_cd = 'MATH'
ORDER BY
crse_num, crse_suffix, crse_prefix, crse_suffix2
Because the prefix and suffix columns are nullable, and null values sort last, the result is not quite what we want:
| DEPT_CD | CRSE_NUM | CRSE_SUFFIX | CRSE_PREFIX | CRSE_SUFFIX2 |
|---|---|---|---|---|
| MATH | 001 | A | null | null |
| MATH | 001 | B | H | null |
| MATH | 001 | B | null | null |
| MATH | 016 | A | null | null |
| MATH | 016 | B | null | null |
| MATH | 024 | null | null | null |
| MATH | 032 | null | null | null |
| MATH | 039 | null | null | null |
| MATH | 049 | null | null | null |
| MATH | 053 | M | null | null |
| MATH | 053 | null | H | null |
| MATH | 053 | null | null | null |
| MATH | 054 | M | null | null |
| MATH | 054 | null | H | null |
| MATH | 054 | null | null | null |
2. Correct version
The solution lies in the SQL built-in function DECODE, which allows case-like expressions to be included in a query. Here we are simply replacing null values with ascii space so that lexical sort is correct. Because the course number column is a zero-padded VARCHAR, we use the TO_NUMBER function to strip leading zeros and obtain a numeric rather than lexical sort, and use the concatenation operator to present the course name:
SELECT
dept_cd || ' ' || crse_prefix || TO_NUMBER(crse_num) ||
crse_suffix || crse_suffix2 as course,
TO_NUMBER(crse_num) as cat_num,
DECODE(crse_suffix, null, ' ', crse_suffix) as s0,
DECODE(crse_prefix, null, ' ', crse_prefix) as p,
DECODE(crse_suffix2, null, ' ', crse_suffix2) as s1
FROM
sisgco.catlg_crse_info_tb@SISGCO_SS1P
WHERE
dept_cd = 'MATH'
ORDER BY cat_num, s0, p, s1
Our new result is presented in a user-friendly format, and almost correctly ordered:
| COURSE | CAT_NUM | S0 | P | S1 |
|---|---|---|---|---|
| MATH 1A | 1 | A | ||
| MATH 1B | 1 | B | ||
| MATH H1B | 1 | B | H | |
| MATH 16A | 16 | A | ||
| MATH 16B | 16 | B | ||
| MATH 24 | 24 | |||
| MATH 32 | 32 | |||
| MATH 39 | 39 | |||
| MATH 49 | 49 | |||
| MATH 53 | 53 | |||
| MATH H53 | 53 | H | ||
| MATH 53M | 53 | M | ||
| MATH 54 | 54 | |||
| MATH H54 | 54 | H | ||
| MATH 54M | 54 | M |
Why "almost?" The order of the Math 53 sequence is incorrect, because the prefixed course appears before the suffixed course. Since courses with a suffix but no prefix should appear before those with a prefix, we need to sort by prefix first. So we change the ORDER BY clause from:
ORDER BY cat_num, s0, p, s1
to
ORDER BY cat_num, p, s0, s1
Our new result is in the desired order:
| COURSE | CAT_NUM | S0 | P | S1 |
|---|---|---|---|---|
| MATH 1A | 1 | A | ||
| MATH 1B | 1 | B | ||
| MATH H1B | 1 | B | H | |
| MATH 16A | 16 | A | ||
| MATH 16B | 16 | B | ||
| MATH 24 | 24 | |||
| MATH 32 | 32 | |||
| MATH 39 | 39 | |||
| MATH 49 | 49 | |||
| MATH 53 | 53 | |||
| MATH 53M | 53 | M | ||
| MATH H53 | 53 | H | ||
| MATH 54 | 54 | |||
| MATH 54M | 54 | M | ||
| MATH H54 | 54 | H |


