-
Notifications
You must be signed in to change notification settings - Fork 117
Add CASE expression documentation #3763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cdafb7d
46ae200
02824f3
1b15364
787d32f
9294e15
eff2cc5
e1313f6
740bffa
4e518de
0a0e5fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ Syntax | |
| DQL/SELECT | ||
| DQL/WITH | ||
| DQL/WHERE | ||
| DQL/CASE | ||
| DQL/ORDER_BY | ||
| DQL/EXPLAIN | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| Diagram( | ||
| Terminal('CASE'), | ||
| OneOrMore( | ||
| Sequence( | ||
| Terminal('WHEN'), | ||
| NonTerminal('condition'), | ||
| Terminal('THEN'), | ||
| NonTerminal('result') | ||
| ) | ||
| ), | ||
| Optional( | ||
| Sequence( | ||
| Terminal('ELSE'), | ||
| NonTerminal('result') | ||
| ) | ||
| ), | ||
| Terminal('END') | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,303 @@ | ||
| ==== | ||
| CASE | ||
| ==== | ||
|
|
||
| .. _case: | ||
|
|
||
| Returns a result based on conditional evaluation, similar to if-then-else logic. | ||
|
|
||
| Syntax | ||
| ====== | ||
|
|
||
| .. raw:: html | ||
| :file: CASE.diagram.svg | ||
|
|
||
| CASE expressions use conditional evaluation: | ||
|
|
||
| .. code-block:: sql | ||
|
|
||
| CASE | ||
| WHEN condition1 THEN result1 | ||
| WHEN condition2 THEN result2 | ||
| ... | ||
| ELSE default_result | ||
| END | ||
|
|
||
| Parameters | ||
| ========== | ||
|
|
||
| ``condition`` | ||
| A boolean expression evaluated for each WHEN clause. | ||
|
|
||
| ``result`` | ||
| The value to return if the corresponding condition is TRUE. Can be any expression, column, or literal. | ||
|
|
||
| ``ELSE result`` (optional) | ||
| The default value returned if no conditions are TRUE. If omitted and no conditions match, returns NULL. | ||
|
|
||
| Returns | ||
| ======= | ||
|
|
||
| Returns the result of the first matching WHEN clause, or the ELSE value if no matches are found, or NULL if no ELSE clause is specified and no matches are found. | ||
|
|
||
| All result expressions must be of compatible types (or NULL). | ||
|
|
||
| Examples | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think we also need an example of the CASE construct in SELECT and ORDER BY clauses (as indicated in PR desc)? (I didn't know we could use it in ORDER BY (can we?), and how that would look!)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are two examples with CASE constructs in SELECT (Value Matching with CASE, and Nested CASE). We essentially cannot generate an index with a dispatch as part of a KeyExpression at the moment, therefore we cannot plan an order by query with that. |
||
| ======== | ||
|
|
||
| Setup | ||
| ----- | ||
|
|
||
| For these examples, assume we have a ``products`` table: | ||
|
|
||
| .. code-block:: sql | ||
|
|
||
| CREATE TABLE products( | ||
| id BIGINT, | ||
| name STRING, | ||
| category STRING, | ||
| price BIGINT, | ||
| stock INTEGER, | ||
| PRIMARY KEY(id)) | ||
|
|
||
| INSERT INTO products VALUES | ||
| (1, 'Widget A', 'Electronics', 100, 50), | ||
| (2, 'Widget B', 'Electronics', 150, 5), | ||
| (3, 'Gadget X', 'Electronics', 200, 0), | ||
| (4, 'Tool A', 'Hardware', 80, 100), | ||
| (5, 'Tool B', 'Hardware', 120, 15) | ||
|
|
||
| Simple Condition | ||
| ---------------- | ||
|
|
||
| Categorize products by stock level: | ||
|
|
||
| .. code-block:: sql | ||
|
|
||
| SELECT name, | ||
| 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 products | ||
|
|
||
| .. list-table:: | ||
| :header-rows: 1 | ||
|
|
||
| * - :sql:`name` | ||
| - :sql:`stock` | ||
| - :sql:`stock_status` | ||
| * - :json:`"Widget A"` | ||
| - :json:`50` | ||
| - :json:`"Well Stocked"` | ||
| * - :json:`"Widget B"` | ||
| - :json:`5` | ||
| - :json:`"Low Stock"` | ||
| * - :json:`"Gadget X"` | ||
| - :json:`0` | ||
| - :json:`"Out of Stock"` | ||
| * - :json:`"Tool A"` | ||
| - :json:`100` | ||
| - :json:`"Well Stocked"` | ||
| * - :json:`"Tool B"` | ||
| - :json:`15` | ||
| - :json:`"In Stock"` | ||
|
|
||
| Multiple Conditions | ||
| ------------------- | ||
|
|
||
| Calculate discount based on price and category: | ||
|
|
||
| .. code-block:: sql | ||
|
|
||
| SELECT name, | ||
| price, | ||
| CASE | ||
| WHEN category = 'Electronics' AND price > 150 THEN price * 0.85 | ||
| WHEN category = 'Electronics' THEN price * 0.90 | ||
| WHEN price > 100 THEN price * 0.95 | ||
| ELSE price | ||
| END AS discounted_price | ||
| FROM products | ||
|
|
||
| .. list-table:: | ||
| :header-rows: 1 | ||
|
|
||
| * - :sql:`name` | ||
| - :sql:`price` | ||
| - :sql:`discounted_price` | ||
| * - :json:`"Widget A"` | ||
| - :json:`100` | ||
| - :json:`90.0` | ||
| * - :json:`"Widget B"` | ||
| - :json:`150` | ||
| - :json:`135.0` | ||
| * - :json:`"Gadget X"` | ||
| - :json:`200` | ||
| - :json:`170.0` | ||
| * - :json:`"Tool A"` | ||
| - :json:`80` | ||
| - :json:`80.0` | ||
| * - :json:`"Tool B"` | ||
| - :json:`120` | ||
| - :json:`114.0` | ||
|
|
||
| Value Matching with CASE | ||
| ------------------------- | ||
|
|
||
| Map category codes to names using WHEN conditions: | ||
|
|
||
| .. code-block:: sql | ||
|
|
||
| SELECT name, | ||
| category, | ||
| CASE | ||
| WHEN category = 'Electronics' THEN 'E' | ||
| WHEN category = 'Hardware' THEN 'H' | ||
| WHEN category = 'Media' THEN 'M' | ||
| ELSE 'Other' | ||
| END AS category_code | ||
| FROM products | ||
|
|
||
| .. list-table:: | ||
| :header-rows: 1 | ||
|
|
||
| * - :sql:`name` | ||
| - :sql:`category` | ||
| - :sql:`category_code` | ||
| * - :json:`"Widget A"` | ||
| - :json:`"Electronics"` | ||
| - :json:`"E"` | ||
| * - :json:`"Widget B"` | ||
| - :json:`"Electronics"` | ||
| - :json:`"E"` | ||
| * - :json:`"Gadget X"` | ||
| - :json:`"Electronics"` | ||
| - :json:`"E"` | ||
| * - :json:`"Tool A"` | ||
| - :json:`"Hardware"` | ||
| - :json:`"H"` | ||
| * - :json:`"Tool B"` | ||
| - :json:`"Hardware"` | ||
| - :json:`"H"` | ||
|
|
||
| Nested CASE | ||
| ----------- | ||
|
|
||
| CASE expressions can be nested: | ||
|
|
||
| .. code-block:: sql | ||
|
|
||
| SELECT name, | ||
| price, | ||
| stock, | ||
| CASE | ||
| WHEN stock = 0 THEN 'Unavailable' | ||
| ELSE CASE | ||
| WHEN price < 100 THEN 'Budget' | ||
| WHEN price < 150 THEN 'Standard' | ||
| ELSE 'Premium' | ||
| END | ||
| END AS product_tier | ||
| FROM products | ||
|
|
||
| .. list-table:: | ||
| :header-rows: 1 | ||
|
|
||
| * - :sql:`name` | ||
| - :sql:`price` | ||
| - :sql:`stock` | ||
| - :sql:`product_tier` | ||
| * - :json:`"Widget A"` | ||
| - :json:`100` | ||
| - :json:`50` | ||
| - :json:`"Standard"` | ||
| * - :json:`"Widget B"` | ||
| - :json:`150` | ||
| - :json:`5` | ||
| - :json:`"Premium"` | ||
| * - :json:`"Gadget X"` | ||
| - :json:`200` | ||
| - :json:`0` | ||
| - :json:`"Unavailable"` | ||
| * - :json:`"Tool A"` | ||
| - :json:`80` | ||
| - :json:`100` | ||
| - :json:`"Budget"` | ||
| * - :json:`"Tool B"` | ||
| - :json:`120` | ||
| - :json:`15` | ||
| - :json:`"Standard"` | ||
|
|
||
| Important Notes | ||
| =============== | ||
|
|
||
| Evaluation Order | ||
| ---------------- | ||
|
|
||
| CASE evaluates conditions **in order** and returns the first matching result. Once a match is found, remaining conditions are not evaluated: | ||
|
|
||
| .. code-block:: sql | ||
|
|
||
| CASE | ||
| WHEN x > 100 THEN 'High' -- Evaluated first | ||
| WHEN x > 50 THEN 'Medium' -- Only checked if first is false | ||
| WHEN x > 0 THEN 'Low' -- Only checked if previous are false | ||
| ELSE 'Zero or Negative' | ||
| END | ||
|
|
||
| Order your conditions from most specific to least specific. | ||
|
|
||
| NULL Handling | ||
| ------------- | ||
|
|
||
| NULL values in conditions are treated as FALSE: | ||
|
|
||
| .. code-block:: sql | ||
|
|
||
| -- If price IS NULL, condition is FALSE | ||
| CASE | ||
| WHEN price > 100 THEN 'Expensive' | ||
| ELSE 'Other' | ||
| END | ||
|
|
||
| To explicitly check for NULL: | ||
|
|
||
| .. code-block:: sql | ||
|
|
||
| CASE | ||
| WHEN price IS NULL THEN 'No Price' | ||
| WHEN price > 100 THEN 'Expensive' | ||
| ELSE 'Affordable' | ||
| END | ||
|
|
||
| Type Compatibility | ||
| ------------------ | ||
|
|
||
| .. important:: | ||
|
|
||
| All result expressions in a CASE statement must return compatible types. | ||
| Mixing types (e.g., strings and numbers) will cause an error. | ||
|
Comment on lines
+282
to
+283
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could a result be NULL and be compatible to other results? If it so, then we can avoid writing that case altogether, and not have ELSE; but just curious if the former is possible
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, a result can be
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think we need to have an explicit example for that here?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is documented in the Evaluation Order section |
||
|
|
||
| .. code-block:: sql | ||
|
|
||
| -- VALID: All results are strings | ||
| CASE | ||
| WHEN x > 100 THEN 'High' | ||
| WHEN x > 50 THEN 'Medium' | ||
| ELSE 'Low' | ||
| END | ||
|
|
||
| -- INVALID: Mixed types (string and number) | ||
| CASE | ||
| WHEN x > 100 THEN 'High' | ||
| ELSE 0 | ||
| END | ||
|
|
||
| See Also | ||
| ======== | ||
|
|
||
| * :ref:`Comparison Operators <comparison-operators>` - Operators used in CASE conditions | ||
Uh oh!
There was an error while loading. Please reload this page.