skip to content

What do MIN and MAX return for VARCHAR and DATE columns, and what decides the order?

level: middleimportance: should knowfreq 44%

answer

  1. Same type in, same type out
  2. Ordering is a property of the type
  3. For text, the environment can change the answer
  4. '9' versus '11' stored as text
  5. Two extremes, two different source rows

basics

~20 s

MIN and MAX return a value of the argument's own type — the earliest and latest DATE, the first and last string. Dates order chronologically; character data orders by the column's collation, so '9' sorts after '10'.

solid answer

~40 s

`MIN` and `MAX` work on any type with a defined ordering, and they return a value of that same declared type: `MAX(hire_date)` is a `DATE`, `MIN(city)` is a string. What "largest" means comes from the type's ordering rules. Dates and timestamps order chronologically, so `MAX` is the latest instant. Character data orders by the column's **collation**, which decides case sensitivity, accent handling and the relative order of digits and letters — so the same query can give different answers on two databases holding identical data. The classic trap is numbers stored as text: over `'9'`, `'10'`, `'11'`, `MAX(version_label)` returns `'9'`, because comparison proceeds character by character and `'9'` beats `'1'`. If you want a numeric maximum, cast the argument: `MAX(CAST(version_label AS INTEGER))`.

code

sql · 3 lines
sql
-- version_label is VARCHAR holding '9', '10', '11'
SELECT MAX(version_label) FROM releases;                    -- '9'  (string order)
SELECT MAX(CAST(version_label AS INTEGER)) FROM releases;    -- 11   (numeric order)

go deeper

for a junior

Know that MIN and MAX work on dates and text as well as numbers and return the same type, and be ready to say what MAX over the text values '9', '10' and '11' gives.

for a middle

Explain where the ordering comes from — chronological for temporal types, collation-defined for character data — and name the numbers-stored-as-text trap plus its cast fix.

for a senior

Show the operational angle: a report whose alphabetical extreme changes between environments is a collation contract problem, and text keys that must sort numerically should be zero-padded at write time rather than cast on every read.

for a principal

Own it at the schema level: decide and document the collation as part of the data contract, and rule that identifiers which need numeric ordering are stored numerically or fixed-width, so no query author has to compensate.

## The signature `MIN` and `MAX` are the two core aggregates that are not arithmetic. They require only that the argument's type has a defined ordering, which in practice covers numeric types, character strings, `DATE`, `TIME` and `TIMESTAMP`. They return the extreme value **in the argument's own declared type** — no promotion, no conversion: ```sql SELECT MIN(hire_date) AS first_hire, -- a DATE MAX(hire_date) AS latest_hire, -- a DATE MIN(city) AS first_city -- a character string FROM employees; ``` That is why they are the workhorses of "earliest", "latest", "alphabetically first" questions, and why they show up in far more real queries than `SUM` does. ## Dates and timestamps: chronological, and unambiguous Temporal types order chronologically, so `MAX(order_date)` is the most recent order date and `MIN(order_date)` is the oldest. There is no ambiguity in the ordering itself. Two details matter in practice: - **A `DATE` is not a `TIMESTAMP`.** `MAX(order_date)` over a `DATE` column gives a day, not the last moment of that day. Comparing it against a timestamp requires an explicit conversion, and mixing them in a predicate is where off-by-one-day bugs live. - **Time zones follow the type.** For `TIMESTAMP WITH TIME ZONE`, the comparison is on the underlying instant; for `TIMESTAMP` without a zone, it is on the wall-clock value as stored. `MAX` does not reconcile zones for you. ## Character data: the collation decides everything For character types, the ordering is not a property of SQL but of the **collation** attached to the column, expression or database. A collation defines the comparison rules: whether `'apple'` sorts before or after `'Banana'`, how accented characters compare with unaccented ones, and how digits and punctuation rank against letters. The consequence is uncomfortable but important: `MAX(city)` over identical data can legitimately return different answers on two databases with different collations. A binary collation compares raw code points, so all uppercase letters precede all lowercase ones and `'Zebra' < 'apple'`. A linguistic collation typically compares case-insensitively at the primary level, so `'apple' < 'Zebra'`. If a query's result must be stable across environments, the collation is part of that contract, not an implementation detail. ## The numbers-as-text trap This is the version of the question interviewers actually ask: ```sql -- releases.version_label is VARCHAR and holds '9', '10', '11' SELECT MAX(version_label) FROM releases; -- '9' ``` String comparison is positional: it compares the first characters, and `'9'` is greater than `'1'`, so `'9'` wins outright and the remaining characters are never examined. The same effect makes `'100' < '99'` and makes `MAX(invoice_no)` wrong whenever invoice numbers are stored as text without zero padding. There are two honest fixes. Cast the argument, accepting that a non-numeric value anywhere in the column will raise an error: ```sql SELECT MAX(CAST(version_label AS INTEGER)) FROM releases; -- 11 ``` Or fix the storage: store numbers in a numeric column, or, if the identifier must be text, zero-pad it to a fixed width so that lexicographic order and numeric order coincide. Fixed-width zero padding is the reason well-designed text keys look like `'0000000011'`. ## Two more edges worth knowing **MIN and MAX in one row do not come from one row.** `SELECT MIN(price), MAX(created_at) FROM products` computes each extreme independently over the group. The cheapest product is very likely not the most recently created one, and the result row is not a product. Candidates who assume the pair describes a single row produce confidently wrong reports. **Fixed-length CHAR comparison may ignore trailing spaces.** Under a PAD SPACE collation, `'ab'` and `'ab '` compare equal, so which of them `MAX` reports for a `CHAR` column can be arbitrary. `VARCHAR` under a NO PAD collation treats the trailing spaces as significant. If trailing whitespace can occur in your data, trim at write time rather than reasoning about it at read time. As with the other aggregates, NULL inputs are ignored, so the extreme is taken over the values that are present. ## Answering it well Lead with the type rule — same type in, same type out — then say the ordering comes from the type: chronological for temporal, collation-defined for text. Offer the `'9'` versus `'11'` example unprompted; it proves you have met the bug rather than memorised the definition. Finish with the fix that matches the situation: cast for a one-off query, fix the column type or pad the key for a system you own.

  • Why can MAX over a text column return different values on two databases holding the same data?
    Because character ordering comes from the column's collation, and collations differ. A binary collation compares code points, so every uppercase letter precedes every lowercase one; a linguistic collation usually compares case-insensitively at the primary level. Same rows, different comparison rules, different maximum — which is why collation belongs in the schema contract.
  • How do you get a numeric maximum from a column of numbers stored as text?
    Cast the argument inside the aggregate: `MAX(CAST(version_label AS INTEGER))`. The caveat is that any non-numeric value in the column will make the cast fail, so on messy data you either filter first or fix the storage. The durable fix is a numeric column, or a zero-padded fixed-width text key so lexicographic and numeric order coincide.
  • Do MIN(price) and MAX(created_at) in the same SELECT describe the same row?
    No. Each aggregate is evaluated independently over the group, so the cheapest product and the most recently created product are usually different rows, and the result row corresponds to no row in the table. Treating that output as a record is a common source of wrong reports.

saying these in an interview costs you the question

  • Assuming MIN and MAX only work on numeric columns
  • Expecting text like '11' to beat '9' in MAX
  • Reading MIN and MAX in one row as one record
  • Thinking string ordering is fixed rather than collation-defined
  • Believing MAX on a DATE returns the end of that day

context