# Secondary index

::: 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 also use [record filtering 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/go/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.

```go
import (

    "fmt"

    "github.com/aerospike/aerospike-client-go/v6"

)

// Establishes a connection to the server

client, err := aerospike.NewClient("127.0.0.1", 3000)

if err != nil {

  log.Fatal(err)

}

defer client.Close()
```

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.

```go
// Create index task

task, err := client.CreateIndex(nil, // write policy

    "sandbox", // namespace

    "ufodata", // set name

    "occurred_idx", // index name

    "occurred", // bin name

    aerospike.NUMERIC) // index type

if err != nil {

  log.Fatal(err)

}

// Wait for the task to complete

<-task.OnComplete()
```

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.

```go
// Drop an index

err := client.DropIndex(nil, // write policy

    "sandbox", // namespace

    "ufodata", // set name

    "occurred_idx") // index name

if err != nil {

  log.Fatal(err)

}
```

## 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`.

```go
// Create statement

stmt := aerospike.NewStatement("sandbox", "ufodata")

// Create index filter

stmt.SetFilter(aerospike.NewRangeFilter("occurred", 20210101, 20211231));

// Execute the query

recordSet, err := client.Query(nil, stmt);

if err != nil {

  log.Fatal(err)

}

// Get the results

for records := range recordSet.Results() {

    if records != nil {

        // Do something

        fmt.Printf("Record: %v \\n", records.Record.Bins)

    }

}

recordSet.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

```go
// Create geo region

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.

```go
// Create new query policy

queryPolicy := aerospike.NewQueryPolicy()

queryPolicy.FilterExpression = aerospike.ExpGeoCompare(

    aerospike.ExpGeoBin("location"),

    aerospike.ExpGeoVal(region))

// Create statement

stmt := aerospike.NewStatement("sandbox", "ufodata")

// Create index filter

stmt.SetFilter(aerospike.NewRangeFilter("occurred", 20210101, 20211231))

// Execute the query

recordSet, err := client.Query(queryPolicy, stmt)

if err != nil {

  log.Fatal(err)

}

// Get the results

for records := range recordSet.Results() {

    if records != nil {

        // Do something

        fmt.Printf("Record: %v \\n", records.Record.Bins)

    }

}

recordSet.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.

```go
// Create new query policy

queryPolicy := aerospike.NewQueryPolicy()

queryPolicy.FilterExpression = aerospike.ExpGeoCompare(

    aerospike.ExpGeoBin("location"),

    aerospike.ExpGeoVal(region))

// do not include record bins

queryPolicy.IncludeBinData = false

// Create statement

stmt := aerospike.NewStatement("sandbox", "ufodata")

// Create index filter

stmt.SetFilter(aerospike.NewRangeFilter("occurred", 20210101, 20211231))

// Create keys list

keys := []*aerospike.Key{}

// Execute the query

recordSet, err := client.Query(queryPolicy, stmt)

if err != nil {

  log.Fatal(err)

}

// Get the results

for records := range recordSet.Results() {

    if records != nil {

        keys = append(keys, records.Record.Key)

    }

}

recordSet.Close()

// Create map key list

mapKeys := []interface{}{"city", "state"}

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

records, err := client.BatchGetOperate(nil, keys,

    aerospike.MapGetByKeyListOp("report", mapKeys, aerospike.MapReturnType.VALUE))

if err != nil {

  log.Fatal(err)

}

// Access the records

for _, record := range records {

    if record != nil {

        // Do something

        fmt.Printf("Record: %v \\n", record.Bins)

    }

}
```

## Pagination

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

## Code block

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

```go
import (

    "fmt"

    "github.com/aerospike/aerospike-client-go/v6"

)

func main(){

    // Establishes a connection to the server

    client, err := aerospike.NewClient("127.0.0.1", 3000)

    if err != nil {

        log.Fatal(err)

    }

    defer client.Close()

    // Create geo region

    region := "{ " +

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

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

    "     [ " +

    "           [-109.061279296875, 36.97622678464096], " +

    "           [-102.01904296874999, 36.97622678464096], " +

    "           [-102.01904296874999, 41.0130657870063], " +

    "           [-109.061279296875, 41.0130657870063], " +

    "           [-109.061279296875, 36.97622678464096] " +

    "       ] " +

    "   ] " +

    "}"

    // Create new query policy

    queryPolicy := aerospike.NewQueryPolicy()

    queryPolicy.FilterExpression = aerospike.ExpGeoCompare(

        aerospike.ExpGeoBin("location"),

        aerospike.ExpGeoVal(region))

    // Create statement

    stmt := aerospike.NewStatement("sandbox", "ufodata")

    // Create index filter

    stmt.SetFilter(aerospike.NewRangeFilter("occurred", 20210101, 20211231))

    // Execute the query

    recordSet, err := client.Query(queryPolicy, stmt)

    if err != nil {

        log.Fatal(err)

    }

    // Get the results

    for records := range recordSet.Results() {

        if records != nil {

            // Do something

            fmt.Printf("Record: %v \\n", records.Record.Bins)

        }

    }

    recordSet.Close()

}
```