Skip to content

Commit f58c999

Browse files
add null_if_any_null flag
1 parent 3aafca7 commit f58c999

File tree

3 files changed

+41
-0
lines changed

3 files changed

+41
-0
lines changed

sqlglot/dialects/bigquery.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ def _build_timestamp(args: t.List) -> exp.Timestamp:
164164
return timestamp
165165

166166

167+
def _build_greatest(args: t.List) -> exp.Greatest:
168+
"""Build GREATEST with BigQuery's null-if-any-null behavior."""
169+
greatest = exp.Greatest.from_arg_list(args)
170+
greatest.null_if_any_null = True
171+
return greatest
172+
173+
167174
def _build_date(args: t.List) -> exp.Date | exp.DateFromParts:
168175
expr_type = exp.DateFromParts if len(args) == 3 else exp.Date
169176
return expr_type.from_arg_list(args)
@@ -545,6 +552,7 @@ class Parser(parser.Parser):
545552
"EDIT_DISTANCE": _build_levenshtein,
546553
"FORMAT_DATE": _build_format_time(exp.TsOrDsToDate),
547554
"GENERATE_ARRAY": exp.GenerateSeries.from_arg_list,
555+
"GREATEST": _build_greatest,
548556
"JSON_EXTRACT_SCALAR": _build_extract_json_with_default_path(exp.JSONExtractScalar),
549557
"JSON_EXTRACT_ARRAY": _build_extract_json_with_default_path(exp.JSONExtractArray),
550558
"JSON_EXTRACT_STRING_ARRAY": _build_extract_json_with_default_path(exp.JSONValueArray),

sqlglot/dialects/duckdb.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,37 @@ def _initcap_sql(self: DuckDB.Generator, expression: exp.Initcap) -> str:
405405
return _build_capitalization_sql(self, this_sql, escaped_delimiters_sql)
406406

407407

408+
def _greatest_sql(self: DuckDB.Generator, expression: exp.Greatest) -> str:
409+
"""
410+
Handle GREATEST function with dialect-aware NULL behavior.
411+
412+
- If null_if_any_null=True (BigQuery-style): return NULL if any argument is NULL
413+
- If null_if_any_null=False (DuckDB/PostgreSQL-style): ignore NULLs, return greatest non-NULL value
414+
"""
415+
all_args = [expression.this] + expression.expressions
416+
greatest_expr = self.func("GREATEST", *all_args)
417+
418+
# Check if this GREATEST should return NULL if any arg is NULL (BigQuery behavior)
419+
if getattr(expression, 'null_if_any_null', False):
420+
# BigQuery behavior: NULL if any argument is NULL
421+
null_checks = []
422+
for arg in all_args:
423+
null_checks.append(exp.Is(this=arg, expression=exp.Null()))
424+
425+
is_any_null_expr: exp.Expression = null_checks[0]
426+
for null_check in null_checks[1:]:
427+
is_any_null_expr = exp.Or(this=is_any_null_expr, expression=null_check)
428+
429+
# Create CASE expression
430+
case_expr = exp.Case(
431+
ifs=[exp.If(this=is_any_null_expr, true=exp.Null())], default=greatest_expr
432+
)
433+
return self.sql(case_expr)
434+
else:
435+
# DuckDB/PostgreSQL behavior: use native GREATEST (ignores NULLs)
436+
return self.sql(greatest_expr)
437+
438+
408439
class DuckDB(Dialect):
409440
NULL_ORDERING = "nulls_are_last"
410441
SUPPORTS_USER_DEFINED_TYPES = True
@@ -866,6 +897,7 @@ class Generator(generator.Generator):
866897
exp.EuclideanDistance: rename_func("LIST_DISTANCE"),
867898
exp.GenerateDateArray: _generate_datetime_array_sql,
868899
exp.GenerateTimestampArray: _generate_datetime_array_sql,
900+
exp.Greatest: _greatest_sql,
869901
exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, within_group=False),
870902
exp.Explode: rename_func("UNNEST"),
871903
exp.IntDiv: lambda self, e: self.binary(e, "//"),

sqlglot/expressions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6694,6 +6694,7 @@ class Getbit(Func):
66946694
class Greatest(Func):
66956695
arg_types = {"this": True, "expressions": False}
66966696
is_var_len_args = True
6697+
null_if_any_null = False # Set to True for BigQuery-style NULL behavior
66976698

66986699

66996700
class GreatestIgnoreNulls(Func):

0 commit comments

Comments
 (0)