Skip to content
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

SQL: Implement CASE... WHEN... THEN... ELSE... END #41349

Merged
merged 12 commits into from
Apr 22, 2019
95 changes: 94 additions & 1 deletion docs/reference/sql/functions/conditional.asciidoc
Original file line number Diff line number Diff line change
@@ -1,10 +1,103 @@
[role="xpack"]
[testenv="basic"]
[[sql-functions-conditional]]
=== Conditional Functions
=== Conditional Functions And Expressions

Functions that return one of their arguments by evaluating in an if-else manner.

[[sql-functions-conditional-case]]
==== `CASE`

.Synopsis:
[source, sql]
----
CASE WHEN condition THEN result
[WHEN ...]
[ELSE default_result]
END
----

*Input*:

One or multiple _WHEN *condition* THEN *result_* clauses are used and the expression can optionally have
an _ELSE *default_result_* clause. Every *condition* should be a boolean expression.

*Output*: one of the *result* expressions if the corresponding _WHEN *condition_* evaluates to `true` or
the *default_result* if all _WHEN *condition_* clauses evaluate to `false`. If the optional _ELSE *default_result_*
clause is missing and all _WHEN *condition_* clauses evaluate to `false` then `null` is returned.

.Description

The CASE expression is a generic conditional expression which simulates if/else statements of other programming languages
If the condition’s result is true, the value of the result expression that follows the condition will be the returned
the subsequent when clauses will be skipped and not processed.


["source","sql",subs="attributes,callouts,macros"]
----
include-tagged::{sql-specs}/docs/docs.csv-spec[case]
----

["source","sql",subs="attributes,callouts,macros"]
----
include-tagged::{sql-specs}/docs/docs.csv-spec[caseReturnNull]
----

["source","sql",subs="attributes,callouts,macros"]
----
include-tagged::{sql-specs}/docs/docs.csv-spec[caseWithElse]
----


As a variant, a case expression can be expressed with a syntax similar to *switch-case* of other programming languages:
[source, sql]
----
CASE expression
WHEN value1 THEN result1
[WHEN value2 THEN result2]
[WHEN ...]
[ELSE default_result]
END
----

In this case it's transformed internally to:
[source, sql]
----
CASE WHEN expression = value1 THEN result1
[WHEN expression = value2 THEN result2]
[WHEN ...]
[ELSE default_result]
END
----

["source","sql",subs="attributes,callouts,macros"]
----
include-tagged::{sql-specs}/docs/docs.csv-spec[caseWithOperand]
----

["source","sql",subs="attributes,callouts,macros"]
----
include-tagged::{sql-specs}/docs/docs.csv-spec[caseWithOperandAndElse]
----

[NOTE]
===============================
All result expressions must be of compatible data types. More specifically all result
expressions should have a compatible data type with the 1st _non-null_ result expression.
E.g.:

for the following query:

[source, sql]
CASE WHEN a = 1 THEN null
WHEN a > 2 THEN 10
WHEN a > 5 THEN 'foo'
END

an error message would be returned, mentioning that *'foo'* is of data type *keyword*,
which does not match the expected data type *integer* (based on result *10*).
===============================

[[sql-functions-conditional-coalesce]]
==== `COALESCE`

Expand Down
1 change: 1 addition & 0 deletions docs/reference/sql/functions/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
** <<sql-functions-type-conversion-cast>>
** <<sql-functions-type-conversion-convert>>
* <<sql-functions-conditional>>
** <<sql-functions-conditional-case>>
** <<sql-functions-conditional-coalesce>>
** <<sql-functions-conditional-greatest>>
** <<sql-functions-conditional-ifnull>>
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugin/sql/qa/src/main/resources/command.csv-spec
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ STDDEV_POP |AGGREGATE
SUM_OF_SQUARES |AGGREGATE
VAR_POP |AGGREGATE
HISTOGRAM |GROUPING
CASE |CONDITIONAL
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you agree with listing CASE as a conditional function?
Case is quite special and doesn't follow at all the traditional functional format.

COALESCE |CONDITIONAL
GREATEST |CONDITIONAL
IFNULL |CONDITIONAL
Expand Down
181 changes: 181 additions & 0 deletions x-pack/plugin/sql/qa/src/main/resources/conditionals.csv-spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
caseField
SELECT emp_no, CASE WHEN emp_no - 10000 < 10 THEN 'First 10' ELSE 'Second 10' END as "case" FROM test_emp WHERE emp_no >= 10005
ORDER BY emp_no LIMIT 10;

emp_no | case
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following this PR - #41355 - can you change one of these tests to not have an alias?

--------+-----------
10005 | First 10
10006 | First 10
10007 | First 10
10008 | First 10
10009 | First 10
10010 | Second 10
10011 | Second 10
10012 | Second 10
10013 | Second 10
10014 | Second 10
;

caseFieldWithoutAlias
SELECT emp_no, CASE WHEN emp_no - 10000 < 10 THEN emp_no ELSE emp_no % 10 END FROM test_emp WHERE emp_no >= 10005
ORDER BY emp_no LIMIT 10;

emp_no | CASE WHEN emp_no - 10000 < 10 THEN emp_no ELSE emp_no % 10 END
--------+----------------------------------------------------------------
10005 | 10005
10006 | 10006
10007 | 10007
10008 | 10008
10009 | 10009
10010 | 0
10011 | 1
10012 | 2
10013 | 3
10014 | 4
;

caseFieldNoElse
SELECT emp_no, CASE WHEN emp_no - 10000 < 10 THEN 'First 10' END as "case" FROM test_emp WHERE emp_no >= 10005
ORDER BY emp_no LIMIT 10;

emp_no | case
--------+----------
10005 | First 10
10006 | First 10
10007 | First 10
10008 | First 10
10009 | First 10
10010 | null
10011 | null
10012 | null
10013 | null
10014 | null
;

caseWhere
SELECT last_name FROM test_emp WHERE CASE WHEN LENGTH(last_name) < 7 THEN 'ShortName' ELSE 'LongName' END = 'LongName'
ORDER BY emp_no LIMIT 10;

last_name
-----------
Facello
Bamford
Koblick
Maliniak
Preusig
Zielinski
Kalloufi
Piveteau
Bridgland
Nooteboom
;

caseWhereNoElse
SELECT last_name FROM test_emp WHERE CASE WHEN LENGTH(last_name) < 7 THEN 'ShortName' END IS NOT NULL
ORDER BY emp_no LIMIT 10;

last_name
-----------
Simmel
Peac
Sluis
Terkki
Genin
Peha
Erde
Famili
Pettey
Heyers
;

caseOrderBy
schema::last_name:s|languages:byte|emp_no:i
SELECT last_name, languages, emp_no FROM test_emp WHERE emp_no BETWEEN 10005 AND 10015
ORDER BY CASE WHEN languages >= 3 THEN 'first' ELSE 'second' END, emp_no LIMIT 10;

last_name | languages | emp_no
-----------+-----------+--------
Preusig | 3 | 10006
Zielinski | 4 | 10007
Piveteau | 4 | 10010
Sluis | 5 | 10011
Bridgland | 5 | 10012
Genin | 5 | 10014
Nooteboom | 5 | 10015
Maliniak | 1 | 10005
Kalloufi | 2 | 10008
Peac | 1 | 10009
;

caseOrderByNoElse
schema::last_name:s|languages:byte|emp_no:i
SELECT last_name, languages, emp_no FROM test_emp WHERE emp_no BETWEEN 10005 AND 10015
ORDER BY CASE WHEN languages >= 3 THEN 'first' END NULLS FIRST, emp_no LIMIT 10;

last_name | languages | emp_no
-----------+-----------+--------
Maliniak | 1 | 10005
Kalloufi | 2 | 10008
Peac | 1 | 10009
Terkki | 1 | 10013
Preusig | 3 | 10006
Zielinski | 4 | 10007
Piveteau | 4 | 10010
Sluis | 5 | 10011
Bridgland | 5 | 10012
Genin | 5 | 10014
;

caseGroupBy
schema::count:l|lang_skills:s
SELECT count(*) AS count, CASE WHEN NVL(languages, 0) <= 1 THEN 'zero-to-one' ELSE 'multilingual' END as lang_skills
FROM test_emp GROUP BY lang_skills ORDER BY lang_skills;

count | lang_skills
-------+--------------
75 | multilingual
25 | zero-to-one
;

caseGroupByNoElse
schema::count:l|lang_skills:s
SELECT count(*) AS count, CASE WHEN NVL(languages, 0) <= 1 THEN 'zero-to-one' END as lang_skills
FROM test_emp GROUP BY lang_skills ORDER BY lang_skills;

count | lang_skills
-------+-------------
75 | null
25 | zero-to-one
;

caseGroupByComplexNested
schema::count:l|lang_skills:s
SELECT count(*) AS count,
CASE WHEN NVL(languages, 0) = 0 THEN 'zero'
WHEN languages = 1 THEN 'one'
WHEN languages = 2 THEN 'bilingual'
WHEN languages = 3 THEN 'trilingual'
ELSE 'multilingual'
END as lang_skills FROM test_emp GROUP BY lang_skills ORDER BY 2;

count | lang_skills
-------+--------------
19 | bilingual
39 | multilingual
15 | one
17 | trilingual
10 | zero
;

caseGroupByAndHaving
schema::count:l|gender:s|languages:byte
SELECT count(*) AS count, gender, languages FROM test_emp
GROUP BY 2, 3 HAVING CASE WHEN count(*) > 10 THEN 'many' ELSE 'a few' END = 'many'
ORDER BY 2, 3;

count | gender | languages
----------+-------------+---------------
11 | M | 2
11 | M | 3
11 | M | 4
;
74 changes: 73 additions & 1 deletion x-pack/plugin/sql/qa/src/main/resources/docs/docs.csv-spec
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ STDDEV_POP |AGGREGATE
SUM_OF_SQUARES |AGGREGATE
VAR_POP |AGGREGATE
HISTOGRAM |GROUPING
CASE |CONDITIONAL
COALESCE |CONDITIONAL
GREATEST |CONDITIONAL
IFNULL |CONDITIONAL
Expand Down Expand Up @@ -1987,10 +1988,81 @@ SELECT TRUNCATE(-345.153, 1) AS trimmed;

///////////////////////////////
//
// Null handling
// Conditional
//
///////////////////////////////

case
schema::case:s
// tag::case
SELECT CASE WHEN 1 > 2 THEN 'elastic'
WHEN 2 <= 3 THEN 'search'
END AS "case";

case
---------------
search
// end::case
;

caseReturnNull
schema::case:s
// tag::caseReturnNull
SELECT CASE WHEN 1 > 2 THEN 'elastic'
WHEN 2 > 10 THEN 'search'
END AS "case";

case
---------------
null
// end::caseReturnNull
;

caseWithElse
schema::case:s
// tag::caseWithElse
SELECT CASE WHEN 1 > 2 THEN 'elastic'
WHEN 2 > 10 THEN 'search'
ELSE 'default'
END AS "case";

case
---------------
default
// end::caseWithElse
;

caseWithOperand
schema::case:s
// tag::caseWithOperand
SELECT CASE 5
WHEN 1 THEN 'elastic'
WHEN 2 THEN 'search'
WHEN 5 THEN 'elasticsearch'
END AS "case";

case
---------------
elasticsearch
// end::caseWithOperand
;

caseWithOperandAndElse
schema::case:s
// tag::caseWithOperandAndElse
SELECT CASE 5
WHEN 1 THEN 'elastic'
WHEN 2 THEN 'search'
WHEN 3 THEN 'elasticsearch'
ELSE 'default'
END AS "case";

case
---------------
default
// end::caseWithOperandAndElse
;

coalesceReturnNonNull
// tag::coalesceReturnNonNull
SELECT COALESCE(null, 'elastic', 'search') AS "coalesce";
Expand Down
Loading