Applies to VAST Cluster 5.5.
Functions › List, struct and map functions › Aggregate a list. Returns the first element of a list that isn't NULL. Unlike list_first, it skips NULL elements.
Syntax
list_any_value(l)
| Argument | Type | Description |
|---|---|---|
l |
a list of any type | The list whose first non-NULL element is returned. |
Returns: the element type of the list (INTEGER for [1, 2], DOUBLE for a DOUBLE[]).
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_lists_list_any_value. 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 rows of lists: numbers, words, measurements, flags and lists of lists. Row 3 has empty lists and row 4 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_lists_list_any_value"'
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}.samples"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, nums BIGINT[], words VARCHAR[], vals DOUBLE[], "
"flags BOOLEAN[], nested BIGINT[][])")
run(f"INSERT INTO {TABLE} VALUES "
"(1, [1, 2, 3], ['a', 'b', 'c'], [0.5, 2.5, -1.0], [true, false, true], [[1, 2], [3]]), "
"(2, [3, NULL, 3], ['b', NULL, 'B'], [2.0, NULL], [NULL, true], [[], NULL, [4]]), "
"(3, [], [], [], [], []), "
"(4, NULL, NULL, NULL, NULL, NULL), "
"(5, [-5, 0, -5, 10], ['\u00e9', 'Zo\u00eb', '\U0001F1EB\U0001F1F7', ''], [1e308, 1e308, -1e-308], "
"[false, false], [[5, NULL], [], [6, 7, 8]])")
[]
[]
[(5,)]
What it does
list_any_value(l) returns the first element of l that isn't NULL:
list_any_value([1, 2])is1.list_any_value([NULL, 2])is2: the NULL is skipped. list_first returns NULL for the same list.- A list of only NULLs, an empty list and a NULL list return NULL.
Use it to get a value from a list that may have gaps, for example the first reading that was recorded.
NULLs and edge cases
| Input | Result |
|---|---|
NULL |
NULL |
[] |
NULL |
[NULL, NULL] |
NULL |
[NULL, 2] |
2 |
[1, 2] |
1 |
A number, string, BLOB, date or boolean |
Error: No function matches the given name and argument types, for example 'list_aggr(INTEGER_LITERAL, STRING_LITERAL)' |
The same cases, as the cluster returns them. They use the helpers from Set up the examples:
run("SELECT list_any_value(NULL), list_any_value([]::BIGINT[]), list_any_value([NULL, NULL]::BIGINT[]), "
"list_any_value([NULL, 2]), list_any_value([1, 2])")
run("SELECT list_any_value(123)")
[(None, None, None, 2, 1)]
InternalError: Binder Error: No function matches the given name and argument types 'list_aggr(INTEGER_LITERAL, STRING_LITERAL)'. You might need to add explicit type casts.
Examples
The first non-NULL element of each list in the table, next to list_first. Row 2's flags start with a NULL:
run(f"SELECT id, list_any_value(nums), list_any_value(words), list_any_value(flags), list_first(flags) "
f"FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 1, 'a', True, True), (2, 3, 'b', True, None), (3, None, None, None, None), (4, None, None, None, None), (5, -5, 'é', False, False)]
The rows whose list of numbers holds at least one non-NULL value:
run(f"SELECT id, nums FROM {TABLE} WHERE list_any_value(nums) IS NOT NULL ORDER BY id LIMIT 10")
[(1, [1, 2, 3]), (2, [3, None, 3]), (5, [-5, 0, -5, 10])]
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). The scan reads a list column whole, and list_any_value() always runs in the Query Engine:
- In the
SELECTlist, a projection step computeslist_any_value()for each row the scan returns. - In
WHERE, the scan sends the list for every row, and aFILTERstep computeslist_any_value()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 list_any_value(nums) IS NOT NULL")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (list_aggr(nums, │
│ 'any_value') IS NOT NULL) │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ nums │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Add a condition on a plain column. A comparison of a column with a constant, such as
id > 1, is evaluated in VAST DataBase, solist_any_value()runs only on the rows that pass it. The plan forid > 1alone has noFILTERstep:
plan(f"SELECT id FROM {TABLE} WHERE id > 1")
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
- Store the value if you filter on it often. Keep it in its own column when you write the rows: a comparison of that column with a constant is pushed down, a condition on
list_any_value(nums)isn't.
Limitations and errors
- Lists only. Any other type fails with
No function matches the given name and argument types. The error nameslist_aggr, the functionlist_any_valuecalls, and its accepted formlist_aggr(ANY[], VARCHAR, [ANY...]) -> ANY. ORDER BYneeds aLIMIT. Without one, the query fails withThe operator 'ORDER_BY' type=1 is currently not supported for this usage.
Similar functions
| Function | What it returns | Result for [3, 1, NULL, 3, 10] |
|---|---|---|
list_any_value |
the first element that isn't NULL | 3 |
| list_first | the first element, even if NULL | 3 |
| list_last | the last element, even if NULL | 10 |
On [NULL, 2], list_any_value returns 2 and list_first returns NULL:
run("SELECT list_any_value([NULL, 2]), list_first([NULL, 2])")
run("SELECT list_any_value(l), list_first(l), list_last(l) FROM (SELECT [3, 1, NULL, 3, 10] AS l)")
[(2, None)]
[(3, 3, 10)]
Clean up:
run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]
See also
- List, struct and map functions: all the list functions.
- list_filter: keep only the elements you want.
- Where filters run and pushdowns.
- EXPLAIN: how to read a plan.