# Projection

**Projection** is choosing which data is returned from each matching record, analogous to choosing columns in a relational database using `SELECT`. Aerospike distinguishes two types of projection:

-   **Bin projection** — Return values from one or more **named bins** (a vertical subset of the record’s bin data).
-   **Operation projection** — Return the results of **read-only bin operations**, including simple bin reads, [path expressions](https://aerospike.com/docs/develop/expressions/path/), and other read operations described under [operation expressions](https://aerospike.com/docs/develop/expressions#operation-expressions). This supports nested paths, computed values, and lightweight server-side shaping without an extra round trip.

## Uniform API across commands

Single-record [`operate`](https://aerospike.com/docs/develop/learn/bin-operations/) and [batched](https://aerospike.com/docs/develop/learn/batch/) `operate` have always supported **both** bin projection and operation projection in one command: you specify an ordered list of read operations, which can be plain bin reads or richer read operations.

Prior to Aerospike Database 8.1.2, [foreground queries](https://aerospike.com/docs/develop/learn/queries/#foreground-queries) supported only a list of bin names (bin projection). Starting with 8.1.2, queries also support operation projection, so the query result shape matches what you can request from `operate` on a single key or in a batch.

::: note
Write operations are not allowed in query projection; the server accepts only read-only operations in the projection list, consistent with a read-only query.
:::

Specifying a list of bin names for projection remains supported. Performance of that form is equivalent to using corresponding bin read operations in the projection list.

## Benefits

-   **Unified API**: Queries behave like batched operations for projections, with a predictable interface across single-key, batch, and query commands.
-   **Path expressions in query projection**: Use [path expressions](https://aerospike.com/docs/develop/expressions/path/) and other expression APIs in the projection, not just in the filter selection.
-   **Server-side computation**: Offload lightweight transformations, arithmetic, and aggregations to the server; only requested bins or results are sent back.
-   **Lower latency**: Avoid the two-phase query-then-batch pattern; one query returns the projected data.

## Supported projection operations

Query projection accepts read-only operations:

-   **Bin read operations:** `Operation.get(binName)` or equivalent in your client. Equivalent to specifying bin names.
-   **CDT read operations:** Map and List read operations (e.g., `MapOperation.getByKey`, `ListOperation.getByIndex`).
-   **Read expressions:** [Operation expressions](https://aerospike.com/docs/develop/expressions#operation-expressions) that compute a value and return it in a computed bin via `ExpOperation.read()`.

## Example: Replacing the two-phase pattern

**Before (two-phase):** Query with no bins to get keys, then `operate` batched commands to get projected data.

-   [Java](#tab-panel-2625)
-   [Python](#tab-panel-2626)
-   [C#](#tab-panel-2627)
-   [Go](#tab-panel-2628)
-   [Node.js](#tab-panel-2629)
-   [C](#tab-panel-2630)

```java
// Phase 1: Query returns only keys (no bin data)

queryPolicy.includeBinData = false;

RecordSet recordSet = client.query(queryPolicy, stmt);

List<Key> keysList = new ArrayList<>();

while (recordSet.next()) {

    keysList.add(recordSet.getKey());

}

recordSet.close();

// Phase 2: Batch operate to get city and state from report map

Key[] keys = keysList.toArray(new Key[0]);

BatchResults batchResult = client.operate(null, null, keys,

    MapOperation.getByKeyList("report",

        Arrays.asList(Value.get("city"), Value.get("state")),

        MapReturnType.VALUE)

);
```

```python
from aerospike_helpers.operations import map_operations

# Phase 1: Query returns only keys (no bin data)

query = client.query("test", "reports")

keys_list = []

for key, _, _ in query.results(options={"nobins": True}):

    keys_list.append(key)

# Phase 2: Batch operate to get city and state from report map

ops = [

    map_operations.map_get_by_key_list(

        "report", ["city", "state"], aerospike.MAP_RETURN_VALUE

    )

]

batch_results = client.batch_operate(keys_list, ops)
```

```csharp
// Phase 1: Query returns only keys (no bin data)

QueryPolicy queryPolicy = new() { includeBinData = false };

RecordSet recordSet = client.Query(queryPolicy, stmt);

List<Key> keysList = new();

while (recordSet.Next())

{

    keysList.Add(recordSet.Key);

}

recordSet.Close();

// Phase 2: Batch operate to get city and state from report map

BatchResults batchResult = client.Operate(null, null,

    keysList.ToArray(),

    MapOperation.GetByKeyList("report",

        new List<Value> { Value.Get("city"), Value.Get("state") },

        MapReturnType.VALUE)

);
```

```go
// Phase 1: Query returns only keys (no bin data)

stmt := as.NewStatement("test", "reports")

qp := as.NewQueryPolicy()

qp.IncludeBinData = false

rs, err := client.Query(qp, stmt)

if err != nil {

    log.Fatal(err)

}

var batchRecords []as.BatchRecordIfc

for res := range rs.Results() {

    if res.Err != nil {

        log.Fatal(res.Err)

    }

    batchRecords = append(batchRecords,

        as.NewBatchReadOps(nil, res.Record.Key,

            as.MapGetByKeyListOp("report",

                []any{"city", "state"}, as.MapReturnType.VALUE)))

}

// Phase 2: Batch operate to get city and state from report map

err = client.BatchOperate(nil, batchRecords)

if err != nil {

    log.Fatal(err)

}
```

```javascript
const Aerospike = await import("aerospike");

const maps = Aerospike.maps;

// Phase 1: Query returns only keys (no bin data)

const query = client.query("test", "reports");

query.nobins = true;

const records = await query.results();

// Phase 2: Batch operate to get city and state from report map

const batchRecords = records.map((rec) => ({

    type: Aerospike.batchType.BATCH_READ,

    key: rec.key,

    ops: [maps.getByKeyList("report", ["city", "state"],

        maps.returnType.VALUE)],

}));

const batchResults = await client.batchRead(batchRecords);
```

```c
// Phase 1: Query returns only keys (no bin data)

as_query q;

as_query_init(&q, "test", "reports");

q.no_bins = true;

// Collect keys in callback (simplified)

aerospike_query_foreach(&as, &err, NULL, &q, collect_keys_cb, &keys_vec);

as_query_destroy(&q);

// Phase 2: Batch operate to get city and state from report map

as_arraylist key_list;

as_arraylist_init(&key_list, 2, 0);

as_arraylist_append_str(&key_list, "city");

as_arraylist_append_str(&key_list, "state");

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_map_get_by_key_list(

    &ops, "report", NULL, (as_list*)&key_list, AS_MAP_RETURN_VALUE);

aerospike_batch_operate(&as, &err, NULL, NULL, batch, &ops,

    batch_cb, NULL);

as_operations_destroy(&ops);
```

**After (single query with projection ops):** One query returns the projected data.

-   [Java](#tab-panel-2631)
-   [Python](#tab-panel-2632)
-   [C#](#tab-panel-2633)
-   [Go](#tab-panel-2634)
-   [Node.js](#tab-panel-2635)
-   [C](#tab-panel-2636)

```java
stmt.setOperations(

    MapOperation.getByKeyList("report",

        Arrays.asList(Value.get("city"), Value.get("state")),

        MapReturnType.VALUE)

);

RecordSet recordSet = client.query(null, stmt);

while (recordSet.next()) {

    Record record = recordSet.getRecord();

}

recordSet.close();
```

```python
from aerospike_helpers.operations import map_operations

query = client.query("test", "reports")

query.add_ops([

    map_operations.map_get_by_key_list(

        "report", ["city", "state"], aerospike.MAP_RETURN_VALUE

    )

])

for key, _, bins in query.results():

    print(bins)
```

```csharp
stmt.Operations = new Operation[] {

    MapOperation.GetByKeyList("report",

        new List<Value> { Value.Get("city"), Value.Get("state") },

        MapReturnType.VALUE)

};

RecordSet recordSet = client.Query(null, stmt);

while (recordSet.Next())

{

    Record record = recordSet.Record;

}

recordSet.Close();
```

```go
stmt := as.NewStatement("test", "reports")

stmt.Operations = []*as.Operation{

    as.MapGetByKeyListOp("report",

        []any{"city", "state"}, as.MapReturnType.VALUE),

}

rs, err := client.Query(nil, stmt)

if err != nil {

    log.Fatal(err)

}

for res := range rs.Results() {

    if res.Err != nil {

        log.Fatal(res.Err)

    }

    fmt.Println(res.Record.Bins)

}
```

```javascript
const Aerospike = await import("aerospike");

const maps = Aerospike.maps;

const query = client.query("test", "reports");

query.ops = [

    maps.getByKeyList("report", ["city", "state"],

        maps.returnType.VALUE),

];

const records = await query.results();

for (const rec of records) {

    console.log(rec.bins);

}
```

```c
as_arraylist key_list;

as_arraylist_init(&key_list, 2, 0);

as_arraylist_append_str(&key_list, "city");

as_arraylist_append_str(&key_list, "state");

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_map_get_by_key_list(

    &ops, "report", NULL, (as_list*)&key_list, AS_MAP_RETURN_VALUE);

as_query q;

as_query_init(&q, "test", "reports");

q.ops = &ops;

aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);

as_operations_destroy(&ops);

as_query_destroy(&q);
```

## Example: Read expression in projection

Use a read expression to compute a value and return it in a computed bin:

-   [Java](#tab-panel-2637)
-   [Python](#tab-panel-2638)
-   [C#](#tab-panel-2639)
-   [Go](#tab-panel-2640)
-   [Node.js](#tab-panel-2641)
-   [C](#tab-panel-2642)

```java
Expression readExp = Exp.build(

    Exp.sub(Exp.intBin("posted"), Exp.intBin("occurred"))

);

stmt.setOperations(

    ExpOperation.read("daysToPost", readExp, ExpReadFlags.DEFAULT)

);

RecordSet recordSet = client.query(null, stmt);
```

```python
from aerospike_helpers.expressions.arithmetic import Sub

from aerospike_helpers.expressions import base as exp

from aerospike_helpers.operations import expression_operations as expr_ops

read_exp = Sub(exp.IntBin("posted"), exp.IntBin("occurred")).compile()

query = client.query("test", "reports")

query.add_ops([

    expr_ops.expression_read("daysToPost", read_exp)

])

for key, _, bins in query.results():

    print(bins["daysToPost"])
```

```csharp
Expression readExp = Exp.Build(

    Exp.Sub(Exp.IntBin("posted"), Exp.IntBin("occurred"))

);

stmt.Operations = new Operation[] {

    ExpOperation.Read("daysToPost", readExp, ExpReadFlags.DEFAULT)

};

RecordSet recordSet = client.Query(null, stmt);
```

```go
readExp := as.ExpNumSub(as.ExpIntBin("posted"), as.ExpIntBin("occurred"))

stmt := as.NewStatement("test", "reports")

stmt.Operations = []*as.Operation{

    as.ExpReadOp("daysToPost", readExp, as.ExpReadFlagDefault),

}

rs, err := client.Query(nil, stmt)

if err != nil {

    log.Fatal(err)

}

for res := range rs.Results() {

    if res.Err != nil {

        log.Fatal(res.Err)

    }

    fmt.Println(res.Record.Bins["daysToPost"])

}
```

```javascript
const Aerospike = await import("aerospike");

const exp = Aerospike.exp;

const readExp = exp.sub(exp.binInt("posted"), exp.binInt("occurred"));

const query = client.query("test", "reports");

query.ops = [

    exp.operations.read("daysToPost", readExp, exp.expReadFlags.DEFAULT),

];

const records = await query.results();

for (const rec of records) {

    console.log(rec.bins.daysToPost);

}
```

```c
as_exp_build(read_exp,

    as_exp_sub(as_exp_bin_int("posted"), as_exp_bin_int("occurred")));

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_exp_read(&ops, "daysToPost", read_exp, AS_EXP_READ_DEFAULT);

as_query q;

as_query_init(&q, "test", "reports");

q.ops = &ops;

aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);

as_exp_destroy(read_exp);

as_operations_destroy(&ops);

as_query_destroy(&q);
```

## Related documentation

-   [Primary index queries](https://aerospike.com/docs/develop/learn/queries/primary-index/)
-   [Secondary index queries](https://aerospike.com/docs/develop/learn/queries/secondary-index/)
-   [Bin operations](https://aerospike.com/docs/develop/learn/bin-operations/)
-   [Batched commands](https://aerospike.com/docs/develop/learn/batch/)
-   [Operation expressions](https://aerospike.com/docs/develop/expressions#operation-expressions)
-   [Path expressions](https://aerospike.com/docs/develop/expressions/path/)