# Secondary index queries

::: note
Prior to Server 6.0.0, secondary index (SI) queries were called queries. See the [Queries](https://aerospike.com/docs/develop/learn/queries) feature guide for more information.
:::

Jump to the [Code block](#code-block) for a combined complete example.

Basic SI queries can employ the following index filters:

-   Equality comparison against string or numeric indexes.
-   Range comparison against numeric indexes. Range result sets are inclusive of lower and upper limits.
-   [Point-In-Region or Region-Contain-Point](https://aerospike.com/docs/develop/data-types/geospatial#geospatial-index) comparisons against geo indexes.

You can use [record filtering with expressions](https://aerospike.com/docs/develop/expressions/#record-filtering-with-expressions) with secondary index queries.

## Policies

See [Basic Queries](https://aerospike.com/docs/develop/client/csharp/usage/multi/queries/basic#policies) for query policy information.

## Setup

The following examples will use the setup and record structure below to illustrate secondary index queries in an Aerospike database.

```csharp
using Aerospike.Client;

using System;

using System.Collections;

// Define host configuration

Host config = new Host("127.0.0.1", 3000);

// Establishes a connection to the server

AerospikeClient client = new AerospikeClient(null, config);
```

The record structure:

```asciidoc
Occurred: Integer

Reported: Integer

Posted: Integer

Report: Map

{

    shape: List,

    summary: String,

    city: String,

    state: String,

    duration: String

}

Location: GeoJSON
```

## Create an index

You should plan and schedule index creation/removal on production systems as described in [Secondary Index Capacity Planning](https://aerospike.com/docs/database/manage/planning/capacity/secondary-indexes). An index consumes RAM for every index entry. Background index creation/removal can take a substantial amount of resources. It is important to carefully plan and schedule index creation/removal on production systems.

The following command uses the [Aerospike Admin (asadm)](https://aerospike.com/docs/database/tools/asadm) to create an integer index on the `sandbox` namespace, `ufodata` set, and `occurred` bin. This is the recommended way to create a secondary index.

```plaintext
asadm -e 'enable; manage sindex create numeric occurred_idx ns sandbox set ufodata bin occurred'
```

The Aerospike Client API can be used to create a secondary index as well. The following example creates the same index as the example above.

```csharp
// Create index task

IndexTask task = client.CreateIndex(null, //policy

    "sandbox", // namespace

    "ufodata", // set name

    "occurred_idx", // index name

    "occurred", // bin name

    IndexType.NUMERIC //index type

);

// Wait for the task to complete

task.Wait();
```

In this example, the `IndexType` is `NUMERIC`. Aerospike supports index types `NUMERIC`,`STRING`, `GEO2DSPHERE`, and `BLOB` as of server 7.0.

## Remove an index

The following command uses the [Aerospike Admin (asadm)](https://aerospike.com/docs/database/tools/asadm) to remove the index created above.

```plaintext
asadm -e 'enable; manage sindex delete occurred_idx ns sandbox set ufodata'
```

The Aerospike Client API can be used to remove a secondary index as well. The following example removes the same index as the example above.

```csharp
// Create index task

IndexTask task = client.DropIndex(null, //policy

    "sandbox", // namespace

    "ufodata", // set name

    "occurred_idx", // index name

);

// Wait for the task to complete

task.Wait();
```

## Query an index

The following example queries the `sandbox` namespace and `ufodata` set name, with an inclusive range filter on the `occurred` bin, returning records with a bin value between `20210101` and `20211231`.

```csharp
// Create statement

Statement stmt = new Statement();

// Set namespace and set name

stmt.SetNamespace("sandbox");

stmt.SetSetName("ufodata");

// Create index filter

stmt.SetFilter(Filter.Range("occurred", 20210101, 20211231));

// Execute the query

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

// Get the results

try

{

    while(recordSet.Next())

    {

        Key key = recordSet.Key;

        Record record = recordSet.Record;

        // Do something

        Console.WriteLine("Key: {0} | Record: {1}\\n", key.userKey, record.ToString().Split("bins:")[1]);

    }

}

finally

{

    recordSet.Close();

}

// Close the connection to the server

client.Close();
```

## Query an index with a Filter Expression

The following example will use the geo string defined in the expandable section below.

View the language specific data creation

```csharp
// Create geo region

string region = "{ " +

"   \\"type\\": \\"Polygon\\", " +

"   \\"coordinates\\": [ " +

"     [ " +

"           [-109.061279296875, 36.97622678464096], " +

"           [-102.01904296874999, 36.97622678464096], " +

"           [-102.01904296874999, 41.0130657870063], " +

"           [-109.061279296875, 41.0130657870063], " +

"           [-109.061279296875, 36.97622678464096] " +

"       ] " +

"   ] " +

"}";
```

This example queries the same namespace and set name, while using the same index filter as the example above, but adds a Filter Expression to the query policy to only return records with a `location` bin value within the geo region specified above.

```csharp
// Create new query policy

QueryPolicy queryPolicy = new QueryPolicy();

queryPolicy.filterExp = Exp.Build(

    Exp.GeoCompare(Exp.GeoBin("location"), Exp.Geo(region))

);

// Create statement

Statement stmt = new Statement();

// Set namespace and set name

stmt.SetNamespace("sandbox");

stmt.SetSetName("ufodata");

// Create index filter

stmt.SetFilter(Filter.Range("occurred", 20210101, 20211231));

// Execute the query

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

// Get the results

try

{

    while(recordSet.Next())

    {

        Key key = recordSet.Key;

        Record record = recordSet.Record;

        // Do something

        Console.WriteLine("Key: {0} | Record: {1}\\n", key.userKey, record.ToString().Split("bins:")[1]);

    }

}

finally

{

    recordSet.Close();

}

// Close the connection to the server

client.Close();
```

## Query an index then create a batch operation on the returned keys

Currently, Transaction and CDT operations are not available for basic queries. The following example shows how a basic query and a batch operation can be combined to use transaction operations on the results.

This example queries the same namespace and set name, while using the same index filter, geo region, and Filter Expression as above, but only returns record metadata, which is then used to create a batch operation to return only the `city` and `state` from the `report` map.

```csharp
// Create new query policy

QueryPolicy queryPolicy = new QueryPolicy();

queryPolicy.filterExp = Exp.Build(

    // region defined in expandable section above

    Exp.GeoCompare(Exp.GeoBin("location"), Exp.Geo(region))

);

// do not include record bins

queryPolicy.includeBinData = false;

// Create statement

Statement stmt = new Statement();

// Set namespace and set name

stmt.SetNamespace("sandbox");

stmt.SetSetName("ufodata");

// Create index filter

stmt.SetFilter(Filter.Range("occurred", 20210101, 20211231));

// Create keys list

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

// Execute the query

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

// Get the results

try

{

    while(recordSet.Next())

    {

        keysList.Add(recordSet.Key);

    }

}

finally

{

    recordSet.Close();

}

// Convert list to array

Key[] keys = keysList.ToArray();

// Create map key list

string[] mapKeys = {"city", "state"};

// Get 'city' and 'state' from report map for each record

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

    MapOperation.GetByKeyList("report", mapKeys, MapReturnType.VALUE)

);

// Access the records

foreach (BatchRecord batchRecord in batchResult.records)

{

    Key key = batchRecord.key;

    Record record = batchRecord.record;

    if(record != null)

    {

        // Do something

        Console.WriteLine("Key: {0} | Record: {1}\\n", key.userKey, record.ToString().Split("bins:")[1]);

    }

}

// Close the connection to the server

client.Close();
```

## Pagination

See [Pagination](https://aerospike.com/docs/develop/client/csharp/usage/multi/queries/primary#pagination) for more information.

## Code block

Expand this section for a single code block to execute a basic SI query

```csharp
using Aerospike.Client;

using System;

using System.Collections;

// Define host configuration

Host config = new Host("127.0.0.1", 3000);

// Establishes a connection to the server

AerospikeClient client = new AerospikeClient(null, config);

string region = "{ " +

"   \\"type\\": \\"Polygon\\", " +

"   \\"coordinates\\": [ " +

"     [ " +

"           [-109.061279296875, 36.97622678464096], " +

"           [-102.01904296874999, 36.97622678464096], " +

"           [-102.01904296874999, 41.0130657870063], " +

"           [-109.061279296875, 41.0130657870063], " +

"           [-109.061279296875, 36.97622678464096] " +

"       ] " +

"   ] " +

"}";

QueryPolicy queryPolicy = new QueryPolicy();

queryPolicy.filterExp = Exp.Build(

    Exp.GeoCompare(Exp.GeoBin("location"), Exp.Geo(region))

);

// Create statement

Statement stmt = new Statement();

// Set namespace and set name

stmt.SetNamespace("sandbox");

stmt.SetSetName("ufodata");

// Create index filter

stmt.SetFilter(Filter.Range("occurred", 20210101, 20211231));

// Execute the query

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

// Get the results

try

{

    while(recordSet.Next())

    {

        Key key = recordSet.Key;

        Record record = recordSet.Record;

        // Do something

        Console.WriteLine("Key: {0} | Record: {1}\\n", key.userKey, record.ToString().Split("bins:")[1]);

    }

}

finally

{

    recordSet.Close();

}

// Close the connection to the server

client.Close();
```