--- title: "geomean" slug: "dev-database-reference-sql-functions-aggregate-geomean" updated: 2026-10-30T18:18:46Z published: 2026-10-30T18:18:46Z canonical: "docs.vastdata.com/dev-database-reference-sql-functions-aggregate-geomean" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.vastdata.com/llms.txt > Use this file to discover all available pages before exploring further. # geomean *Applies to VAST Cluster 5.5.* *[Functions](/develop/docs/dev-database-reference-sql-functions-index) › [Aggregate and analytic functions](/develop/docs/dev-database-reference-sql-functions-aggregate) › Sums and averages.* Returns the geometric mean of positive values: the n-th root of the product of n values. `geometric_mean` is the same function. ## Syntax ``` geomean(x) geometric_mean(x) ``` | Argument | Type | Description | | --- | --- | --- | | `x` | a number: any integer type, `FLOAT`, `DOUBLE` or `DECIMAL` | The values. Every non-NULL value must be greater than 0. | **Returns:** `DOUBLE`. **Aliases:** `geometric_mean`. ## Set up the examples The examples connect with the ADBC driver, as in [VAST Query Engine (ADBC)](/develop/docs/dev-database-integrations-adbc), and work in a schema called `docs_sql_fn_aggregate_geomean`. Set `QE_ENDPOINT`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `VASTDB_BUCKET` ([SQL reference](/develop/docs/dev-database-reference-sql-index#example-connect-and-use-a-transaction)), 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 four years of a fund: its growth factor that year (1.10 is 10% up), the same change as a fraction (0.10), and the number of new investors. The last year has no growth figures yet, and no new investors: ``` 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_aggregate_geomean"' 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, "") 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}.fund_years" run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}") run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, factor DOUBLE, change DOUBLE, investors INTEGER)") run(f"INSERT INTO {TABLE} VALUES " "(1, 1.10, 0.10, 2), " "(2, 0.95, -0.05, 8), " "(3, 1.20, 0.20, 4), " "(4, NULL, NULL, 0)") ``` ``` [] [] [(4,)] ``` ## What it does `geomean(x)` is an aggregate: it returns the n-th root of the product of the n non-NULL values of `x`. It's the right mean for factors that multiply, such as yearly growth rates: the geometric mean of the yearly factors is the constant yearly factor that gives the same total growth. The plan shows how it's computed, through logarithms: ``` exp(avg(ln(x))) ``` So the rules of [ln](/develop/docs/dev-database-reference-sql-functions-numeric-ln) apply to every value: **a zero or a negative value stops the query with an error**, `cannot take logarithm of zero` or `cannot take logarithm of a negative number`. Leave such rows out in `WHERE`, or measure growth as factors (1.10, 0.95), not as changes (0.10, -0.05), which can be negative. NULLs are skipped, as by [avg](/develop/docs/dev-database-reference-sql-functions-aggregate-avg). `geomean` is a macro, a shorthand the Query Engine expands into that expression, not an aggregate of its own, so it can't take `FILTER`, `DISTINCT` or `ORDER BY` (see [Limitations and errors](/develop/docs/dev-database-reference-sql-functions-aggregate-geomean#limitations-and-errors)). ## NULLs and edge cases | Input | Result | | --- | --- | | NULL values | Skipped | | Only NULLs, or `geomean(NULL)` | NULL | | No rows (an empty table, or a `WHERE` that matches nothing) | NULL | | `geomean(2.5)` with no `FROM`, or one row | The value itself | | A zero among the values | Error: `cannot take logarithm of zero` | | A negative value among the values | Error: `cannot take logarithm of a negative number` | | A string, date, time, timestamp, `BOOLEAN` or list | Error: `No function matches the given name and argument types`, naming `ln`, for example `'ln(VARCHAR)'` | | `geomean(x) FILTER (WHERE ...)` | Error: `Function "geomean" is a Macro Function. "DISTINCT", "FILTER", and "ORDER BY" are only applicable to aggregate functions.` | The same cases, as the cluster returns them. They use the table and helpers from [Set up the examples](/develop/docs/dev-database-reference-sql-functions-aggregate-geomean#set-up-the-examples): ``` run(f"SELECT geomean(factor), geometric_mean(factor) FROM {TABLE}") run(f"SELECT geomean(factor) FROM {TABLE} WHERE id = 4") run(f"SELECT geomean(factor) FROM {TABLE} WHERE id > 100") run("SELECT geomean(NULL), geomean(2.5)") run(f"SELECT geomean(investors) FROM {TABLE}") run(f"SELECT geomean(change) FROM {TABLE}") run(f"SELECT geomean(id > 1) FROM {TABLE}") run(f"SELECT geomean(factor) FILTER (WHERE id < 3) FROM {TABLE}") ``` ``` [(1.078365153390936, 1.078365153390936)] [(None,)] [(None,)] [(None, 2.5)] OSError: cannot take logarithm of zero OSError: cannot take logarithm of a negative number InternalError: Binder Error: No function matches the given name and argument types 'ln(BOOLEAN)'. You might need to add explicit type casts. InternalError: Invalid Input Error: Function "geomean" is a Macro Function. "DISTINCT", "FILTER", and "ORDER BY" are only applicable to aggregate functions. ``` ## Examples The average yearly growth factor of the fund, next to the arithmetic mean of the same factors and the formula written out: ``` run(f"SELECT geomean(factor), avg(factor), exp(avg(ln(factor))) FROM {TABLE}") ``` ``` [(1.078365153390936, 1.0833333333333333, 1.078365153390936)] ``` The geometric mean of the number of new investors, over the years that had any. The year with 0 is left out in `WHERE`, which runs before the aggregate: ``` run(f"SELECT geomean(investors) FROM {TABLE} WHERE investors > 0") ``` ``` [(4.0,)] ``` The average yearly change from changes stored as fractions: add 1 to turn them into factors, and subtract 1 from the result: ``` run(f"SELECT geomean(1 + change) - 1 FROM {TABLE}") ``` ``` [(0.07836515339093597,)] ``` ## How it runs in VAST The Query Engine runs on the cluster's CNodes. A `geomean` query has three steps, which [EXPLAIN](/develop/docs/dev-database-reference-sql-explain) shows: 1. **A scan in VAST DataBase** (`VAST_SCAN`), which reads the column and evaluates the `WHERE` conditions it can [push down](/develop/docs/dev-database-reference-pushdowns). 2. **One aggregate step** (`UNGROUPED_AGGREGATE`) in the Query Engine, which computes `avg(ln(x))` over the rows the scan sends. 3. **A `PROJECTION` step**, which applies `exp()` to the result. The `WHERE` condition of the second example compares a column with a constant, so it's evaluated in the scan and doesn't appear in the plan: ``` plan(f"SELECT geomean(investors) FROM {TABLE} WHERE investors > 0") ``` ``` ┌───────────────────────────┐ │ PROJECTION │ │ ──────────────────── │ │ exp(avg(ln(CAST(investors │ │ AS DOUBLE)))) │ │ │ └─────────────┬─────────────┘ ┌─────────────┴─────────────┐ │ UNGROUPED_AGGREGATE │ │ ──────────────────── │ │ Aggregates: │ │ avg(ln(CAST(investors AS │ │ DOUBLE))) │ └─────────────┬─────────────┘ ┌─────────────┴─────────────┐ │ VAST_SCAN │ │ ──────────────────── │ │ Function: VAST_SCAN │ │ │ │ Projections: │ │ investors │ │ │ └───────────────────────────┘ ``` ## Make it faster - **Leave out zeros and negatives in `WHERE`.** `WHERE x > 0` is pushed down to VAST DataBase, so those rows never leave storage, and the query doesn't fail on them. - **Compute it with other aggregates in one query.** They share one scan. - **Store factors rather than changes** if you often take their geometric mean: `geomean(factor)` reads the column as it is, while `geomean(1 + change)` computes `1 + change` on every row. ## Limitations and errors - **Positive values only.** A zero or a negative value fails the whole query, not just that row. Filter them out, or shift the values (`geomean(x + 1) - 1` for changes), knowing that a shift changes the result. - **No `FILTER`, `DISTINCT` or `ORDER BY`.** They fail with `Function "geomean" is a Macro Function. "DISTINCT", "FILTER", and "ORDER BY" are only applicable to aggregate functions.` (`geometric_mean` in the message when you use that name). Use `WHERE`, or write `exp(avg(ln(x)) FILTER (WHERE ...))` out. - **No window form.** `geomean(x) OVER (...)` fails with `Catalog Error: exp is not an aggregate function`. - **Numbers only.** A string, a date, a `BOOLEAN` or a list fails with `No function matches the given name and argument types`, and the message names `ln`, such as `'ln(VARCHAR)'`. - **No `GROUP BY`.** A geometric mean per group fails with `The operator 'HASH_GROUP_BY' type=9 is currently not supported.` ``` run(f"SELECT exp(avg(ln(factor)) FILTER (WHERE id < 3)) FROM {TABLE}") run(f"SELECT id, geomean(factor) OVER (ROWS UNBOUNDED PRECEDING) FROM {TABLE}") ``` ``` [(1.0222524150130436,)] InternalError: Catalog Error: exp is not an aggregate function ``` ## Similar functions | Function | Returns | | --- | --- | | `geomean(x)`, `geometric_mean(x)` | the n-th root of the product of n positive values | | `exp(avg(ln(x)))` ([exp](/develop/docs/dev-database-reference-sql-functions-numeric-exp), [ln](/develop/docs/dev-database-reference-sql-functions-numeric-ln), [avg](/develop/docs/dev-database-reference-sql-functions-aggregate-avg)) | the same value; it can also take `FILTER` | | [avg](/develop/docs/dev-database-reference-sql-functions-aggregate-avg) `(x)` | the arithmetic mean, which is never lower than the geometric mean of the same positive values | | [product](/develop/docs/dev-database-reference-sql-functions-aggregate-product) `(x)` | the product itself | | [wavg](/develop/docs/dev-database-reference-sql-functions-aggregate-wavg) `(x, w)` | a weighted arithmetic mean | ``` run(f"SELECT geomean(factor), exp(avg(ln(factor))), avg(factor), product(factor), wavg(factor, 1) FROM {TABLE}") ``` ``` [(1.078365153390936, 1.078365153390936, 1.0833333333333333, 1.2539999999999998, 1.0833333333333333)] ``` Clean up: ``` run(f"DROP TABLE IF EXISTS {TABLE}") run(f"DROP SCHEMA IF EXISTS {SCHEMA}") conn.close() ``` ``` [] [] ``` ## See also - [Aggregate and analytic functions](/develop/docs/dev-database-reference-sql-functions-aggregate): all the aggregates, and the ones that aren't available. - [avg](/develop/docs/dev-database-reference-sql-functions-aggregate-avg), [product](/develop/docs/dev-database-reference-sql-functions-aggregate-product) and [ln](/develop/docs/dev-database-reference-sql-functions-numeric-ln). - [Pushdowns](/develop/docs/dev-database-reference-pushdowns) and [EXPLAIN](/develop/docs/dev-database-reference-sql-explain).