Applies to VAST Cluster 5.5.
Functions › Aggregate and analytic functions › Sums and averages. Returns the arithmetic mean of the non-NULL values: their sum divided by how many there are. mean is the same function.
Syntax
avg(x)
mean(x)
| Argument | Type | Description |
|---|---|---|
x |
a number: any integer type, FLOAT, DOUBLE or DECIMAL |
The values to average. NULLs are skipped. |
Returns: DOUBLE, for every input type, DECIMAL included.
Aliases: mean.
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_aggregate_avg. 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 four sensor readings: a temperature, a level, a price and an OK flag. The last row has no values:
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_avg"'
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}.readings"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} "
"(id BIGINT, temperature DOUBLE, level INTEGER, price DECIMAL(10,2), ok BOOLEAN)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, 18.5, 10, 1.25, true), "
"(2, 21.0, 20, 2.50, true), "
"(3, 19.5, 30, 3.75, false), "
"(4, NULL, NULL, NULL, NULL)")
[]
[]
[(4,)]
What it does
avg(x) is an aggregate: it adds up the non-NULL values of x over the rows of the query and divides by how many there are. It's the same as sum(x) / count(x), not sum(x) / count(*): a row where x is NULL counts neither in the sum nor in the number of values.
The result is a DOUBLE for every input type. An average of INTEGER values has a fractional part, and an average of DECIMAL values is a DOUBLE too, not a DECIMAL. Round it for display with round.
NULLs and edge cases
| Input | Result |
|---|---|
| NULL values | Skipped: not in the sum, not in the count |
Only NULLs, or avg(NULL) |
NULL |
No rows (an empty table, or a WHERE that matches nothing) |
NULL |
avg(42) with no FROM |
42.0 |
NaN among the values, or inf and -inf |
nan |
DOUBLE values whose sum is too large |
inf |
DECIMAL(38, s) values whose sum is beyond 38 digits |
Error: Overflow in HUGEINT addition: ... |
A BOOLEAN |
Error: No function matches the given name and argument types 'avg(BOOLEAN)'; use avg(b::INTEGER) |
| A string, date, time, timestamp, interval or list | Error: No function matches the given name and argument types, for example 'avg(DATE)' |
avg(DISTINCT x), avg(x ORDER BY y) |
Error: The operator 'UNGROUPED_AGGREGATE' type=8 is currently not supported for this usage. |
The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:
run(f"SELECT avg(temperature), sum(temperature) / count(temperature), sum(temperature) / count(*) FROM {TABLE}")
run(f"SELECT avg(temperature) FROM {TABLE} WHERE id = 4")
run(f"SELECT avg(temperature) FROM {TABLE} WHERE id > 100")
run("SELECT avg(NULL), avg(42)")
run("SELECT avg(x) FROM (VALUES ('NaN'::DOUBLE), (1.0::DOUBLE)) v(x)")
run("SELECT avg(x) FROM (VALUES (1.0e308::DOUBLE), (1.0e308::DOUBLE)) v(x)")
run(f"SELECT avg(ok) FROM {TABLE}")
run(f"SELECT avg(ok::INTEGER) FROM {TABLE}")
run(f"SELECT avg(DISTINCT level) FROM {TABLE}")
[(19.666666666666668, 19.666666666666668, 14.75)]
[(None,)]
[(None,)]
[(None, 42.0)]
[(nan,)]
[(inf,)]
InternalError: Binder Error: No function matches the given name and argument types 'avg(BOOLEAN)'. You might need to add explicit type casts.
[(0.6666666666666666,)]
InternalError: The operator 'UNGROUPED_AGGREGATE' type=8 is currently not supported for this usage.
Examples
The average temperature, level and price. mean gives the same result as avg, and every result is a DOUBLE:
run(f"SELECT avg(temperature), mean(temperature), avg(level), avg(price) FROM {TABLE}")
[(19.666666666666668, 19.666666666666668, 20.0, 2.5)]
The average temperature of the OK readings, rounded to one decimal, and the share of readings that are OK:
run(f"SELECT round(avg(temperature) FILTER (WHERE ok), 1), avg(ok::INTEGER) FROM {TABLE}")
[(19.8, 0.6666666666666666)]
How it runs in VAST
The Query Engine runs on the cluster's CNodes. An avg query has two steps, which EXPLAIN shows:
- A scan in VAST DataBase (
VAST_SCAN). It reads only the columns the average uses, and evaluates theWHEREconditions it can push down, so rows that don't match never leave storage. - One aggregate step (
UNGROUPED_AGGREGATE) in the Query Engine, which computes the average of the values the scan sends and returns one row.
The plan of an average with a pushed-down WHERE condition. The condition doesn't appear in the plan, because the scan evaluates it:
plan(f"SELECT avg(temperature) FROM {TABLE} WHERE id > 1")
┌───────────────────────────┐
│ UNGROUPED_AGGREGATE │
│ ──────────────────── │
│ Aggregates: │
│ avg(temperature) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ temperature │
│ │
└───────────────────────────┘
Make it faster
- Filter in
WHERE, not inFILTER (WHERE ...). AWHEREcondition that compares a column with a constant is evaluated in VAST DataBase; aFILTERcondition is evaluated in the aggregate step, after the scan has sent every row. Its plan shows the condition insideUNGROUPED_AGGREGATE:
plan(f"SELECT avg(temperature) FILTER (WHERE id > 1) FROM {TABLE}")
┌───────────────────────────┐
│ UNGROUPED_AGGREGATE │
│ ──────────────────── │
│ Aggregates: │
│ avg(temperature) FILTER │
│ (WHERE (id > 1)) Filter: │
│ (id > 1) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ id │
│ temperature │
│ │
└───────────────────────────┘
- Compute several aggregates in one query.
avg,sum,countand the others in oneSELECTlist share one scan and one aggregate step. - Keep functions out of the
WHEREcondition. A function of a column, such asabs(temperature) > 20, isn't pushed down, so the scan sends every row.
Limitations and errors
- Numbers only. A
BOOLEAN, a string, a date, a timestamp, an interval or a list fails withNo function matches the given name and argument types; the error lists the typesavgtakes. Cast aBOOLEANtoINTEGERto get the share of true values. - No average of dates or times.
avg(day)on aDATEorTIMESTAMPfails, and so does an average of anINTERVAL. Use min and max for the range. - No comparison with the average in the same query.
WHERE x > (SELECT avg(x) FROM t)fails, withThe operator 'UNGROUPED_AGGREGATE' type=8 is currently not supported for this usage.(as below) or, for some queries,The operator 'NESTED_LOOP_JOIN' type=31 is currently not supported.;SELECT (SELECT avg(x) FROM t)fails withThe operator 'UNGROUPED_AGGREGATE' type=8 is currently not supported for this usage.Run the average as its own query and put the value in the second one. - No
GROUP BY,DISTINCTorORDER BYinside. An average per group fails withThe operator 'HASH_GROUP_BY' type=9 is currently not supported.;avg(DISTINCT x)andavg(x ORDER BY y)fail withThe operator 'UNGROUPED_AGGREGATE' type=8 is currently not supported for this usage.favg, a compensated average, isn't available either. - Window forms.
avg(x) OVER (ROWS UNBOUNDED PRECEDING)runs, as a running average in the order the scan returns the rows. OtherOVER (...)forms fail withThe operator 'WINDOW' type=6 is currently not supported.See window functions.
run(f"SELECT id FROM {TABLE} WHERE temperature > (SELECT avg(temperature) FROM {TABLE})")
run(f"SELECT ok, avg(temperature) FROM {TABLE} GROUP BY ok")
run(f"SELECT avg(temperature ORDER BY id) FROM {TABLE}")
InternalError: The operator 'UNGROUPED_AGGREGATE' type=8 is currently not supported for this usage.
InternalError: The operator 'HASH_GROUP_BY' type=9 is currently not supported.
InternalError: The operator 'UNGROUPED_AGGREGATE' type=8 is currently not supported for this usage.
Similar functions
| Function | Returns |
|---|---|
avg(x), mean(x) |
the arithmetic mean of the non-NULL values |
sum(x) / count(x) (sum, count) |
the same value |
sum(x) / count(*) |
the sum divided by every row, NULLs included: lower than avg when there are NULLs |
wavg (x, weight) |
a weighted mean |
geomean (x) |
the geometric mean of positive values |
list_avg (list) |
the mean of the elements of one list, per row |
run(f"SELECT avg(level), sum(level) / count(level), sum(level) / count(*), wavg(level, 1), geomean(level) FROM {TABLE}")
run("SELECT list_avg([10, 20, 30])")
[(20.0, 20.0, 15.0, 20.0, 18.171205928321406)]
[(20.0,)]
Clean up:
run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]