Map indexes and querying
Overview
This page describes how to create a secondary index on bins where the data type is a map. You can index either map key or map value.
Indexing on Map elements
- Similar to basic indexing, the indexable map data types are numeric, string, and GeoJSON.
- You can index a Map at any depth. Prior to Database 6.1.0, map indexing was only on the top-level element, not nested elements.
- When creating index, specify explicitly that map bins should be indexed, and what data type to index on.
- When querying, specify that query should be applied on a CDT data type.
- Similar to basic querying, equality, range for numeric and string data type, points-within-region, and region-containing-points for GeoJSON data type are supported.
Examples
The following example uses asadm to create an index with source type mapkeys and a String data type:
Admin+> manage sindex create string foo_mapkey_idx in mapkeys ns test set demo bin fooThe following example uses asadm to create an index with source type mapvalues with a Numeric data type:
Admin+> manage sindex create numeric foo_mapval_idx in mapvalues ns test set demo bin fooUse show sindex to confirm the indexes were created successfully.
Admin+> show sindex~~~~~~~~~~~~~~~~~~~Secondary Indexes (2026-04-08 16:01:35 UTC)~~~~~~~~~~~~~~~~~~~ Index Name|Namespace| Set|Bin| Bin| Index|State | | | | Type| Type|foo_mapkey_idx|test |demo|foo|string |mapkeys |RWfoo_mapval_idx|test |demo|foo|numeric|mapvalues|RWNumber of rows: 2Elements of the indexed map are type checked, so a record whose foo bin
contains { a:1, b:"2", c:3, d:[4], e:5, 666:"zzz" } results in the following indexing:
| Index On | Key Type | Index Type | Eligible Secondary Index Key |
|---|---|---|---|
| foo | string | MAPKEYS | a, b, c, d, e |
| foo | numeric | MAPVALUES | 1, 3, 5 |
Map index queries
This example inserts records with a foo map bin containing mixed key-value types, then queries by map key and map value using the indexes created above.
Insert data
Key key1 = new Key("test", "demo", "u1");Map<String, Object> cart1 = new HashMap<>();cart1.put("a", 1);cart1.put("b", "2");cart1.put("c", 3);client.put(null, key1, new Bin("username", "Bob Roberts"), new Bin("foo", cart1));
Key key2 = new Key("test", "demo", "u2");Map<String, Object> cart2 = new HashMap<>();cart2.put("c", 3);cart2.put("e", 5);client.put(null, key2, new Bin("username", "rocketbob"), new Bin("foo", cart2));
Key key3 = new Key("test", "demo", "u3");Map<String, Object> cart3 = new HashMap<>();cart3.put("x", new HashMap<>(Map.of("z", 26)));cart3.put("y", "yyy");client.put(null, key3, new Bin("username", "samunwise"), new Bin("foo", cart3));key1 = ("test", "demo", "u1")client.put(key1, { "username": "Bob Roberts", "foo": {"a": 1, "b": "2", "c": 3},})
key2 = ("test", "demo", "u2")client.put(key2, { "username": "rocketbob", "foo": {"c": 3, "e": 5},})
key3 = ("test", "demo", "u3")client.put(key3, { "username": "samunwise", "foo": {"x": {"z": 26}, "y": "yyy"},})key1, _ := as.NewKey("test", "demo", "u1")client.PutBins(nil, key1, as.NewBin("username", "Bob Roberts"), as.NewBin("foo", map[interface{}]interface{}{"a": 1, "b": "2", "c": 3}),)
key2, _ := as.NewKey("test", "demo", "u2")client.PutBins(nil, key2, as.NewBin("username", "rocketbob"), as.NewBin("foo", map[interface{}]interface{}{"c": 3, "e": 5}),)
key3, _ := as.NewKey("test", "demo", "u3")client.PutBins(nil, key3, as.NewBin("username", "samunwise"), as.NewBin("foo", map[interface{}]interface{}{ "x": map[interface{}]interface{}{"z": 26}, "y": "yyy", }),)as_key key1;as_key_init_str(&key1, "test", "demo", "u1");as_hashmap cart1;as_hashmap_init(&cart1, 3);as_stringmap_set_int64((as_map*)&cart1, "a", 1);as_stringmap_set_str((as_map*)&cart1, "b", "2");as_stringmap_set_int64((as_map*)&cart1, "c", 3);as_record rec1;as_record_inita(&rec1, 2);as_record_set_str(&rec1, "username", "Bob Roberts");as_record_set_map(&rec1, "foo", (as_map*)&cart1);aerospike_key_put(&as, &err, NULL, &key1, &rec1);as_record_destroy(&rec1);
as_key key2;as_key_init_str(&key2, "test", "demo", "u2");as_hashmap cart2;as_hashmap_init(&cart2, 2);as_stringmap_set_int64((as_map*)&cart2, "c", 3);as_stringmap_set_int64((as_map*)&cart2, "e", 5);as_record rec2;as_record_inita(&rec2, 2);as_record_set_str(&rec2, "username", "rocketbob");as_record_set_map(&rec2, "foo", (as_map*)&cart2);aerospike_key_put(&as, &err, NULL, &key2, &rec2);as_record_destroy(&rec2);
as_key key3;as_key_init_str(&key3, "test", "demo", "u3");as_hashmap inner;as_hashmap_init(&inner, 1);as_stringmap_set_int64((as_map*)&inner, "z", 26);as_hashmap cart3;as_hashmap_init(&cart3, 2);as_stringmap_set((as_map*)&cart3, "x", (as_val*)&inner);as_stringmap_set_str((as_map*)&cart3, "y", "yyy");as_record rec3;as_record_inita(&rec3, 2);as_record_set_str(&rec3, "username", "samunwise");as_record_set_map(&rec3, "foo", (as_map*)&cart3);aerospike_key_put(&as, &err, NULL, &key3, &rec3);as_record_destroy(&rec3);Key key1 = new Key("test", "demo", "u1");Dictionary<string, object> cart1 = new(){ ["a"] = 1L, ["b"] = "2", ["c"] = 3L,};client.Put(null, key1, new Bin("username", "Bob Roberts"), new Bin("foo", cart1));
Key key2 = new Key("test", "demo", "u2");Dictionary<string, object> cart2 = new(){ ["c"] = 3L, ["e"] = 5L,};client.Put(null, key2, new Bin("username", "rocketbob"), new Bin("foo", cart2));
Key key3 = new Key("test", "demo", "u3");Dictionary<string, object> cart3 = new(){ ["x"] = new Dictionary<string, object> { ["z"] = 26L }, ["y"] = "yyy",};client.Put(null, key3, new Bin("username", "samunwise"), new Bin("foo", cart3));const key1 = new Aerospike.Key("test", "demo", "u1");await client.put(key1, { username: "Bob Roberts", foo: { a: 1, b: "2", c: 3 },});
const key2 = new Aerospike.Key("test", "demo", "u2");await client.put(key2, { username: "rocketbob", foo: { c: 3, e: 5 },});
const key3 = new Aerospike.Key("test", "demo", "u3");await client.put(key3, { username: "samunwise", foo: { x: { z: 26 }, y: "yyy" },});Query by map value (numeric equality)
Query foo_mapval_idx for records where the foo map contains a value equal to 1.
Only records with a matching numeric value are returned — b: "2" is a string, not a numeric, so it does not match.
Statement stmt = new Statement();stmt.setNamespace("test");stmt.setSetName("demo");stmt.setFilter(Filter.contains("foo", IndexCollectionType.MAPVALUES, 1));
RecordSet rs = client.query(null, stmt);// Returns: {username: "Bob Roberts", foo: {a: 1, b: "2", c: 3}}query = client.query("test", "demo")query.where(p.contains("foo", aerospike.INDEX_TYPE_MAPVALUES, 1))
for _, _, record in query.results(): print(record["username"])# Bob Robertsstmt := as.NewStatement("test", "demo")stmt.SetFilter(as.NewContainsFilter("foo", as.ICT_MAPVALUES, 1))
rs, _ := client.Query(nil, stmt)for rec := range rs.Results() { fmt.Println(rec.Record.Bins["username"])}// Bob Robertsas_query q;as_query_init(&q, "test", "demo");as_query_where_inita(&q, 1);as_query_where(&q, "foo", as_contains(MAPVALUES, NUMERIC, 1));
aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);as_query_destroy(&q);// Returns: {username: "Bob Roberts", foo: {a: 1, b: "2", c: 3}}Statement stmt = new();stmt.SetNamespace("test");stmt.SetSetName("demo");stmt.SetFilter(Filter.Contains("foo", IndexCollectionType.MAPVALUES, 1L));
RecordSet rs = client.Query(null, stmt);// Returns: {username: "Bob Roberts", foo: {a: 1, b: "2", c: 3}}const query = client.query("test", "demo");query.where(Aerospike.filter.contains("foo", 1, Aerospike.indexType.MAPVALUES));
const stream = query.foreach();stream.on("data", (record) => { console.log(record.bins.username);});// Bob RobertsQuery by map value (numeric equality — multiple matches)
Query for records where the foo map contains a value equal to 3.
Both u1 (c: 3) and u2 (c: 3) match.
Statement stmt = new Statement();stmt.setNamespace("test");stmt.setSetName("demo");stmt.setFilter(Filter.contains("foo", IndexCollectionType.MAPVALUES, 3));
RecordSet rs = client.query(null, stmt);// Returns: Bob Roberts, rocketbobquery = client.query("test", "demo")query.where(p.contains("foo", aerospike.INDEX_TYPE_MAPVALUES, 3))
for _, _, record in query.results(): print(record["username"])# Bob Roberts# rocketbobstmt := as.NewStatement("test", "demo")stmt.SetFilter(as.NewContainsFilter("foo", as.ICT_MAPVALUES, 3))
rs, _ := client.Query(nil, stmt)for rec := range rs.Results() { fmt.Println(rec.Record.Bins["username"])}// Bob Roberts// rocketbobas_query q;as_query_init(&q, "test", "demo");as_query_where_inita(&q, 1);as_query_where(&q, "foo", as_contains(MAPVALUES, NUMERIC, 3));
aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);as_query_destroy(&q);// Returns: Bob Roberts, rocketbobStatement stmt = new();stmt.SetNamespace("test");stmt.SetSetName("demo");stmt.SetFilter(Filter.Contains("foo", IndexCollectionType.MAPVALUES, 3L));
RecordSet rs = client.Query(null, stmt);// Returns: Bob Roberts, rocketbobconst query = client.query("test", "demo");query.where(Aerospike.filter.contains("foo", 3, Aerospike.indexType.MAPVALUES));
const stream = query.foreach();stream.on("data", (record) => { console.log(record.bins.username);});// Bob Roberts// rocketbobQuery by map value (numeric range)
Query foo_mapval_idx for records where the foo map contains any numeric value in the range [1, 5].
A record is returned once for each matching value in its map.
Statement stmt = new Statement();stmt.setNamespace("test");stmt.setSetName("demo");stmt.setFilter(Filter.range("foo", IndexCollectionType.MAPVALUES, 1, 5));
RecordSet rs = client.query(null, stmt);// Returns: Bob Roberts (twice: a=1, c=3), rocketbob (twice: c=3, e=5)query = client.query("test", "demo")query.where(p.range("foo", aerospike.INDEX_TYPE_MAPVALUES, 1, 5))
for _, _, record in query.results(): print(record["username"])# Bob Roberts# Bob Roberts# rocketbob# rocketbobstmt := as.NewStatement("test", "demo")stmt.SetFilter(as.NewContainsRangeFilter("foo", as.ICT_MAPVALUES, 1, 5))
rs, _ := client.Query(nil, stmt)for rec := range rs.Results() { fmt.Println(rec.Record.Bins["username"])}// Bob Roberts (x2), rocketbob (x2)as_query q;as_query_init(&q, "test", "demo");as_query_where_inita(&q, 1);as_query_where(&q, "foo", as_range(MAPVALUES, NUMERIC, 1, 5));
aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);as_query_destroy(&q);// Returns: Bob Roberts (x2), rocketbob (x2)Statement stmt = new();stmt.SetNamespace("test");stmt.SetSetName("demo");stmt.SetFilter(Filter.Range("foo", IndexCollectionType.MAPVALUES, 1L, 5L));
RecordSet rs = client.Query(null, stmt);// Returns: Bob Roberts (x2), rocketbob (x2)const query = client.query("test", "demo");query.where(Aerospike.filter.range("foo", 1, 5, Aerospike.indexType.MAPVALUES));
const stream = query.foreach();stream.on("data", (record) => { console.log(record.bins.username);});// Bob Roberts (x2), rocketbob (x2)Query by map key (string equality)
Query foo_mapkey_idx for records where the foo map contains the key "y".
Statement stmt = new Statement();stmt.setNamespace("test");stmt.setSetName("demo");stmt.setFilter(Filter.contains("foo", IndexCollectionType.MAPKEYS, "y"));
RecordSet rs = client.query(null, stmt);// Returns: samunwisequery = client.query("test", "demo")query.where(p.contains("foo", aerospike.INDEX_TYPE_MAPKEYS, "y"))
for _, _, record in query.results(): print(record["username"])# samunwisestmt := as.NewStatement("test", "demo")stmt.SetFilter(as.NewContainsFilter("foo", as.ICT_MAPKEYS, "y"))
rs, _ := client.Query(nil, stmt)for rec := range rs.Results() { fmt.Println(rec.Record.Bins["username"])}// samunwiseas_query q;as_query_init(&q, "test", "demo");as_query_where_inita(&q, 1);as_query_where(&q, "foo", as_contains(MAPKEYS, STRING, "y"));
aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);as_query_destroy(&q);// Returns: samunwiseStatement stmt = new();stmt.SetNamespace("test");stmt.SetSetName("demo");stmt.SetFilter(Filter.Contains("foo", IndexCollectionType.MAPKEYS, "y"));
RecordSet rs = client.Query(null, stmt);// Returns: samunwiseconst query = client.query("test", "demo");query.where(Aerospike.filter.contains("foo", "y", Aerospike.indexType.MAPKEYS));
const stream = query.foreach();stream.on("data", (record) => { console.log(record.bins.username);});// samunwiseQuery by map key (multiple matches)
Query for records where the foo map contains the key "c".
Both u1 and u2 have a "c" key.
Statement stmt = new Statement();stmt.setNamespace("test");stmt.setSetName("demo");stmt.setFilter(Filter.contains("foo", IndexCollectionType.MAPKEYS, "c"));
RecordSet rs = client.query(null, stmt);// Returns: Bob Roberts, rocketbobquery = client.query("test", "demo")query.where(p.contains("foo", aerospike.INDEX_TYPE_MAPKEYS, "c"))
for _, _, record in query.results(): print(record["username"])# Bob Roberts# rocketbobstmt := as.NewStatement("test", "demo")stmt.SetFilter(as.NewContainsFilter("foo", as.ICT_MAPKEYS, "c"))
rs, _ := client.Query(nil, stmt)for rec := range rs.Results() { fmt.Println(rec.Record.Bins["username"])}// Bob Roberts// rocketbobas_query q;as_query_init(&q, "test", "demo");as_query_where_inita(&q, 1);as_query_where(&q, "foo", as_contains(MAPKEYS, STRING, "c"));
aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);as_query_destroy(&q);// Returns: Bob Roberts, rocketbobStatement stmt = new();stmt.SetNamespace("test");stmt.SetSetName("demo");stmt.SetFilter(Filter.Contains("foo", IndexCollectionType.MAPKEYS, "c"));
RecordSet rs = client.Query(null, stmt);// Returns: Bob Roberts, rocketbobconst query = client.query("test", "demo");query.where(Aerospike.filter.contains("foo", "c", Aerospike.indexType.MAPKEYS));
const stream = query.foreach();stream.on("data", (record) => { console.log(record.bins.username);});// Bob Roberts// rocketbobQuery by map key (no match)
Query for a key that does not exist in any record’s map. The index for "z" exists only as a nested key inside the "x" sub-map — top-level MAPKEYS indexing does not reach nested keys.
Statement stmt = new Statement();stmt.setNamespace("test");stmt.setSetName("demo");stmt.setFilter(Filter.contains("foo", IndexCollectionType.MAPKEYS, "z"));
RecordSet rs = client.query(null, stmt);// Returns: 0 recordsquery = client.query("test", "demo")query.where(p.contains("foo", aerospike.INDEX_TYPE_MAPKEYS, "z"))
results = list(query.results())print(len(results))# 0stmt := as.NewStatement("test", "demo")stmt.SetFilter(as.NewContainsFilter("foo", as.ICT_MAPKEYS, "z"))
rs, _ := client.Query(nil, stmt)// 0 recordsas_query q;as_query_init(&q, "test", "demo");as_query_where_inita(&q, 1);as_query_where(&q, "foo", as_contains(MAPKEYS, STRING, "z"));
aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);as_query_destroy(&q);// Returns: 0 recordsStatement stmt = new();stmt.SetNamespace("test");stmt.SetSetName("demo");stmt.SetFilter(Filter.Contains("foo", IndexCollectionType.MAPKEYS, "z"));
RecordSet rs = client.Query(null, stmt);// Returns: 0 recordsconst query = client.query("test", "demo");query.where(Aerospike.filter.contains("foo", "z", Aerospike.indexType.MAPKEYS));
const stream = query.foreach();// 0 recordsKnown limitations
- When using range queries on maps, records can be returned multiple times if the map contains multiple values that fall within the range.