This page documents conditional expressions.
Name & Aliases: CASE WHEN ... THEN ... ELSE ... END (searched CASE form)
Description:
Evaluates boolean WHEN expressions in order; returns the result expression corresponding to the first true condition; if none match, returns the ELSE expression (or NULL if ELSE omitted).
Syntax:
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
...
ELSE default_result
ENDInputs:
- One or more
WHEN condition THEN resultpairs. OptionalELSE result.
Output:
- Type coerced from result expressions (THEN/ELSE).
Examples:
1. Salary banding:
SELECT
name,
salary,
CASE
WHEN salary > 100000 THEN 'very_high'
WHEN salary > 50000 THEN 'high'
ELSE 'normal'
END AS salary_band
FROM emp2. Product status:
SELECT
title,
stock,
CASE
WHEN stock = 0 THEN 'Out of Stock'
WHEN stock < 10 THEN 'Low Stock'
WHEN stock < 50 THEN 'In Stock'
ELSE 'Well Stocked'
END AS stock_status
FROM products3. Discount calculation:
SELECT
title,
price,
CASE
WHEN price > 1000 THEN price * 0.85 -- 15% off
WHEN price > 500 THEN price * 0.90 -- 10% off
WHEN price > 100 THEN price * 0.95 -- 5% off
ELSE price
END AS discounted_price
FROM products4. Without ELSE (returns NULL if no match):
SELECT
name,
CASE
WHEN age < 18 THEN 'Minor'
WHEN age < 65 THEN 'Adult'
END AS age_group
FROM persons
-- Returns NULL for age >= 65Name & Aliases: CASE expr WHEN val1 THEN r1 WHEN val2 THEN r2 ... ELSE rN END (simple CASE)
Description:
Compare expr to valN sequentially using equality; returns corresponding rN for first match; else ELSE result or NULL.
Syntax:
CASE expression
WHEN value1 THEN result1
WHEN value2 THEN result2
...
ELSE default_result
ENDInputs:
expr(any comparable type) and pairsWHEN value THEN result.
Output:
- Type coerced from result expressions.
Implementation notes:
The simple form evaluates by comparing expr = value for each WHEN.
Both CASE forms are parsed and translated into nested conditional Painless scripts for script_fields when used outside an aggregation push-down.
Examples:
1. Department categorization:
SELECT
name,
department,
CASE department
WHEN 'IT' THEN 'tech'
WHEN 'Sales' THEN 'revenue'
WHEN 'Marketing' THEN 'revenue'
WHEN 'Engineering' THEN 'tech'
ELSE 'other'
END AS dept_category
FROM emp2. Status mapping:
SELECT
order_id,
CASE status
WHEN 'P' THEN 'Pending'
WHEN 'S' THEN 'Shipped'
WHEN 'D' THEN 'Delivered'
WHEN 'C' THEN 'Cancelled'
ELSE 'Unknown'
END AS status_label
FROM orders3. Priority levels:
SELECT
ticket_id,
CASE priority
WHEN 1 THEN 'Critical'
WHEN 2 THEN 'High'
WHEN 3 THEN 'Medium'
WHEN 4 THEN 'Low'
ELSE 'Undefined'
END AS priority_name
FROM tickets4. Numeric to text conversion:
SELECT
product_id,
CASE rating
WHEN 5 THEN '★★★★★'
WHEN 4 THEN '★★★★☆'
WHEN 3 THEN '★★★☆☆'
WHEN 2 THEN '★★☆☆☆'
WHEN 1 THEN '★☆☆☆☆'
ELSE 'No rating'
END AS star_display
FROM reviewsReturns the first non-null argument.
Syntax:
COALESCE(expr1, expr2, ...)Inputs:
- One or more expressions
Output:
- Value of first non-null expression (coerced to common type)
Examples:
1. Display name fallback:
SELECT
COALESCE(nickname, firstname, 'N/A') AS display_name
FROM users
-- If nickname = 'Jo': returns 'Jo'
-- If nickname = NULL, firstname = 'John': returns 'John'
-- If both NULL: returns 'N/A'2. Default values:
SELECT
title,
COALESCE(discount_price, price) AS final_price
FROM products
-- Uses discount_price if available, otherwise regular price3. Multiple fallbacks:
SELECT
COALESCE(mobile_phone, work_phone, home_phone, 'No phone') AS contact_phone
FROM customers4. Handling missing data:
SELECT
name,
COALESCE(email, 'no-email@example.com') AS email,
COALESCE(country, 'Unknown') AS country
FROM usersReturns NULL if expr1 = expr2; otherwise returns expr1.
Syntax:
NULLIF(expr1, expr2)Inputs:
expr1- first expressionexpr2- second expression
Output:
- Type of
expr1, or NULL if equal
Examples:
1. Normalize unknown values:
SELECT
NULLIF(status, 'unknown') AS status_norm
FROM events
-- If status = 'unknown': returns NULL
-- If status = 'active': returns 'active'2. Handle sentinel values:
SELECT
title,
NULLIF(price, 0) AS valid_price
FROM products
-- Converts 0 prices to NULL3. Avoid division by zero — no longer needed, and measured not to work:
-- ⚠️ Since 0.24.0 the engine returns NULL for a zero divisor by itself, so write this:
SELECT
total_sales / total_orders AS avg_order_value
FROM sales_summary
-- NULL on the rows where total_orders = 0
⚠️ total_sales / NULLIF(total_orders, 0)— the idiom this section used to recommend — throws anull_pointer_exceptionin a search on exactly the rows wheretotal_orders = 0, and silently drops the computed column in an ingest pipeline. Measured on real Elasticsearch, identically before and after0.24.0.NULLIFremains correct everywhere else; see the/operator for the division rule.
4. Clean data:
SELECT
name,
NULLIF(TRIM(description), '') AS description
FROM products
-- Converts empty strings to NULL after trimmingNotes
-
The comparison follows what each argument renders, not the type of the column it came from.
NULLIF(CAST(zip_code AS BIGINT), 0)compares two numbers even thoughzip_codeis aKEYWORD. Before 0.24.0 it was compared as text and the generated script was rejected by Elasticsearch wherever it appeared — a computed column, a projection or aWHERE. A function argument is evaluated once from 0.24.0 as well;NULLIF(UPPER(name), 'X')used to callUPPERthree times, and aCASEas the first argument could return the wrong value. -
The two arguments must be comparable, and from 0.24.0 the engine says so itself. Two arguments are comparable when both render text, both render a temporal type, both render a number, both render a boolean, or one of them is NULL or a column whose type is not known. Any other pair is refused, because the engine has no way to compare them:
NULLIF(status, 0)is refused withNULLIF(status, 0) compares KEYWORD with BIGINT: NULLIF requires two arguments of comparable types, so cast one of them— at
CREATE TABLE/ALTER TABLE … SET SCRIPT ASfor a computed column, and before the query is sent for a projection or aWHERE. Earlier versions built a script around the mismatch instead: in a query Elasticsearch rejected it, and in a computed column the ingest processor'signore_failureswallowed the failure, so the column was simply missing from every document and theCREATE TABLEstill answered200. Write the comparison in the type you mean:NULLIF(status, '0')orNULLIF(CAST(status AS BIGINT), 0).A cast can create the mismatch as well as cure it:
NULLIF(CAST(qty AS KEYWORD), 0)renders text against a number and is refused, even thoughqtyis numeric.The same rule refuses a date against a number (
NULLIF(created_at, 0)) and a boolean against anything but a boolean (NULLIF(is_active, 0)). Before 0.24.0 these were accepted and compared with Painless==, which answersfalsefor a date against a number without ever failing — so theNULLIFreturned its first argument for every row and the query answered200with the wrong values. If you relied on one of these, write the comparison in the type you mean. -
A string literal against a
datecolumn is refused, and the message depends on the literal. A malformed one is named as such —NULLIF(created_at, 'yesterday')reports that'yesterday'is not a date for that field's format, checked against the column's mapping exactly as aWHEREcomparison is. A well-formed one —NULLIF(created_at, '2024-01-15')— is refused too, with a message saying the comparison is not supported yet: the engine cannot turn a string into a temporal value inside a script, so accepting it would mean comparing a date with a string, which silently never matches. Cast the literal instead:NULLIF(created_at, CAST('2024-01-15' AS DATE)). -
expr2being NULL is not a match: SQL says the comparison is then UNKNOWN, so the answer isexpr1, not NULL.
Tests if expression is NULL.
Syntax:
ISNULL(expr)Inputs:
expr- expression to test
Output:
BOOLEAN- TRUE if NULL, FALSE otherwise
Examples:
1. Check for missing manager:
SELECT
name,
ISNULL(manager) AS manager_missing
FROM emp
-- Result: TRUE if manager is NULL, else FALSE2. Filter NULL values:
SELECT *
FROM products
WHERE ISNULL(description)
-- Returns products without description3. Count NULLs:
SELECT
COUNT(*) as total,
SUM(CASE WHEN ISNULL(email) THEN 1 ELSE 0 END) as missing_emails
FROM users4. Conditional logic:
SELECT
name,
CASE
WHEN ISNULL(last_login) THEN 'Never logged in'
ELSE 'Active user'
END AS user_status
FROM usersTests if expression is NOT NULL.
Syntax:
ISNOTNULL(expr)Inputs:
expr- expression to test
Output:
BOOLEAN- TRUE if NOT NULL, FALSE if NULL
Examples:
1. Check for existing manager:
SELECT
name,
ISNOTNULL(manager) AS has_manager
FROM emp
-- Result: TRUE if manager is NOT NULL, else FALSE2. Filter non-NULL values:
SELECT *
FROM products
WHERE ISNOTNULL(description)
-- Returns products with description3. Count non-NULLs:
SELECT
COUNT(*) as total,
SUM(CASE WHEN ISNOTNULL(email) THEN 1 ELSE 0 END) as with_emails
FROM users4. Required fields validation:
SELECT
product_id,
CASE
WHEN ISNOTNULL(title) AND ISNOTNULL(price) AND ISNOTNULL(category)
THEN 'Valid'
ELSE 'Incomplete'
END AS validation_status
FROM productsReturns the largest value among the given numeric expressions. NULL arguments are ignored (PostgreSQL-style NULL handling); the result is NULL only if every argument is NULL.
Syntax:
GREATEST(expression1, expression2, ...)Inputs:
- One or more numeric expressions to compare
Output:
- Numeric (widest input type), or NULL if every argument is NULL
Examples:
1. Highest of multiple prices:
SELECT GREATEST(price_us, price_eu, price_uk) AS max_price
FROM products2. Floor on a computed value:
SELECT GREATEST(0, base_price - rebate) AS net
FROM orders
-- Never lets the net go below zeroNotes:
- Ignores NULL arguments (PostgreSQL-style NULL handling); returns NULL only if every argument is NULL.
- All arguments should be numeric and have comparable types.
- See also:
LEAST,COALESCE.
Returns the smallest value among the given numeric expressions. NULL arguments are ignored (PostgreSQL-style NULL handling); the result is NULL only if every argument is NULL.
Syntax:
LEAST(expression1, expression2, ...)Inputs:
- One or more numeric expressions to compare
Output:
- Numeric (widest input type), or NULL if every argument is NULL
Examples:
1. Lowest of multiple prices:
SELECT LEAST(price_us, price_eu, price_uk) AS min_price
FROM products2. Cap on a computed value:
SELECT LEAST(rebate, base_price) AS applied_rebate
FROM orders
-- Never lets the rebate exceed the base priceNotes:
- Ignores NULL arguments (PostgreSQL-style NULL handling); returns NULL only if every argument is NULL.
- All arguments should be numeric and have comparable types.
- See also:
GREATEST,COALESCE.