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.0finds theINTEGERvalue1. - 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
SELECTlist, the Query Engine builds the map and computesmap_contains_value()for each row it returns. - In
WHERE, the scan sends the lists for every row, and aFILTERstep 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 aFILTERstep:
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 theFILTERstep:
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 = 10is 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
INTEGERvalues fails withcontains: 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
- List, struct and map functions: all the map functions.
- map_values: a map's values as a list.
- Where filters run and pushdowns.
- EXPLAIN: how to read a plan.