Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1421,13 +1421,13 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "w": {
| "script": {
| "lang": "painless",
| "source": "def param1 = (doc['lastUpdated'].size() == 0 ? null : doc['lastUpdated'].value.toInstant().atZone(ZoneId.of('Z')).with(DayOfWeek.SUNDAY).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)"
| "source": "def param1 = (doc['lastUpdated'].size() == 0 ? null : doc['lastUpdated'].value.toInstant().atZone(ZoneId.of('Z')).with(DayOfWeek.MONDAY).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)"
| }
| },
| "w2": {
| "script": {
| "lang": "painless",
| "source": "def param1 = (doc['lastUpdated'].size() == 0 ? null : doc['lastUpdated'].value.toInstant().atZone(ZoneId.of('Z')).with(DayOfWeek.SUNDAY).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)"
| "source": "def param1 = (doc['lastUpdated'].size() == 0 ? null : doc['lastUpdated'].value.toInstant().atZone(ZoneId.of('Z')).with(DayOfWeek.MONDAY).truncatedTo(ChronoUnit.DAYS)); def param2 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"); (param1 == null) ? null : param2.format(param1)"
| }
| },
| "d": {
Expand Down Expand Up @@ -1655,7 +1655,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "diff": {
| "script": {
| "lang": "painless",
| "source": "def param1 = (doc['createdAt'].size() == 0 ? null : doc['createdAt'].value.toInstant().atZone(ZoneId.of('Z'))); def param2 = (doc['updatedAt'].size() == 0 ? null : doc['updatedAt'].value.toInstant().atZone(ZoneId.of('Z'))); (param1 == null || param2 == null) ? null : Long.valueOf(ChronoUnit.DAYS.between(param1.toLocalDate(), param2.toLocalDate()))"
| "source": "def param1 = (doc['updatedAt'].size() == 0 ? null : doc['updatedAt'].value.toInstant().atZone(ZoneId.of('Z'))); def param2 = (doc['createdAt'].size() == 0 ? null : doc['createdAt'].value.toInstant().atZone(ZoneId.of('Z'))); (param1 == null || param2 == null) ? null : Long.valueOf(ChronoUnit.DAYS.between(param1.toLocalDate(), param2.toLocalDate()))"
| }
| }
| },
Expand Down Expand Up @@ -1712,7 +1712,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "max": {
| "script": {
| "lang": "painless",
| "source": "def param1 = (doc['createdAt'].size() == 0 ? null : doc['createdAt'].value); def param2 = (doc['updatedAt'].size() == 0 ? null : doc['updatedAt'].value.toInstant().atZone(ZoneId.of('Z'))); def param3 = (param1 == null) ? null : ZonedDateTime.parse(param1, new DateTimeFormatterBuilder().appendPattern(\"yyyy-MM-dd HH:mm:ss\").appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).toFormatter().withZone(ZoneId.of('Z'))); (param1 == null || param2 == null) ? null : Long.valueOf(ChronoUnit.DAYS.between(param3.toLocalDate(), param2.toLocalDate()))"
| "source": "def param1 = (doc['updatedAt'].size() == 0 ? null : doc['updatedAt'].value.toInstant().atZone(ZoneId.of('Z'))); def param2 = (doc['createdAt'].size() == 0 ? null : doc['createdAt'].value); def param3 = (param2 == null) ? null : ZonedDateTime.parse(param2, new DateTimeFormatterBuilder().appendPattern(\"yyyy-MM-dd HH:mm:ss\").appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).toFormatter().withZone(ZoneId.of('Z'))); (param1 == null || param2 == null) ? null : Long.valueOf(ChronoUnit.DAYS.between(param1.toLocalDate(), param3.toLocalDate()))"
| }
| }
| }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
{
"title": "Table with computed column",
"description": "Create table with script-based computed field",
"sql": "CREATE TABLE employees (\n id INT NOT NULL,\n birthdate DATE,\n age INT SCRIPT AS (DATEDIFF(birthdate, CURRENT_DATE, YEAR)),\n hire_date DATE,\n tenure INT SCRIPT AS (DATEDIFF(hire_date, CURRENT_DATE, DAY)),\n PRIMARY KEY (id)\n)"
"sql": "CREATE TABLE employees (\n id INT NOT NULL,\n birthdate DATE,\n age INT SCRIPT AS (TIMESTAMPDIFF(YEAR, birthdate, CURRENT_DATE)),\n hire_date DATE,\n tenure INT SCRIPT AS (DATEDIFF(CURRENT_DATE, hire_date, DAY)),\n PRIMARY KEY (id)\n)"
},
{
"title": "Partitioned table",
Expand Down
42 changes: 24 additions & 18 deletions core/src/main/resources/help/functions/date/date_diff.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,60 +7,66 @@
"DATE_DIFF(unit, date1, date2)",
"TIMESTAMPDIFF(unit, date1, date2)"
],
"description": "Returns the difference between two dates in the specified unit, as date2 - date1: the first argument is the START and the second is the END.",
"description": "Returns the difference between two dates in the specified unit. With the dates first it is date1 - date2, as in BigQuery's DATE_DIFF(end_date, start_date, unit); with the unit first it is date2 - date1. DATE_DIFF counts the calendar boundaries crossed; TIMESTAMPDIFF counts the whole units elapsed, as MySQL does.",
"parameters": [
{
"name": "date1",
"type": "DATE/TIMESTAMP",
"description": "First date (start)",
"description": "First date: the end when the dates come first, the start when the unit comes first",
"optional": false,
"defaultValue": null
},
{
"name": "date2",
"type": "DATE/TIMESTAMP",
"description": "Second date (end)",
"description": "Second date: the start when the dates come first, the end when the unit comes first",
"optional": false,
"defaultValue": null
},
{
"name": "unit",
"type": "KEYWORD",
"description": "Unit for result (YEAR, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND)",
"description": "Unit for result (YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND)",
"optional": true,
"defaultValue": "DAY"
}
],
"returnType": "BIGINT",
"examples": [
{
"title": "Calculate age",
"description": "Years between birthdate and now",
"sql": "SELECT DATE_DIFF(birthdate, CURRENT_DATE, YEAR) as age FROM users"
"title": "BigQuery's own documented example",
"description": "The end date first: returns 559",
"sql": "SELECT DATE_DIFF('2010-07-07'::DATE, '2008-12-25'::DATE, DAY) as days FROM orders"
},
{
"title": "Days since event",
"description": "Calculate days elapsed",
"sql": "SELECT DATE_DIFF(event_date, CURRENT_DATE, DAY) as days_ago FROM events"
"description": "Calendar days from the event to today, positive for a past event",
"sql": "SELECT DATE_DIFF(CURRENT_DATE, event_date, DAY) as days_ago FROM events"
},
{
"title": "Hours between timestamps",
"description": "Calculate duration",
"sql": "SELECT DATE_DIFF(start_time, end_time, HOUR) as duration_hours FROM sessions"
"description": "The hour boundaries crossed from start_time to end_time",
"sql": "SELECT DATE_DIFF(HOUR, start_time, end_time) as duration_hours FROM sessions"
},
{
"title": "Calculate age",
"description": "The whole years elapsed since birthdate, which TIMESTAMPDIFF counts",
"sql": "SELECT TIMESTAMPDIFF(YEAR, birthdate, CURRENT_DATE) as age FROM users"
}
],
"notes": [
"Returns a negative value if date2 is before date1",
"Truncates to whole units (no fractions)",
"DATEDIFF is NOT an alias of this function. It is MySQL's, and subtracts the other way round (expr1 - expr2). See HELP DATEDIFF.",
"TIMESTAMPDIFF is the ODBC/JDBC spelling and takes the unit first; MySQL defines it as dt2 - dt1, which is what this function already computes."
"Every vendor computes end - start, and the layout decides where the end sits: the dates first is date1 - date2, the unit first is date2 - date1.",
"DATE_DIFF and DATEDIFF count the calendar boundaries crossed: one second across a year end is 1 YEAR, and 1992-09-15 to 1992-11-14 is 2 MONTHs.",
"TIMESTAMPDIFF counts the whole units elapsed, truncated toward zero: 1992-09-15 to 1992-11-14 is 1 MONTH. A DATE is the datetime at 00:00:00.",
"Boundaries are in UTC. A WEEK starts on Monday (ISO 8601); a QUARTER on January 1, April 1, July 1 and October 1.",
"DATEDIFF follows the same rules, and also takes MySQL's two-argument DATEDIFF(date1, date2), in days. See HELP DATEDIFF.",
"TIMESTAMPDIFF is not an alias of DATE_DIFF: it takes the same layouts but counts the whole units elapsed, where DATE_DIFF counts the boundaries crossed. TIMESTAMPDIFF(YEAR, '2025-12-31 23:59:59', '2026-01-01 00:00:00') is 0; DATE_DIFF(YEAR, '2025-12-31 23:59:59', '2026-01-01 00:00:00') is 1.",
"Changed in 0.24.0: the dates-first forms returned date2 - date1, and the units from WEEK up counted the whole units elapsed between two calendar dates. HOUR, MINUTE and SECOND count the boundaries crossed too: DATE_DIFF(HOUR, '2025-01-10 10:59:00', '2025-01-10 11:00:00') is 1."
],
"seeAlso": [
"DATEDIFF",
"DATE_ADD",
"DATE_SUB"
],
"aliases": [
"TIMESTAMPDIFF"
]
"aliases": []
}
23 changes: 11 additions & 12 deletions core/src/main/resources/help/functions/date/datediff.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
{
"name": "DATEDIFF",
"category": "Date/Time",
"shortDescription": "MySQL day difference: expr1 - expr2",
"shortDescription": "Difference between dates: date1 - date2 with the dates first",
"syntax": [
"DATEDIFF(date1, date2)",
"DATEDIFF(date1, date2, unit)",
"DATEDIFF(unit, date1, date2)"
],
"description": "MySQL's day-difference function. DATEDIFF(date1, date2) returns date1 - date2 in DAYS, which is the opposite subtraction from DATE_DIFF. They are different functions with similar names.",
"description": "DATEDIFF(date1, date2) is MySQL's: date1 - date2 in DAYS. With a unit, the dates first is still date1 - date2, and the unit first - the spelling of SQL Server, Snowflake, Redshift, DuckDB and Elasticsearch SQL - is date2 - date1. It counts the calendar boundaries crossed, as DATE_DIFF does.",
"parameters": [
{
"name": "date1",
"type": "DATE/TIMESTAMP",
"description": "Date subtracted FROM",
"description": "The date subtracted FROM when the dates come first; the start when the unit comes first",
"optional": false,
"defaultValue": null
},
{
"name": "date2",
"type": "DATE/TIMESTAMP",
"description": "Date subtracted",
"description": "The date subtracted when the dates come first; the end when the unit comes first",
"optional": false,
"defaultValue": null
},
{
"name": "unit",
"type": "KEYWORD",
"description": "Unit for result. NOT MySQL - this engine's own extension, and it reverses the subtraction to date2 - date1",
"description": "Unit for result (YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND)",
"optional": true,
"defaultValue": "DAY"
}
Expand All @@ -44,16 +44,15 @@
"sql": "SELECT DATEDIFF(due_date, CURRENT_DATE) as days_left FROM tasks"
},
{
"title": "The three-argument form reverses it",
"description": "This form is not MySQL and follows DATE_DIFF instead",
"sql": "SELECT DATEDIFF(start_date, end_date, MONTH) as months FROM projects"
"title": "The unit first",
"description": "The month boundaries crossed from start_date to end_date, as SQL Server counts them",
"sql": "SELECT DATEDIFF(MONTH, start_date, end_date) as months FROM projects"
}
],
"notes": [
"WARNING: the two-argument and three-argument forms subtract in OPPOSITE directions. DATEDIFF(a, b) is a - b (MySQL, days only); DATEDIFF(a, b, unit) is b - a (this engine's own extension). Adding a unit reverses the sign.",
"If you want a unit, prefer DATE_DIFF(start, end, unit) or TIMESTAMPDIFF(unit, start, end), where one rule holds at every arity.",
"DATEDIFF(unit, date1, date2) - the unit FIRST - is T-SQL's and DuckDB's spelling and means date2 - date1, like TIMESTAMPDIFF. Only the two-argument form is MySQL's.",
"Before engine 0.24.0 this name was an alias of DATE_DIFF and returned the opposite sign. Statements written against the old behaviour need their arguments swapped."
"The dates first is date1 - date2, with or without a unit; the unit first is date2 - date1.",
"DATEDIFF counts the calendar boundaries crossed, in UTC, a WEEK starting on Monday: DATEDIFF(YEAR, '2025-12-31 23:59:59', '2026-01-01 00:00:00') is 1. For the whole units elapsed, use TIMESTAMPDIFF(unit, date1, date2).",
"Changed in 0.24.0: this name was an alias of DATE_DIFF; every dates-first form returned date2 - date1, and from WEEK up it counted the whole units elapsed between the two calendar dates. Swapping the dates of a statement written against the old behaviour restores its sign, not its count."
],
"seeAlso": [
"DATE_DIFF",
Expand Down
10 changes: 5 additions & 5 deletions documentation/sql/ddl_statements.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ CREATE TABLE users (
zip VARCHAR
),
join_date DATE,
seniority INT SCRIPT AS (DATEDIFF(profile.join_date, CURRENT_DATE, DAY))
seniority INT SCRIPT AS (DATEDIFF(CURRENT_DATE, profile.join_date, DAY))
)
)
```
Expand Down Expand Up @@ -304,7 +304,7 @@ CREATE TABLE users (
id INT,
name VARCHAR DEFAULT 'anonymous',
birthdate DATE,
age INT SCRIPT AS (DATE_DIFF(birthdate, CURRENT_DATE, YEAR)),
age INT SCRIPT AS (TIMESTAMPDIFF(YEAR, birthdate, CURRENT_DATE)),
PRIMARY KEY (id)
);
```
Expand Down Expand Up @@ -399,7 +399,7 @@ value derived for it.
CREATE TABLE users (
id INT,
birthdate DATE,
age INT SCRIPT AS (DATE_DIFF(birthdate, CURRENT_DATE, YEAR))
age INT SCRIPT AS (TIMESTAMPDIFF(YEAR, birthdate, CURRENT_DATE))
);
```

Expand All @@ -410,7 +410,7 @@ computing it here:
CREATE TABLE users_view (
id INT,
birthdate DATE,
age INT SCRIPT AS (DATE_DIFF(birthdate, CURRENT_DATE, YEAR)) STORED
age INT SCRIPT AS (TIMESTAMPDIFF(YEAR, birthdate, CURRENT_DATE)) STORED
);
```

Expand Down Expand Up @@ -705,7 +705,7 @@ WITH PROCESSORS (
value = "anonymous"
),
SCRIPT (
description = "age INT SCRIPT AS (DATE_DIFF(birthdate, CURRENT_DATE, YEAR))",
description = "age INT SCRIPT AS (TIMESTAMPDIFF(YEAR, birthdate, CURRENT_DATE))",
lang = "painless",
source = "...",
ignore_failure = true
Expand Down
Loading
Loading