Welcome to the new DOCS.vastdata.com. We've added new menus and a version selector, plus new persona and resource pages to help customers get started. Looking for a previous article? Use the docs search.

decade

Prev Next

Applies to VAST Cluster 5.5.

Functions › Date and time functions › Parts of a date or time. Returns the decade of a date or timestamp: its year divided by 10, without the remainder. 2024 is in decade 202. On an interval, it returns the decades in its years.

Syntax

decade(value)
Argument Type Description
value DATE, TIMESTAMP (also TIMESTAMP_NS, TIMESTAMP_MS, TIMESTAMP_S) or INTERVAL The value to take the decade of.

Returns: BIGINT.

Same as: date_part('decade', value) and extract(decade FROM value); see date_part.

Set up the examples

The examples connect with the ADBC driver, as in VAST Query Engine (ADBC), and work in a schema called docs_sql_fn_date_time_decade. Set QE_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and VASTDB_BUCKET (SQL reference), and run the blocks in order in one Python session. Below each block is the output you should see.

This block connects, defines the run() and plan() helpers, and creates a table of five events. Each has a date (d), a timestamp (ts) and a timestamp with nanoseconds (tns). Row 4 has no values, and row 5 is the last moment of 1969:

import os
import re

import adbc_driver_manager
import adbc_driver_manager.dbapi as dbapi
import adbc_driver_vastdb

BUCKET = os.environ["VASTDB_BUCKET"]
SCHEMA = f'"{BUCKET}/docs_sql_fn_date_time_decade"'

conn = dbapi.connect(
    driver=adbc_driver_vastdb.get_driver_path(),
    db_kwargs={
        "vast.db.endpoint": os.environ["QE_ENDPOINT"],
        "vast.db.access_key": os.environ["AWS_ACCESS_KEY_ID"],
        "vast.db.secret_key": os.environ["AWS_SECRET_ACCESS_KEY"],
    },
    autocommit=True,
)

def server_message(e):
    """The server's message in a driver error, without the request ID or the echoed statement."""
    text = re.sub(r"\[req=0x[0-9a-f]+\]: ", "", str(e))
    found = (re.search(r'message: "(.*?)", request_id', text, re.S)
             or re.search(r"description: (.*)\)", text, re.S))
    message = found.group(1) if found else text.strip().splitlines()[-1]
    message = re.sub(r"^Plan query: '.*?' failed with: ", "", message, flags=re.S)
    message = re.split(r"\\n|\n", message)[0].replace('\\"', '"')
    return message.replace(BUCKET, "<bucket>")

def run(sql):
    """Run one statement. Print the rows it returns, or the error."""
    try:
        with conn.cursor() as cur:
            cur.execute(sql)
            rows = cur.fetchall() if cur.description else []
    except (adbc_driver_manager.Error, OSError) as e:
        print(f"{type(e).__name__}: {server_message(e)}")
    else:
        print(rows)

def plan(sql):
    """Print the plan without its ~N Rows lines: the table's row count, which can still be 0 just after an insert."""
    with conn.cursor() as cur:
        cur.execute("EXPLAIN " + sql)
        steps = cur.fetchall()[0][1]
    print("\n".join(line for line in steps.splitlines() if "Rows" not in line))

TABLE = f"{SCHEMA}.events"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, d DATE, ts TIMESTAMP, tns TIMESTAMP_NS)")
run(f"INSERT INTO {TABLE} VALUES "
    "(1, DATE '2026-02-28', TIMESTAMP '2026-02-28 23:15:00', CAST('2026-02-28 23:15:00' AS TIMESTAMP_NS)), "
    "(2, DATE '2000-12-31', TIMESTAMP '2000-12-31 12:00:00', CAST('2000-12-31 12:00:00' AS TIMESTAMP_NS)), "
    "(3, DATE '2001-01-01', TIMESTAMP '2001-01-01 00:00:00', CAST('2001-01-01 00:00:00' AS TIMESTAMP_NS)), "
    "(4, NULL, NULL, NULL), "
    "(5, DATE '1969-12-31', TIMESTAMP '1969-12-31 23:59:59.999999', "
    "CAST('1969-12-31 23:59:59.999999999' AS TIMESTAMP_NS))")
[]
[]
[(5,)]

What it does

decade divides the calendar year by 10 and drops the remainder: decade(DATE '2024-02-29') is 202. A decade starts in a year ending in 0: DATE '2029-12-31' is in decade 202, and DATE '2030-01-01' in decade 203. Years 1 to 9 are in decade 0. It works on every timestamp precision.

Years before year 1 count down from 0 (44 BC is year -43), and their decade is the year divided by 10 toward zero: 44 BC is in decade -4.

On an INTERVAL, decade returns the whole decades in its years: decade(INTERVAL '25 years') is 2, and decade(INTERVAL '-25 years') is -2. An interval of less than 10 years, such as INTERVAL '1 year 2 months 3 days 04:05:06.789', gives 0.

NULLs and edge cases

Input Result
A NULL value (row 4) NULL
DATE 'infinity', TIMESTAMP '-infinity' NULL
DATE '0001-01-01', DATE '0005-01-01' 0
DATE '9999-12-31' 999
44 BC: CAST('0044-03-15 (BC)' AS DATE) -4
DATE '2029-12-31' 202
DATE '2030-01-01' 203
INTERVAL '25 years' 2
INTERVAL '-25 years' -2
INTERVAL '25 months 40 days 30 hours' 0
An untyped NULL: decade(NULL) Error: Could not choose a best candidate function for the function call "decade("NULL")"
A TIME Error: No function matches the given name and argument types 'decade(TIME)'
A string Error: Could not choose a best candidate function; cast it to DATE first
A number, boolean, BLOB or list Error: No function matches the given name and argument types, for example 'decade(INTEGER_LITERAL)'

The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:

run(f"SELECT id, decade(d), decade(ts) FROM {TABLE} WHERE id = 4")
run("SELECT decade(DATE 'infinity'), decade(TIMESTAMP '-infinity'), decade(DATE '0001-01-01'), "
    "decade(DATE '0005-01-01'), decade(DATE '9999-12-31'), decade(CAST('0044-03-15 (BC)' AS DATE)), "
    "decade(DATE '2029-12-31'), decade(DATE '2030-01-01')")
run("SELECT decade(INTERVAL '25 years'), decade(INTERVAL '-25 years'), "
    "decade(INTERVAL '25 months 40 days 30 hours')")
run("SELECT decade(NULL)")
run("SELECT decade(TIME '13:45:30')")
run("SELECT decade('2024-02-29')")
run("SELECT decade(123)")
[(4, None, None)]
[(None, None, 0, 0, 999, -4, 202, 203)]
[(2, -2, 0)]
InternalError: Binder Error: Could not choose a best candidate function for the function call "decade("NULL")". In order to select one, please add explicit type casts.
InternalError: Binder Error: No function matches the given name and argument types 'decade(TIME)'. You might need to add explicit type casts.
InternalError: Binder Error: Could not choose a best candidate function for the function call "decade(STRING_LITERAL)". In order to select one, please add explicit type casts.
InternalError: Binder Error: No function matches the given name and argument types 'decade(INTEGER_LITERAL)'. You might need to add explicit type casts.

Examples

The decade of each event's date and timestamp, next to its year:

run(f"SELECT id, year(d), decade(d), decade(ts) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 2026, 202, 202), (2, 2000, 200, 200), (3, 2001, 200, 200), (4, None, None, None), (5, 1969, 196, 196)]

Keep only the events from the 2000s (decade 200). This works, but Make it faster has a faster way to write it:

run(f"SELECT id FROM {TABLE} WHERE decade(d) = 200 ORDER BY id LIMIT 10")
[(2,), (3,)]

How it runs in VAST

The Query Engine runs on the cluster's CNodes, and a query on a table starts with a scan in VAST DataBase (VAST_SCAN). VAST DataBase can evaluate a comparison of a column with a constant, but not a function of a column, so decade() is always computed by the Query Engine:

  • In the SELECT list, the scan reads the column and the Query Engine computes decade() for each row it returns.
  • In WHERE, the scan sends the column for every row, and a FILTER step in the Query Engine computes decade() and keeps the rows that match. No rows are skipped in storage.

The plan of the WHERE example shows the FILTER step above the scan:

plan(f"SELECT id FROM {TABLE} WHERE decade(d) = 200")
┌───────────────────────────┐
│         PROJECTION        │
│    ────────────────────   │
│             #1            │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│           FILTER          │
│    ────────────────────   │
│    ("decade"(d) = 200)    │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│                           │
│        Projections:       │
│             d             │
│             id            │
│                           │
└───────────────────────────┘

Make it faster

  • Filter on a range of the raw column, not on decade(). d >= DATE '2000-01-01' AND d < DATE '2010-01-01' returns the same rows as decade(d) = 200, and it's pushed down to VAST DataBase, so rows from other decades never leave storage. The plan for the range has no FILTER step:
run(f"SELECT id FROM {TABLE} WHERE d >= DATE '2000-01-01' AND d < DATE '2010-01-01' ORDER BY id LIMIT 10")
plan(f"SELECT id FROM {TABLE} WHERE d >= DATE '2000-01-01' AND d < DATE '2010-01-01'")
[(2,), (3,)]
┌───────────────────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│      Projections: id      │
│                           │
└───────────────────────────┘
  • Store the decade if you filter on it often and a range doesn't fit your query. Compute it when you write the rows and keep it in its own column: event_decade = 200 is pushed down, decade(d) = 200 isn't.

Limitations and errors

  • Dates and timestamps only, not TIME. A time of day has no year, so decade(TIME ...) fails with No function matches the given name and argument types 'decade(TIME)'.
  • No time-zone support. On a TIMESTAMP WITH TIME ZONE value, such as now(), decade fails with an Extension Autoloading Error. Cast the value to TIMESTAMP first; see time zones.
  • extract(dec FROM ...) doesn't parse. The short name dec works in date_part('dec', value) but is a syntax error in extract. Write extract(decade FROM value).
  • TIMESTAMP_NS just before 1970. Row 5's tns, 1969-12-31 23:59:59.999999999, is in decade 197, not 196. Store such values as TIMESTAMP if you need their decade.
run("SELECT extract(dec FROM DATE '2024-02-29')")
run(f"SELECT id, decade(tns), decade(ts) FROM {TABLE} ORDER BY id LIMIT 10")
InternalError: Parse query: 'SELECT extract(dec FROM DATE '2024-02-29')' failed with: Parser Error: syntax error at or near "dec"
[(1, 202, 202), (2, 200, 200), (3, 200, 200), (4, None, None), (5, 197, 196)]

Similar functions

Function Returns Result for DATE '2029-12-31' (decade gives 202)
year The calendar year 2029
century The century, counted from year 1 21
millennium The millennium, counted from year 1 3
date_trunc with 'decade' The first day of the decade, as a DATE 2020-01-01
run("SELECT decade(DATE '2029-12-31'), year(DATE '2029-12-31'), century(DATE '2029-12-31'), "
    "millennium(DATE '2029-12-31'), date_trunc('decade', DATE '2029-12-31')")
[(202, 2029, 21, 3, datetime.date(2020, 1, 1))]

Clean up:

run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]

See also