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.

list_bool_and

Prev Next

Applies to VAST Cluster 5.5.

Functions › List, struct and map functions › Aggregate a list. Tells whether every element of a BOOLEAN list is true: it returns true if all its non-NULL elements are true, false if any is false, and NULL if it has none. It's the list form of the bool_and aggregate.

Syntax

list_bool_and(l)
Argument Type Description
l BOOLEAN[] The list to test.

Returns: BOOLEAN.

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_bool_and. 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: a list of check results and a list of numbers. Row 2 has NULL elements, 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_bool_and"'

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, checks BOOLEAN[], nums BIGINT[])")
run(f"INSERT INTO {TABLE} VALUES "
    "(1, [true, false, true], [1, 2, 3]), "
    "(2, [NULL, true], [3, NULL, 3]), "
    "(3, [], []), "
    "(4, NULL, NULL), "
    "(5, [false, false], [-5, 0, -5, 10])")
[]
[]
[(5,)]

What it does

list_bool_and(l) combines the elements of l with AND, skipping NULL elements:

  • list_bool_and([true, true]) is true, and list_bool_and([true, false]) is false.
  • NULL elements are skipped: list_bool_and([true, NULL]) is true, not NULL.
  • A list with no non-NULL elements, empty or all NULL, returns NULL.

The elements must be BOOLEAN. A list of numbers or strings isn't converted: list_bool_and([1, 0]) fails. To test a condition on each element of another list, turn it into a BOOLEAN list with list_transform first: list_bool_and(list_transform(nums, x -> x > 0)) tells whether every number is positive.

NULLs and edge cases

Input Result
list_bool_and(NULL::BOOLEAN[]) NULL
list_bool_and([]::BOOLEAN[]) NULL
list_bool_and([NULL]::BOOLEAN[]) NULL
list_bool_and([true, NULL]) true: NULL elements are skipped
list_bool_and([true, true]) true
list_bool_and([true, false]) false
list_bool_and([1, 0]), list_bool_and(['true']) Error: Binder Error: No matching aggregate function; the next line of the message names the element type, such as bool_and(INTEGER)
A number, string, date or other non-list Error: 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. (the types named are those of the argument)

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

run("SELECT list_bool_and(NULL::BOOLEAN[]), list_bool_and([]::BOOLEAN[]), list_bool_and([NULL]::BOOLEAN[]), "
    "list_bool_and([true, NULL]), list_bool_and([true, true]), list_bool_and([true, false])")
run("SELECT list_bool_and([1, 0])")
run("SELECT list_bool_and(['true'])")
run(f"SELECT list_bool_and(123) FROM {TABLE} LIMIT 1")
[(None, None, None, True, True, False)]
InternalError: Binder Error: No matching aggregate function
InternalError: Binder Error: No matching aggregate function
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

Tell whether every check in each row passed. Row 2's NULL is skipped, and rows 3 and 4 have nothing to test:

run(f"SELECT id, list_bool_and(checks) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, False), (2, True), (3, None), (4, None), (5, False)]

Test a condition on every element of a list of numbers, by mapping it to a BOOLEAN list first:

run(f"SELECT id, list_bool_and(list_transform(nums, x -> x > 0)) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, True), (2, True), (3, None), (4, None), (5, False)]

Keep the rows where every check passed:

run(f"SELECT id, checks FROM {TABLE} WHERE list_bool_and(checks) ORDER BY id LIMIT 10")
[(2, [None, True])]

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_bool_and() always runs in the Query Engine:

  • In the SELECT list, a projection step computes the result for each row after the scan.
  • In WHERE, the scan sends the list for every row, and a FILTER step above the scan computes the condition.

The plan of the WHERE example shows the FILTER step:

plan(f"SELECT id FROM {TABLE} WHERE list_bool_and(checks)")
┌───────────────────────────┐
│         PROJECTION        │
│    ────────────────────   │
│             #1            │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│           FILTER          │
│    ────────────────────   │
│     list_aggr(checks,     │
│        'bool_and')        │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│                           │
│        Projections:       │
│           checks          │
│             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, so list_bool_and runs only on the rows that pass it. The plan for id > 1 alone has no FILTER step:
plan(f"SELECT id FROM {TABLE} WHERE id > 1")
┌───────────────────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│      Projections: id      │
│                           │
└───────────────────────────┘

Limitations and errors

  • The elements must be BOOLEAN. Numbers and strings aren't converted, and fail with Binder Error: No matching aggregate function. Compare them to a value inside list_transform, as in Examples.
  • The argument must be a list. Anything else fails with Binder Error: No function matches the given name and argument types 'list_aggr(...)'; the message names list_aggr because the function is a form of list_aggregate.
  • ORDER BY needs a LIMIT. Without one, the query fails with The operator 'ORDER_BY' type=1 is currently not supported for this usage.

Similar functions

Function Result for [true, NULL, false]
list_bool_and(l) false: not every non-NULL element is true
list_bool_or true: at least one element is true
list_aggregate(l, 'bool_and') false, the same as list_bool_and
list_count 2: the non-NULL elements
bool_and the same test over the rows of a table, instead of the elements of a list
run("SELECT list_bool_and(l), list_bool_or(l), list_aggregate(l, 'bool_and'), list_count(l) "
    "FROM (SELECT [true, NULL, false] AS l)")
[(False, True, False, 2)]

Clean up:

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

See also