---
title: "Map operations examples"
description: "Practical Aerospike map operation examples for modeling event histories, document stores, and leaderboards."
---

# Map operations examples

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/llms.txt)
> 
> All documentation pages available in markdown.

Aerospike [maps](https://aerospike.com/docs/develop/data-types/collections/map) can be used to implement use cases such as:

-   Event History Containers
-   Document Store
-   Leaderboards

## Modeling concepts

Nested lists and maps may be combined to model complex use cases. The examples below use the [map operations](https://aerospike.com/docs/develop/data-types/collections/map/operations) available in each Aerospike language client.

::: note
The `operate()` command executes multiple operations in order on one or more bins of a single record, atomically and with isolation (under a record lock).
:::

Developers familiar with other NoSQL document stores will see the flexibility in applying map and list operations on bins with nested CDTs. Combined with (operate) single record transactions, Aerospike provides powerful functionality for modeling a wide range of use cases.

## Examples

### Event containers with unique timestamps

In this example we want to store and query user event data. Each record contains the recent N events of a specific user, keyed by that user’s unique identifier.

Assuming that events will not occur at the same millisecond, we’ll use millisecond timestamps as map keys for distinct events. Each event’s data will be a tuple `[ event-type, { attr1: v1, attr2: v2, ... } ]`.

Our sample data will be the following events of a single user:

```javascript
{

  1523474230000: ['fav',     {'sku':1, 'b':2}],

  1523474231001: ['comment', {'sku':2, 'b':22}],

  1523474236006: ['viewed',  {'foo':'bar', 'sku':3, 'zz':'top'}],

  1523474235005: ['comment', {'sku':1, 'c':1234}],

  1523474233003: ['viewed',  {'sku':3, 'z':26}],

  1523474234004: ['viewed',  {'sku':1, 'ff':'hhhl'}]

}
```

#### Retrieving data for specific event types

We’ll retrieve all the events of a specific event type, using a [`get_all_by_value`](https://aerospike.com/docs/develop/data-types/collections/map/operations#get_all_by_value) map operation. The argument for the operation is the tuple `['comment', *]`. The [wildcard singleton](https://aerospike.com/docs/develop/data-types/collections/ordering#wildcard) (`*`) matches any remaining elements in the tuple from that position onward, so all tuples starting with `'comment'` as their first element are matched.

-   [Java](#tab-panel-3300)
-   [Python](#tab-panel-3301)
-   [Go](#tab-panel-3302)
-   [C](#tab-panel-3303)
-   [C#](#tab-panel-3304)
-   [Node.js](#tab-panel-3305)

```java
// events = {1523474230000: ["fav", {sku: 1, b: 2}],

//           1523474231001: ["comment", {sku: 2, b: 22}],

//           1523474233003: ["viewed", {sku: 3, z: 26}],

//           1523474234004: ["viewed", {sku: 1, ff: "hhhl"}],

//           1523474235005: ["comment", {sku: 1, c: 1234}],

//           1523474236006: ["viewed", {foo: "bar", sku: 3, zz: "top"}]}

List<Value> pattern = Arrays.asList(Value.get("comment"), Value.WILDCARD);

Record record = client.operate(null, key,

    MapOperation.getByValue("events", Value.get(pattern), MapReturnType.KEY_VALUE)

);

// {1523474231001: ["comment", {sku: 2, b: 22}],

//  1523474235005: ["comment", {sku: 1, c: 1234}]}
```

```python
# events = {1523474230000: ["fav", {"sku": 1, "b": 2}],

#            1523474231001: ["comment", {"sku": 2, "b": 22}],

#            ...

#            1523474235005: ["comment", {"sku": 1, "c": 1234}], ...}

_, _, bins = client.operate(key, [

    map_operations.map_get_by_value(

        "events", ["comment", aerospike.CDTWildcard()],

        aerospike.MAP_RETURN_KEY_VALUE,

    )

])

# {1523474231001: ["comment", {"sku": 2, "b": 22}],

#  1523474235005: ["comment", {"sku": 1, "c": 1234}]}
```

```go
// events = {1523474230000: ["fav", {sku: 1, b: 2}], ...}

pattern := []any{"comment", as.NewWildCardValue()}

record, err := client.Operate(nil, key,

    as.MapGetByValueOp("events", pattern, as.MapReturnType.KEY_VALUE),

)

// {1523474231001: ["comment", {sku: 2, b: 22}],

//  1523474235005: ["comment", {sku: 1, c: 1234}]}
```

```c
// events = {1523474230000: ["fav", {sku: 1, b: 2}], ...}

as_arraylist pattern;

as_arraylist_inita(&pattern, 2);

as_arraylist_append_str(&pattern, "comment");

as_arraylist_append(&pattern, (as_val*)&as_cmp_wildcard);

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_map_get_by_value(&ops, "events", NULL,

    (as_val*)&pattern, AS_MAP_RETURN_KEY_VALUE);

as_record* rec = NULL;

aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);

// {1523474231001: ["comment", {sku: 2, b: 22}],

//  1523474235005: ["comment", {sku: 1, c: 1234}]}
```

```csharp
// events = {1523474230000: ["fav", {sku: 1, b: 2}], ...}

IList pattern = new List<Value> { Value.Get("comment"), Value.WILDCARD };

Record record = client.Operate(null, key,

    MapOperation.GetByValue("events", Value.Get(pattern), MapReturnType.KEY_VALUE)

);

// {1523474231001: ["comment", {sku: 2, b: 22}],

//  1523474235005: ["comment", {sku: 1, c: 1234}]}
```

```js
// events = {1523474230000: ["fav", {sku: 1, b: 2}], ...}

const maps = Aerospike.maps

const result = await client.operate(key, [

    maps.getByValue("events",

        ["comment", new Aerospike.Wildcard()],

        maps.returnType.KEY_VALUE)

])

// {1523474231001: ["comment", {sku: 2, b: 22}],

//  1523474235005: ["comment", {sku: 1, c: 1234}]}
```

::: note
This modeling approach takes advantage of how Aerospike lists are compared to each other. We can currently only rely on it to identify lists whose first element is a specific value, followed by any number of ‘trivial’ attributes, which we cannot query for by value.
:::

Expanding on that example, we will retrieve all the events for multiple event types using [`get_all_by_value_list`](https://aerospike.com/docs/develop/data-types/collections/map/operations#get_all_by_value_list):

-   [Java](#tab-panel-3306)
-   [Python](#tab-panel-3307)
-   [Go](#tab-panel-3308)
-   [C](#tab-panel-3309)
-   [C#](#tab-panel-3310)
-   [Node.js](#tab-panel-3311)

```java
List<Value> commentPattern = Arrays.asList(Value.get("comment"), Value.WILDCARD);

List<Value> favPattern = Arrays.asList(Value.get("fav"), Value.WILDCARD);

List<Value> patterns = Arrays.asList(Value.get(commentPattern), Value.get(favPattern));

Record record = client.operate(null, key,

    MapOperation.getByValueList("events", patterns, MapReturnType.KEY_VALUE)

);

// {1523474230000: ["fav", {sku: 1, b: 2}],

//  1523474231001: ["comment", {sku: 2, b: 22}],

//  1523474235005: ["comment", {sku: 1, c: 1234}]}
```

```python
wildcard = aerospike.CDTWildcard()

_, _, bins = client.operate(key, [

    map_operations.map_get_by_value_list(

        "events",

        [["comment", wildcard], ["fav", wildcard]],

        aerospike.MAP_RETURN_KEY_VALUE,

    )

])

# {1523474230000: ["fav", {"sku": 1, "b": 2}],

#  1523474231001: ["comment", {"sku": 2, "b": 22}],

#  1523474235005: ["comment", {"sku": 1, "c": 1234}]}
```

```go
commentPat := []any{"comment", as.NewWildCardValue()}

favPat := []any{"fav", as.NewWildCardValue()}

record, err := client.Operate(nil, key,

    as.MapGetByValueListOp("events", []any{commentPat, favPat},

        as.MapReturnType.KEY_VALUE),

)

// {1523474230000: ["fav", {sku: 1, b: 2}],

//  1523474231001: ["comment", {sku: 2, b: 22}],

//  1523474235005: ["comment", {sku: 1, c: 1234}]}
```

```c
as_arraylist comment_pat;

as_arraylist_inita(&comment_pat, 2);

as_arraylist_append_str(&comment_pat, "comment");

as_arraylist_append(&comment_pat, (as_val*)&as_cmp_wildcard);

as_arraylist fav_pat;

as_arraylist_inita(&fav_pat, 2);

as_arraylist_append_str(&fav_pat, "fav");

as_arraylist_append(&fav_pat, (as_val*)&as_cmp_wildcard);

as_arraylist patterns;

as_arraylist_inita(&patterns, 2);

as_arraylist_append(&patterns, (as_val*)&comment_pat);

as_arraylist_append(&patterns, (as_val*)&fav_pat);

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_map_get_by_value_list(&ops, "events", NULL,

    (as_list*)&patterns, AS_MAP_RETURN_KEY_VALUE);

as_record* rec = NULL;

aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);

// {1523474230000: ["fav", {sku: 1, b: 2}],

//  1523474231001: ["comment", {sku: 2, b: 22}],

//  1523474235005: ["comment", {sku: 1, c: 1234}]}
```

```csharp
IList commentPattern = new List<Value> { Value.Get("comment"), Value.WILDCARD };

IList favPattern = new List<Value> { Value.Get("fav"), Value.WILDCARD };

IList patterns = new List<Value> { Value.Get(commentPattern), Value.Get(favPattern) };

Record record = client.Operate(null, key,

    MapOperation.GetByValueList("events", patterns, MapReturnType.KEY_VALUE)

);

// {1523474230000: ["fav", {sku: 1, b: 2}],

//  1523474231001: ["comment", {sku: 2, b: 22}],

//  1523474235005: ["comment", {sku: 1, c: 1234}]}
```

```js
const wc = new Aerospike.Wildcard()

const maps = Aerospike.maps

const result = await client.operate(key, [

    maps.getByValueList("events",

        [["comment", wc], ["fav", wc]],

        maps.returnType.KEY_VALUE)

])

// {1523474230000: ["fav", {sku: 1, b: 2}],

//  1523474231001: ["comment", {sku: 2, b: 22}],

//  1523474235005: ["comment", {sku: 1, c: 1234}]}
```

#### Counting events

We will get a count of a specific event type against the sample data above by specifying a [`returnType=count`](https://aerospike.com/docs/develop/data-types/collections/map/operations#return-types). The default behavior would be that of [`returnType=KeyValue`](https://aerospike.com/docs/develop/data-types/collections/map/operations#return-types):

-   [Java](#tab-panel-3312)
-   [Python](#tab-panel-3313)
-   [Go](#tab-panel-3314)
-   [C](#tab-panel-3315)
-   [C#](#tab-panel-3316)
-   [Node.js](#tab-panel-3317)

```java
List<Value> viewedPattern = Arrays.asList(Value.get("viewed"), Value.WILDCARD);

Record viewedCount = client.operate(null, key,

    MapOperation.getByValue("events", Value.get(viewedPattern), MapReturnType.COUNT)

);

// 3

List<Value> commentPattern = Arrays.asList(Value.get("comment"), Value.WILDCARD);

Record commentCount = client.operate(null, key,

    MapOperation.getByValue("events", Value.get(commentPattern), MapReturnType.COUNT)

);

// 2
```

```python
wildcard = aerospike.CDTWildcard()

_, _, bins = client.operate(key, [

    map_operations.map_get_by_value(

        "events", ["viewed", wildcard], aerospike.MAP_RETURN_COUNT

    )

])

# bins["events"] == 3

_, _, bins = client.operate(key, [

    map_operations.map_get_by_value(

        "events", ["comment", wildcard], aerospike.MAP_RETURN_COUNT

    )

])

# bins["events"] == 2
```

```go
viewedPat := []any{"viewed", as.NewWildCardValue()}

record, err := client.Operate(nil, key,

    as.MapGetByValueOp("events", viewedPat, as.MapReturnType.COUNT),

)

// record.Bins["events"] == 3

commentPat := []any{"comment", as.NewWildCardValue()}

record, err = client.Operate(nil, key,

    as.MapGetByValueOp("events", commentPat, as.MapReturnType.COUNT),

)

// record.Bins["events"] == 2
```

```c
as_arraylist viewed_pat;

as_arraylist_inita(&viewed_pat, 2);

as_arraylist_append_str(&viewed_pat, "viewed");

as_arraylist_append(&viewed_pat, (as_val*)&as_cmp_wildcard);

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_map_get_by_value(&ops, "events", NULL,

    (as_val*)&viewed_pat, AS_MAP_RETURN_COUNT);

as_record* rec = NULL;

aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);

// rec bins "events" == 3

as_arraylist comment_pat;

as_arraylist_inita(&comment_pat, 2);

as_arraylist_append_str(&comment_pat, "comment");

as_arraylist_append(&comment_pat, (as_val*)&as_cmp_wildcard);

as_operations ops2;

as_operations_inita(&ops2, 1);

as_operations_map_get_by_value(&ops2, "events", NULL,

    (as_val*)&comment_pat, AS_MAP_RETURN_COUNT);

as_record* rec2 = NULL;

aerospike_key_operate(&as, &err, NULL, &key, &ops2, &rec2);

// rec2 bins "events" == 2
```

```csharp
IList viewedPattern = new List<Value> { Value.Get("viewed"), Value.WILDCARD };

Record viewedCount = client.Operate(null, key,

    MapOperation.GetByValue("events", Value.Get(viewedPattern), MapReturnType.COUNT)

);

// 3

IList commentPattern = new List<Value> { Value.Get("comment"), Value.WILDCARD };

Record commentCount = client.Operate(null, key,

    MapOperation.GetByValue("events", Value.Get(commentPattern), MapReturnType.COUNT)

);

// 2
```

```js
const wc = new Aerospike.Wildcard()

const maps = Aerospike.maps

const viewedResult = await client.operate(key, [

    maps.getByValue("events", ["viewed", wc], maps.returnType.COUNT)

])

// viewedResult.bins.events == 3

const commentResult = await client.operate(key, [

    maps.getByValue("events", ["comment", wc], maps.returnType.COUNT)

])

// commentResult.bins.events == 2
```

#### Trimming the map

Often we want to cap the number of events captured within a map. We will use the [`remove_by_index_range`](https://aerospike.com/docs/develop/data-types/collections/map/operations#remove_by_index_range) map operation to keep the last 1000 events. The INVERTED flag removes everything _outside_ the specified range — effectively keeping only the 1000 highest-indexed (most recent) entries and discarding the rest.

-   [Java](#tab-panel-3318)
-   [Python](#tab-panel-3319)
-   [Go](#tab-panel-3320)
-   [C](#tab-panel-3321)
-   [C#](#tab-panel-3322)
-   [Node.js](#tab-panel-3323)

```java
client.operate(null, key,

    MapOperation.removeByIndexRange("events", -1000, 1000,

        MapReturnType.NONE | MapReturnType.INVERTED)

);
```

```python
client.operate(key, [

    map_operations.map_remove_by_index_range(

        "events", -1000, aerospike.MAP_RETURN_NONE, 1000, inverted=True

    )

])
```

```go
client.Operate(nil, key,

    as.MapRemoveByIndexRangeCountOp("events", -1000, 1000,

        as.MapReturnType.NONE|as.MapReturnType.INVERTED),

)
```

```c
as_operations ops;

as_operations_inita(&ops, 1);

as_operations_map_remove_by_index_range(&ops, "events", NULL,

    -1000, 1000, AS_MAP_RETURN_NONE | AS_MAP_RETURN_INVERTED);

aerospike_key_operate(&as, &err, NULL, &key, &ops, NULL);
```

```csharp
client.Operate(null, key,

    MapOperation.RemoveByIndexRange("events", -1000, 1000,

        MapReturnType.NONE | MapReturnType.INVERTED)

);
```

```js
const maps = Aerospike.maps

await client.operate(key, [

    maps.removeByIndexRange("events", -1000, 1000)

        .invertSelection()

        .andReturn(maps.returnType.NONE)

])
```

### Event containers with unique UUIDs

In some use cases a timestamp would result in frequent collisions. We can model using a unique identifier as the map key.

In this example a conversation thread is stored in a single record. The map keys are message UUIDs, and the map values are list tuples `[timestamp, msg-string, username]`.

Our sample data will be the messages in a single conversation thread:

```javascript
{

  '0edf5b73-535c-4be7-b653-c0513dc79fb4': [1523474230, "Billie Jean is not my lover", "MJ"],

  '29342a0b-e20f-4676-9ecf-dfdf02ef6683': [1523474241, "She's just a girl who", "MJ"],

  '31a8ba1b-8415-aab7-0ecc-56ee659f0a83': [1523474245, "claims that I am the one", "MJ"],

  '9f54b4f8-992e-427f-9fb3-e63348cd6ac9': [1523474249, "...", "Tito"],

  '1ae56b18-7a3c-4f64-adb7-2e845eb5094e': [1523474257, "But the kid is not my son", "MJ"],

  '08785e96-eb1b-4a74-a767-7b56e8f13ea9': [1523474306, "ok...", "Tito"],

  '319fa1a6-0640-4354-a426-10c4d3459f0a': [1523474316, "Hee-hee!", "MJ"]

}
```

We will retrieve all the messages in a range of timestamps, using the [`get_by_value_interval`](https://aerospike.com/docs/develop/data-types/collections/map/operations#get_by_value_interval) map operation.

The arguments for the operation are minimum and maximum tuples of `[timestamp, nil]`. The [NIL singleton](https://aerospike.com/docs/develop/data-types/collections/ordering) is lower in value than a string. The [`get_by_value_interval`](https://aerospike.com/docs/develop/data-types/collections/map/operations#get_by_value_interval) checks if each map value (a list) is between the two list arguments of the operation.

-   [Java](#tab-panel-3324)
-   [Python](#tab-panel-3325)
-   [Go](#tab-panel-3326)
-   [C](#tab-panel-3327)
-   [C#](#tab-panel-3328)
-   [Node.js](#tab-panel-3329)

```java
// Retrieve messages with timestamps in [1523474240, 1523474246)

List<Value> rangeStart = Arrays.asList(Value.get(1523474240), Value.getAsNull());

List<Value> rangeEnd = Arrays.asList(Value.get(1523474246), Value.getAsNull());

Record record = client.operate(null, key,

    MapOperation.getByValueRange("msgs", Value.get(rangeStart), Value.get(rangeEnd),

        MapReturnType.KEY_VALUE)

);

// {29342a0b-...: [1523474241, "She's just a girl who", "MJ"],

//  31a8ba1b-...: [1523474245, "claims that I am the one", "MJ"]}
```

```python
# Retrieve messages with timestamps in [1523474240, 1523474246)

_, _, bins = client.operate(key, [

    map_operations.map_get_by_value_range(

        "msgs",

        [1523474240, None],

        [1523474246, None],

        aerospike.MAP_RETURN_KEY_VALUE,

    )

])

# {29342a0b-...: [1523474241, "She's just a girl who", "MJ"],

#  31a8ba1b-...: [1523474245, "claims that I am the one", "MJ"]}
```

```go
// Retrieve messages with timestamps in [1523474240, 1523474246)

rangeStart := []any{1523474240, nil}

rangeEnd := []any{1523474246, nil}

record, err := client.Operate(nil, key,

    as.MapGetByValueRangeOp("msgs", rangeStart, rangeEnd,

        as.MapReturnType.KEY_VALUE),

)

// {29342a0b-...: [1523474241, "She's just a girl who", "MJ"],

//  31a8ba1b-...: [1523474245, "claims that I am the one", "MJ"]}
```

```c
// Retrieve messages with timestamps in [1523474240, 1523474246)

as_arraylist range_start;

as_arraylist_inita(&range_start, 2);

as_arraylist_append_int64(&range_start, 1523474240);

as_arraylist_append(&range_start, (as_val*)&as_nil);

as_arraylist range_end;

as_arraylist_inita(&range_end, 2);

as_arraylist_append_int64(&range_end, 1523474246);

as_arraylist_append(&range_end, (as_val*)&as_nil);

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_map_get_by_value_range(&ops, "msgs", NULL,

    (as_val*)&range_start, (as_val*)&range_end,

    AS_MAP_RETURN_KEY_VALUE);

as_record* rec = NULL;

aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);

// {29342a0b-...: [1523474241, "She's just a girl who", "MJ"],

//  31a8ba1b-...: [1523474245, "claims that I am the one", "MJ"]}
```

```csharp
// Retrieve messages with timestamps in [1523474240, 1523474246)

IList rangeStart = new List<Value> { Value.Get(1523474240), Value.AsNull };

IList rangeEnd = new List<Value> { Value.Get(1523474246), Value.AsNull };

Record record = client.Operate(null, key,

    MapOperation.GetByValueRange("msgs", Value.Get(rangeStart), Value.Get(rangeEnd),

        MapReturnType.KEY_VALUE)

);

// {29342a0b-...: [1523474241, "She's just a girl who", "MJ"],

//  31a8ba1b-...: [1523474245, "claims that I am the one", "MJ"]}
```

```js
// Retrieve messages with timestamps in [1523474240, 1523474246)

const maps = Aerospike.maps

const result = await client.operate(key, [

    maps.getByValueRange("msgs",

        [1523474240, null],

        [1523474246, null],

        maps.returnType.KEY_VALUE)

])

// {29342a0b-...: [1523474241, "She's just a girl who", "MJ"],

//  31a8ba1b-...: [1523474245, "claims that I am the one", "MJ"]}
```

By the [interval comparison rules](https://aerospike.com/docs/develop/data-types/collections/ordering#intervals) the following is evaluated:

Terminal window

```bash
[1523474240, nil] ≰ [1523474230, "Billie Jean is not my lover", "MJ"] < [1523474246, nil] (false)

[1523474240, nil] ≤ [1523474241, "She's just a girl who", "MJ"]       < [1523474246, nil] (true)

[1523474240, nil] ≤ [1523474245, "claims that I am the one", "MJ"]    < [1523474246, nil] (true)

[1523474240, nil] ≤ [1523474249, "...", "Tito"]                       ≮ [1523474246, nil] (false)

[1523474240, nil] ≤ [1523474257, "But the kid is not my son", "MJ"]   ≮ [1523474246, nil] (false)

[1523474240, nil] ≤ [1523474306, "ok...", "Tito"]                     ≮ [1523474246, nil] (false)

[1523474240, nil] ≤ [1523474316, "Hee-hee!", "MJ"]                    ≮ [1523474246, nil] (false)
```

::: note
Again, this modeling approach takes advantage of how Aerospike lists are compared to each other. We can currently only rely on it to identify lists whose first element is in a range of specified values. The subsequent ‘trivial’ attributes cannot be queried by value.
:::