In PostgreSQL, I would like to select a row based on some criteria, but if no row matches the criteria, I would like to return the first row. The table actually contains an ordinal column, so the task should be easier (the first row is the one with ordinal 0). For example:
SELECT street, zip, city
FROM address
WHERE street LIKE 'Test%' OR ord = 0
LIMIT 1;
But in this case, there is no way to guarantee the order of the records that match, and I have nothing to order them by. What would be the way to do this using a single SELECT
statement?
Shorter (and correct)
You don't actually need a
WHERE
clause at all:!~~
is just the Postgres operator forNOT LIKE
. You can use either. Note that by inverting the logic (NOT LIKE
instead ofLIKE
), we can now use defaultASC
sort order and NULLs sort last, which may be important. Read on.This is shorter (but not necessarily faster). It is also subtly different (more reliable) than the currently accepted answer by @Gordon.
When sorting by a
boolean
expression you must understand how it works:The currently accepted answer uses
ORDER BY <boolean expression> DESC
, which would sort NULLs first. In such a case you should typically addNULLS LAST
:If
street
is definedNOT NULL
this is obviously irrelevant, but that has not been defined in the question. (Always provide the table definition.) The currently accepted answer avoids the problem by excluding NULL values in theWHERE
clause.Some other RDBMS (MySQL, Oracle, ..) don't have a proper
boolean
type like Postgres, so we often see incorrect advice from people coming from those products.Your current query (as well as the currently accepted answer) need the
WHERE
clause - or at leastNULLS LAST
. With the different expression inORDER BY
neither is necessary.More importantly, yet, if multiple rows have a matching
street
(which is to be expected), the returned row would be arbitrary and could change between calls - generally an undesirable effect. This query picks the row with the smallestord
to break ties and produces a stable result.This form is also more flexible in that it does not rely on the existence of a row with
ord = 0
. Instead, the row with the smallestord
is picked either way.Faster with index
(And still correct.) For big tables, the following index would radically improve performance of this query:
Detailed explanation:
Depending on undefined details it may pay to add more columns to the index.
The fastest query using this index:
BTW, this is a single statement.
This is more verbose, but allows for a simpler query plan. The second
SELECT
of theUNION ALL
is never executed if the firstSELECT
produces enough rows (in our case: 1). If you test withEXPLAIN ANALYZE
, you'll see(never executed)
in the query plan.Details:
Evaluation of
UNION ALL
In reply to Gordon's comment. Per documentation:
Bold emphasis mine.
And
LIMIT
makes Postgres stop evaluating as soon as enough rows are found. That's why you see(never executed)
in the output ofEXPLAIN ANALYZE
.If you add an outer
ORDER BY
before the finalLIMIT
this optimization is not possible. Then all rows have to be collected to see which might sort first.