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.

map_contains_value

Prev Next

Applies to VAST Cluster 5.5.

Functions › List, struct and map functions › Search a map. Returns true if any key of a map has a given value, false if none has.

Syntax

map_contains_value(map, value)
Argument Type Description
map MAP(K, V) The map to search.
value the map's value type V, or one that converts to it The value to look for.

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_maps_map_contains_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.

A table can't return a MAP column, so the examples keep each map as two list columns, codes (the keys) and counts (the values), and build the map in the query with map(codes, counts). This block connects, defines the run() and plan() helpers, and creates a table of five rows, each with a number n to look up. Row 2 has a NULL value, row 3 has empty lists (an empty map) 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_maps_map_contains_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}.langs"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, codes VARCHAR[], counts BIGINT[], code VARCHAR, n BIGINT)")
run(f"INSERT INTO {TABLE} VALUES "
    "(1, ['en', 'de'], [1, 2], 'de', 10), "
    "(2, ['\u00e9', '\u00fc'], [NULL, 3], 'fr', -5), "
    "(3, [], [], '', 0), "
    "(4, NULL, NULL, NULL, NULL), "
    "(5, ['k1', 'k2', 'k3'], [10, 20, 30], 'k3', 2147483647)")
[]
[]
[(5,)]

What it does

map_contains_value is true if value equals the value of at least one key:

  • Values only. The keys aren't searched: map_contains_value(MAP {'en': 'English', 'de': 'German'}, 'de') is false. To search the keys, use map_contains; for a key with a given value, map_contains_entry.
  • Exact, typed comparison. Strings must match exactly, so 'german' doesn't find 'German'. Numbers of different types compare by value: 1.0 finds the INTEGER value 1.
  • Any number of keys can share the value. map_contains_value(MAP {'a': 1, 'b': 1}, 1) is true.
  • Values that are lists compare as whole lists: map_contains_value(MAP {'a': [1, 2]}, [1, 2]) is true.

NULLs and edge cases

Input Result
A NULL map: map_contains_value(NULL::MAP(VARCHAR, BIGINT), 1) NULL
A NULL value: map_contains_value(MAP {'a': 1}, NULL) NULL
A NULL value in a map that has one: map_contains_value(MAP {'a': NULL::BIGINT}, NULL) NULL: NULL never matches
map_contains_value(NULL, NULL) NULL
map_contains_value(MAP {}, 1) (empty map) false
map_contains_value(MAP {'en': 'English', 'de': 'German'}, 'german') false
map_contains_value(MAP {'en': 'English', 'de': 'German'}, 'de') (a key) false
map_contains_value(MAP {'a': 1, 'b': 1}, 1) true
map_contains_value(MAP {'a': 1}, 1.0) true
map_contains_value(MAP {'a': [1, 2]}, [1, 2]) true
map_contains_value(MAP {'a': 1}, '1') (a value of another type) Error: contains: Cannot match element of type 'VARCHAR' in a list of type 'INTEGER[]' - an explicit cast is required

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

run("SELECT map_contains_value(NULL::MAP(VARCHAR, BIGINT), 1), map_contains_value(MAP {'a': 1}, NULL), "
    "map_contains_value(MAP {'a': NULL::BIGINT}, NULL), map_contains_value(NULL, NULL), map_contains_value(MAP {}, 1)")
run("SELECT map_contains_value(MAP {'en': 'English', 'de': 'German'}, 'german'), "
    "map_contains_value(MAP {'en': 'English', 'de': 'German'}, 'de'), map_contains_value(MAP {'a': 1, 'b': 1}, 1), "
    "map_contains_value(MAP {'a': 1}, 1.0), map_contains_value(MAP {'a': [1, 2]}, [1, 2])")
run("SELECT map_contains_value(MAP {'a': 1}, '1')")
[(None, None, None, None, False)]
[(False, False, True, True, True)]
InternalError: Binder Error: contains: Cannot match element of type 'VARCHAR' in a list of type 'INTEGER[]' - an explicit cast is required

Examples

Whether each row's map has the value 2, and whether it has the row's own number n as a value. Only row 1 has the value 2; no row's map has its n; row 4's lists are NULL, so its map and the answers are NULL:

run(f"SELECT id, map_contains_value(map(codes, counts), 2), map_contains_value(map(codes, counts), n) "
    f"FROM {TABLE} ORDER BY id LIMIT 10")
[(1, True, False), (2, False, False), (3, False, False), (4, None, None), (5, False, False)]

Keep only the rows whose map has the value 2:

run(f"SELECT id FROM {TABLE} WHERE map_contains_value(map(codes, counts), 2) ORDER BY id LIMIT 10")
[(1,)]

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). A map exists only inside the query: the scan sends the list columns, and the Query Engine builds the map and searches its values, row by row:

  • In the SELECT list, the Query Engine builds the map and computes map_contains_value() for each row it returns.
  • In WHERE, the scan sends the lists for every row, and a FILTER step in the Query Engine builds the map, searches it 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 map_contains_value(map(codes, counts), 2)")
┌───────────────────────────┐
│         PROJECTION        │
│    ────────────────────   │
│             #2            │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│           FILTER          │
│    ────────────────────   │
│ contains(map_values("map" │
│    (codes, counts)), 2)   │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│                           │
│        Projections:       │
│           codes           │
│           counts          │
│             id            │
│                           │
└───────────────────────────┘

Make it faster

  • Search the value list instead. When the values are in a list column, list_contains(counts, 2) asks the same question without building the map. It still runs in a FILTER step:
run(f"SELECT id FROM {TABLE} WHERE list_contains(counts, 2) ORDER BY id LIMIT 10")
[(1,)]
  • Pair it with a pushed-down condition. A comparison of a plain column with a constant, such as id > 1, is evaluated in VAST DataBase, so the rows it rules out never reach the FILTER step:
plan(f"SELECT id FROM {TABLE} WHERE id > 1 AND map_contains_value(map(codes, counts), 2)")
┌───────────────────────────┐
│         PROJECTION        │
│    ────────────────────   │
│             #0            │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│           FILTER          │
│    ────────────────────   │
│ contains(map_values("map" │
│    (codes, counts)), 2)   │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│                           │
│        Projections:       │
│             id            │
│           codes           │
│           counts          │
│                           │
└───────────────────────────┘
  • Keep a value you filter on often in its own column. A comparison such as n = 10 is evaluated in VAST DataBase; a search of a map built in the query isn't.

Limitations and errors

  • The first argument must be a map. A list, string, number, date or struct fails with The provided argument is not a map.
  • The value must have the map's value type. A string in a map with INTEGER values fails with contains: Cannot match element of type 'VARCHAR' in a list of type 'INTEGER[]' - an explicit cast is required; the error names the map's values as a list. Cast the value to the map's value type.
  • NULL can't be found. map_contains_value(m, NULL) is NULL for every map. To test for a key with a NULL value, use map_contains_entry(m, key, NULL), or count the NULL values with list_filter: len(list_filter(map_values(m), x -> x IS NULL)) > 0.
run("SELECT map_contains_value([1], 1)")
run("SELECT map_contains_value(MAP {'a': 1}, 'x')")
run("SELECT map_contains_value(MAP {'a': NULL::BIGINT}, NULL), map_contains_entry(MAP {'a': NULL::BIGINT}, 'a', NULL), "
    "len(list_filter(map_values(MAP {'a': NULL::BIGINT}), x -> x IS NULL)) > 0")
InternalError: Invalid Input Error: The provided argument is not a map
InternalError: Binder Error: contains: Cannot match element of type 'VARCHAR' in a list of type 'INTEGER[]' - an explicit cast is required
[(None, True, True)]

Similar functions

Function Answers Result for MAP {'en': 'English', 'de': 'German'}
map_contains_value(m, 'German') is 'German' a value? true
map_contains(m, 'de') is 'de' a key? true
map_contains_entry(m, 'de', 'German') is 'de' a key with the value 'German'? true
list_contains(map_values(m), 'German') is 'German' in the list of values? true
run("SELECT map_contains_value(MAP {'en': 'English', 'de': 'German'}, 'German'), "
    "map_contains(MAP {'en': 'English', 'de': 'German'}, 'de'), "
    "map_contains_entry(MAP {'en': 'English', 'de': 'German'}, 'de', 'German'), "
    "list_contains(map_values(MAP {'en': 'English', 'de': 'German'}), 'German')")
[(True, True, True, True)]

Clean up:

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

See also