Índices secundários

Um índice secundário é útil para pesquisar rapidamente os dados por uma ou mais colunas não chave.

Por exemplo, não é preciso fazer nada especial para indexar a coluna de chave primária de Singers, porque ela é indexada automaticamente. Você pode procurar valores da coluna FirstName referentes a um intervalo de SingerIds na tabela Singers. No entanto, para conseguir procurar rapidamente um conjunto de SingerIds referentes a um determinado intervalo de valores de FirstName e LastName sem ter que verificar toda a tabela, você deve criar um índice secundário na tabela Singers.

Como criar um índice secundário

Use a instrução CREATE INDEX para definir um índice secundário no esquema. Veja alguns exemplos:

Para indexar todos os Singers no banco de dados por nome e sobrenome:

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName)

Para criar um índice de todas as Songs no banco de dados pelo valor de SongName:

CREATE INDEX SongsBySongName ON Songs(SongName)

Para indexar apenas as canções de um cantor específico, intercale o índice na tabela Singers:

CREATE INDEX SongsBySingerSongName ON Songs(SingerId, SongName),
    INTERLEAVE IN Singers

Para indexar apenas as canções de um álbum específico:

CREATE INDEX SongsBySingerAlbumSongName ON Songs(SingerId, AlbumId, SongName),
    INTERLEAVE IN Albums

Para indexar em ordem decrescente de SongName:

CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC),
    INTERLEAVE IN Albums

Observe que a anotação DESC acima aplica-se apenas a SongName. Para indexar por ordem decrescente de outras chaves de índice, anote-as com DESC também: SingerId DESC, AlbumId DESC.

O tempo de criação do índice pode variar de minutos a muitas horas. O tempo necessário para criar um índice secundário depende de:

  • tamanho do conjunto de dados
  • do número de nodes na instância;
  • carga na instância.

Não use uma coluna de carimbo de data/hora de commit como a primeira parte da chave principal de um índice secundário. Usar uma coluna do carimbo de data/hora de commit como a primeira parte de uma chave principal cria pontos de acesso e reduz desempenho dos dados.

Você pode cancelar uma operação de criação de índice de longa duração usando o método projects.instances.databases.operations.cancel.

Consultar usando índices

Suponha que você adicionou uma coluna MarketingBudget à tabela Albums e criou um índice AlbumsByAlbumTitle:

CREATE TABLE Albums (
  SingerId         INT64 NOT NULL,
  AlbumId          INT64 NOT NULL,
  AlbumTitle       STRING(MAX),
  MarketingBudget  INT64,
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);

Para ler dados usando esse índice, você pode executar uma instrução de consulta SQL ou usar a interface de leitura do Cloud Spanner. A maneira mais comum de buscar dados desse índice é usando a interface de consulta SQL, que permite consultar dados de qualquer coluna da tabela indexada.

Diretiva de índice

Se quiser que o Cloud Spanner consulte uma tabela MyTable usando o índice MyTableIndex, é preciso indicar o nome desse índice usando uma diretiva de índice na instrução SQL. (O Cloud Spanner escolhe um índice automaticamente apenas em circunstâncias raras. Ele não escolhe automaticamente um índice secundário quando a consulta solicita qualquer coluna que não esteja armazenada no índice.) Sintaxe da diretiva de índice:

FROM MyTable@{FORCE_INDEX=MyTableIndex}

Exemplo de instrução de consulta SQL que usa uma diretiva de índice:

SELECT AlbumId, AlbumTitle, MarketingBudget
FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
WHERE SingerId = 1 AND AlbumTitle >= 'Aardvark' AND AlbumTitle < 'Goo'

Observações sobre o uso de diretivas de índice:

  • Todos os índices no Cloud Spanner armazenam todas as colunas de chave da tabela base, todas as colunas indexadas e todas as colunas especificadas na cláusula STORING opcional da definição do índice.
  • Para consultas SQL que usam uma diretiva de índice, o processador de consulta SQL do Cloud Spanner pode precisar ler colunas que são requeridas pela consulta, mas que não estão armazenadas no índice. O processador de consulta recupera essas colunas usando uma mescla entre o índice e a tabela base.
    • Por exemplo, na definição do índice AlbumsByAlbumTitle acima, a coluna MarketingBudget não está armazenada no índice, mas é uma das colunas selecionadas na consulta SQL nesse índice. Para buscar essa coluna, o Cloud Spanner faz uma pesquisa interna da coluna MarketingBudget da tabela base e se junta com dados do índice para retornar os resultados da consulta. É possível evitar essa junção extra armazenando a coluna MarketingBudget no índice. Consulte a cláusula STORING abaixo.
  • As diretivas de índice não são dicas, o que significa que um erro será gerado se:

O uso de índices secundários em consultas SQL pode acelerar consultas comuns, conforme descrito em Práticas recomendadas de SQL. Os seguintes snippets de código mostram como escrever e executar consultas que buscam os valores de AlbumId, AlbumTitle e MarketingBudget usando o índice AlbumsByAlbumTitle:

C#

string connectionString =
$"Data Source=projects/{projectId}/instances/{instanceId}"
+ $"/databases/{databaseId}";
// Create connection to Cloud Spanner.
using (var connection = new SpannerConnection(connectionString))
{
    var cmd = connection.CreateSelectCommand(
        "SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@ "
        + "{FORCE_INDEX=AlbumsByAlbumTitle} "
        + $"WHERE AlbumTitle >= @startTitle "
        + $"AND AlbumTitle < @endTitle",
        new SpannerParameterCollection {
            {"startTitle", SpannerDbType.String},
            {"endTitle", SpannerDbType.String} });
    cmd.Parameters["startTitle"].Value = startTitle;
    cmd.Parameters["endTitle"].Value = endTitle;
    using (var reader = await cmd.ExecuteReaderAsync())
    {
        while (await reader.ReadAsync())
        {
            Console.WriteLine("AlbumId : "
            + reader.GetFieldValue<string>("AlbumId")
            + " AlbumTitle : "
            + reader.GetFieldValue<string>("AlbumTitle")
            + " MarketingBudget : "
            + reader.GetFieldValue<string>("MarketingBudget"));
        }
    }
}

Go

func queryUsingIndex(ctx context.Context, w io.Writer, client *spanner.Client) error {
	stmt := spanner.Statement{
		SQL: `SELECT AlbumId, AlbumTitle, MarketingBudget
			FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
			WHERE AlbumTitle >= @start_title AND AlbumTitle < @end_title`,
		Params: map[string]interface{}{
			"start_title": "Aardvark",
			"end_title":   "Goo",
		},
	}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		var albumID int64
		var marketingBudget spanner.NullInt64
		var albumTitle string
		if err := row.ColumnByName("AlbumId", &albumID); err != nil {
			return err
		}
		if err := row.ColumnByName("AlbumTitle", &albumTitle); err != nil {
			return err
		}
		if err := row.ColumnByName("MarketingBudget", &marketingBudget); err != nil {
			return err
		}
		budget := "NULL"
		if marketingBudget.Valid {
			budget = strconv.FormatInt(marketingBudget.Int64, 10)
		}
		fmt.Fprintf(w, "%d %s %s\n", albumID, albumTitle, budget)
	}
	return nil
}

Java

static void queryUsingIndex(DatabaseClient dbClient) {
  Statement statement =
      Statement
          // We use FORCE_INDEX hint to specify which index to use. For more details see
          // https://cloud.google.com/spanner/docs/query-syntax#from-clause
          .newBuilder(
              "SELECT AlbumId, AlbumTitle, MarketingBudget\n"
                  + "FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}\n"
                  + "WHERE AlbumTitle >= @StartTitle AND AlbumTitle < @EndTitle")
          // We use @BoundParameters to help speed up frequently executed queries.
          //  For more details see https://cloud.google.com/spanner/docs/sql-best-practices
          .bind("StartTitle")
          .to("Aardvark")
          .bind("EndTitle")
          .to("Goo")
          .build();

  ResultSet resultSet = dbClient.singleUse().executeQuery(statement);
  while (resultSet.next()) {
    System.out.printf(
        "%d %s %s\n",
        resultSet.getLong("AlbumId"),
        resultSet.getString("AlbumTitle"),
        resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
  }
}

Node.js

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const startTitle = 'Ardvark';
// const endTitle = 'Goo';

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

const query = {
  sql: `SELECT AlbumId, AlbumTitle, MarketingBudget
        FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
        WHERE AlbumTitle >= @startTitle AND AlbumTitle <= @endTitle`,
  params: {
    startTitle: startTitle,
    endTitle: endTitle,
  },
};

// Queries rows from the Albums table
try {
  const [rows] = await database.run(query);

  rows.forEach(row => {
    const json = row.toJSON();
    const marketingBudget = json.MarketingBudget
      ? json.MarketingBudget
      : null; // This value is nullable
    console.log(
      `AlbumId: ${json.AlbumId}, AlbumTitle: ${
        json.AlbumTitle
      }, MarketingBudget: ${marketingBudget}`
    );
  });
} catch (err) {
  console.error('ERROR:', err);
} finally {
  // Close the database when finished.
  database.close();
}

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Queries sample data from the database using SQL and an index.
 *
 * The index must exist before running this sample. You can add the index
 * by running the `add_index` sample or by running this DDL statement against
 * your database:
 *
 *     CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)
 *
 * Example:
 * ```
 * query_data_with_index($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 * @param string $startTitle The start of the title index.
 * @param string $endTitle   The end of the title index.
 */
function query_data_with_index($instanceId, $databaseId, $startTitle, $endTitle)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $parameters = [
        'startTitle' => $startTitle,
        'endTitle' => $endTitle
    ];

    $results = $database->execute(
        'SELECT AlbumId, AlbumTitle, MarketingBudget ' .
        'FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} ' .
        'WHERE AlbumTitle >= @startTitle AND AlbumTitle < @endTitle',
        ['parameters' => $parameters]
    );

    foreach ($results as $row) {
        printf('AlbumId: %s, AlbumTitle: %s, MarketingBudget: %d' . PHP_EOL,
            $row['AlbumId'], $row['AlbumTitle'], $row['MarketingBudget']);
    }
}

Python

def query_data_with_index(
        instance_id, database_id, start_title='Aardvark', end_title='Goo'):
    """Queries sample data from the database using SQL and an index.

    The index must exist before running this sample. You can add the index
    by running the `add_index` sample or by running this DDL statement against
    your database:

        CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)

    This sample also uses the `MarketingBudget` column. You can add the column
    by running the `add_column` sample or by running this DDL statement against
    your database:

        ALTER TABLE Albums ADD COLUMN MarketingBudget INT64

    """
    from google.cloud.spanner_v1.proto import type_pb2

    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    params = {
        'start_title': start_title,
        'end_title': end_title
    }
    param_types = {
        'start_title': type_pb2.Type(code=type_pb2.STRING),
        'end_title': type_pb2.Type(code=type_pb2.STRING)
    }

    with database.snapshot() as snapshot:
        results = snapshot.execute_sql(
            "SELECT AlbumId, AlbumTitle, MarketingBudget "
            "FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle} "
            "WHERE AlbumTitle >= @start_title AND AlbumTitle < @end_title",
            params=params, param_types=param_types)

        for row in results:
            print(
                u'AlbumId: {}, AlbumTitle: {}, '
                'MarketingBudget: {}'.format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"
# start_title = "An album title to start with such as 'Ardvark'"
# end_title   = "An album title to end with such as 'Goo'"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

sql_query = "SELECT AlbumId, AlbumTitle, MarketingBudget
             FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
             WHERE AlbumTitle >= @start_title AND AlbumTitle < @end_title"

params      = { start_title: start_title, end_title: end_title }
param_types = { start_title: :STRING,     end_title: :STRING }

client.execute(sql_query, params: params, types: param_types).rows.each do |row|
  puts "#{row[:AlbumId]} #{row[:AlbumTitle]} #{row[:MarketingBudget]}"
end

Ler usando índices

Outra maneira de buscar dados de um índice é usando a interface do Cloud Spanner para executar uma leitura com um índice. Por exemplo, o seguinte snippet de código busca os valores de AlbumId e AlbumTitle usando o índice AlbumsByAlbumTitle:

C#

O código para ler dados usando um índice é o mesmo que o exemplo anterior de consulta usando índices.

string connectionString =
$"Data Source=projects/{projectId}/instances/{instanceId}"
+ $"/databases/{databaseId}";
// Create connection to Cloud Spanner.
using (var connection = new SpannerConnection(connectionString))
{
    var cmd = connection.CreateSelectCommand(
        "SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@ "
        + "{FORCE_INDEX=AlbumsByAlbumTitle} "
        + $"WHERE AlbumTitle >= @startTitle "
        + $"AND AlbumTitle < @endTitle",
        new SpannerParameterCollection {
            {"startTitle", SpannerDbType.String},
            {"endTitle", SpannerDbType.String} });
    cmd.Parameters["startTitle"].Value = startTitle;
    cmd.Parameters["endTitle"].Value = endTitle;
    using (var reader = await cmd.ExecuteReaderAsync())
    {
        while (await reader.ReadAsync())
        {
            Console.WriteLine("AlbumId : "
            + reader.GetFieldValue<string>("AlbumId")
            + " AlbumTitle : "
            + reader.GetFieldValue<string>("AlbumTitle")
            + " MarketingBudget : "
            + reader.GetFieldValue<string>("MarketingBudget"));
        }
    }
}

Go

func readUsingIndex(ctx context.Context, w io.Writer, client *spanner.Client) error {
	iter := client.Single().ReadUsingIndex(ctx, "Albums", "AlbumsByAlbumTitle", spanner.AllKeys(),
		[]string{"AlbumId", "AlbumTitle"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var albumID int64
		var albumTitle string
		if err := row.Columns(&albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %s\n", albumID, albumTitle)
	}
}

Java

static void readUsingIndex(DatabaseClient dbClient) {
  ResultSet resultSet =
      dbClient
          .singleUse()
          .readUsingIndex(
              "Albums",
              "AlbumsByAlbumTitle",
              KeySet.all(),
              Arrays.asList("AlbumId", "AlbumTitle"));
  while (resultSet.next()) {
    System.out.printf("%d %s\n", resultSet.getLong(0), resultSet.getString(1));
  }
}

Node.js

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

const albumsTable = database.table('Albums');

const query = {
  columns: ['AlbumId', 'AlbumTitle'],
  keySet: {
    all: true,
  },
  index: 'AlbumsByAlbumTitle',
};

// Reads the Albums table using an index
try {
  const [rows] = await albumsTable.read(query);

  rows.forEach(row => {
    const json = row.toJSON();
    console.log(`AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`);
  });
} catch (err) {
  console.error('ERROR:', err);
} finally {
  // Close the database when finished.
  database.close();
}

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Reads sample data from the database using an index.
 *
 * The index must exist before running this sample. You can add the index
 * by running the `add_index` sample or by running this DDL statement against
 * your database:
 *
 *     CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)
 *
 * Example:
 * ```
 * read_data_with_index($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function read_data_with_index($instanceId, $databaseId)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $keySet = $spanner->keySet(['all' => true]);
    $results = $database->read(
        'Albums',
        $keySet,
        ['AlbumId', 'AlbumTitle'],
        ['index' => 'AlbumsByAlbumTitle']
    );

    foreach ($results->rows() as $row) {
        printf('AlbumId: %s, AlbumTitle: %s' . PHP_EOL,
            $row['AlbumId'], $row['AlbumTitle']);
    }
}

Python

def read_data_with_index(instance_id, database_id):
    """Reads sample data from the database using an index.

    The index must exist before running this sample. You can add the index
    by running the `add_index` sample or by running this DDL statement against
    your database:

        CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)

    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.snapshot() as snapshot:
        keyset = spanner.KeySet(all_=True)
        results = snapshot.read(
            table='Albums',
            columns=('AlbumId', 'AlbumTitle'),
            keyset=keyset,
            index='AlbumsByAlbumTitle')

        for row in results:
            print('AlbumId: {}, AlbumTitle: {}'.format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

result = client.read "Albums", [:AlbumId, :AlbumTitle],
                     index: "AlbumsByAlbumTitle"

result.rows.each do |row|
  puts "#{row[:AlbumId]} #{row[:AlbumTitle]}"
end

Observações:

  • Ao contrário do exemplo anterior que executou uma instrução de consulta SQL, este exemplo usa a interface de leitura do Cloud Spanner, que é incompatível com a capacidade de se mesclar a um índice com uma tabela de dados para pesquisar valores não armazenados no índice. Isso significa que não é possível ler a coluna MarketingBudget usando o índice no exemplo acima. Se quiser ler o valor dessa coluna, adicione a cláusula STORING (MarketingBudget) à definição do índice, conforme mostrado abaixo ou faça uma leitura manual na tabela Albums usando os valores da chave primária retornados da leitura do índice.
  • Por padrão, os valores que são lidos do índice são retornados em ordem de classificação crescente por chave de índice. Se você quiser que os valores sejam retornados em ordem decrescente por chave de índice, faça o seguinte:
    1. Anote a chave de índice com DESC:
      CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle DESC)
      • Observe que a anotação DESC se aplica a uma única chave de índice. Se houvesse mais de uma chave na lista e você quisesse que os valores fossem retornados em ordem decrescente de todas as chaves, você anotaria cada chave com DESC.
    2. Se você estiver executando uma leitura nesse índice com um KeyRange, o intervalo também precisa usar a ordem de classificação decrescente. Ou seja, o valor da chave de início precisa ser maior que o valor da chave de fim.

Cláusula STORING

Conforme mencionado acima, você tem a opção de armazenar uma cópia de uma coluna no índice usando a cláusula STORING, que oferece vantagens para consultas e chamadas de leitura usando o índice (ao custo de armazenamento extra):

  • As consultas SQL que usam o índice e selecionam as colunas armazenadas na cláusula STORING não exigem uma vinculação extra à tabela base.
  • As chamadas de leitura que usam o índice podem ler colunas armazenadas na cláusula STORING.

Por exemplo, suponha que você criou uma versão alternativa de AlbumsByAlbumTitle que armazena uma cópia da coluna MarketingBudget no índice (observe a cláusula STORING em negrito):

CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)

Quando você seleciona a coluna MarketingBudget usando uma consulta de diretiva de índice com AlbumsByAlbumTitle2, o processador SQL lê a coluna diretamente do índice, em vez de mesclá-lo com a tabela base e recuperar a coluna usando a chave da tabela.

Além disso, agora você pode ler a coluna MarketingBudget na chamada de leitura diretamente, em vez de precisar procurá-la manualmente na tabela base:

C#

Leia os dados usando o índice de armazenamento executando uma consulta que especifica explicitamente o índice:

string connectionString =
$"Data Source=projects/{projectId}/instances/{instanceId}"
+ $"/databases/{databaseId}";
// Create connection to Cloud Spanner.
using (var connection = new SpannerConnection(connectionString))
{
    var cmd = connection.CreateSelectCommand(
        "SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@ "
        + "{FORCE_INDEX=AlbumsByAlbumTitle2} "
        + $"WHERE AlbumTitle >= @startTitle "
        + $"AND AlbumTitle < @endTitle",
        new SpannerParameterCollection {
            {"startTitle", SpannerDbType.String},
            {"endTitle", SpannerDbType.String} });
    cmd.Parameters["startTitle"].Value = startTitle;
    cmd.Parameters["endTitle"].Value = endTitle;
    using (var reader = await cmd.ExecuteReaderAsync())
    {
        while (await reader.ReadAsync())
        {
            Console.WriteLine("AlbumId : "
            + reader.GetFieldValue<string>("AlbumId")
            + " AlbumTitle : "
            + reader.GetFieldValue<string>("AlbumTitle")
            + " MarketingBudget : "
            + reader.GetFieldValue<string>("MarketingBudget"));
        }
    }
}

Go

func readStoringIndex(ctx context.Context, w io.Writer, client *spanner.Client) error {
	iter := client.Single().ReadUsingIndex(ctx, "Albums", "AlbumsByAlbumTitle2", spanner.AllKeys(),
		[]string{"AlbumId", "AlbumTitle", "MarketingBudget"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var albumID int64
		var marketingBudget spanner.NullInt64
		var albumTitle string
		if err := row.Columns(&albumID, &albumTitle, &marketingBudget); err != nil {
			return err
		}
		budget := "NULL"
		if marketingBudget.Valid {
			budget = strconv.FormatInt(marketingBudget.Int64, 10)
		}
		fmt.Fprintf(w, "%d %s %s\n", albumID, albumTitle, budget)
	}
}

Java

static void readStoringIndex(DatabaseClient dbClient) {
  // We can read MarketingBudget also from the index since it stores a copy of MarketingBudget.
  ResultSet resultSet =
      dbClient
          .singleUse()
          .readUsingIndex(
              "Albums",
              "AlbumsByAlbumTitle2",
              KeySet.all(),
              Arrays.asList("AlbumId", "AlbumTitle", "MarketingBudget"));
  while (resultSet.next()) {
    System.out.printf(
        "%d %s %s\n",
        resultSet.getLong(0),
        resultSet.getString(1),
        resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"));
  }
}

Node.js

// "Storing" indexes store copies of the columns they index
// This speeds up queries, but takes more space compared to normal indexes
// See the link below for more information:
// https://cloud.google.com/spanner/docs/secondary-indexes#storing_clause

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

const albumsTable = database.table('Albums');

const query = {
  columns: ['AlbumId', 'AlbumTitle', 'MarketingBudget'],
  keySet: {
    all: true,
  },
  index: 'AlbumsByAlbumTitle2',
};

// Reads the Albums table using a storing index
try {
  const [rows] = await albumsTable.read(query);

  rows.forEach(row => {
    const json = row.toJSON();
    let rowString = `AlbumId: ${json.AlbumId}`;
    rowString += `, AlbumTitle: ${json.AlbumTitle}`;
    if (json.MarketingBudget) {
      rowString += `, MarketingBudget: ${json.MarketingBudget}`;
    }
    console.log(rowString);
  });
} catch (err) {
  console.error('ERROR:', err);
} finally {
  // Close the database when finished.
  database.close();
}

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Reads sample data from the database using an index with a storing
 * clause.
 *
 * The index must exist before running this sample. You can add the index
 * by running the `add_storing_index` sample or by running this DDL statement
 * against your database:
 *
 *     CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle)
 *     STORING (MarketingBudget)
 *
 * Example:
 * ```
 * read_data_with_storing_index($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function read_data_with_storing_index($instanceId, $databaseId)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $keySet = $spanner->keySet(['all' => true]);
    $results = $database->read(
        'Albums',
        $keySet,
        ['AlbumId', 'AlbumTitle', 'MarketingBudget'],
        ['index' => 'AlbumsByAlbumTitle2']
    );

    foreach ($results->rows() as $row) {
        printf('AlbumId: %s, AlbumTitle: %s, MarketingBudget: %d' . PHP_EOL,
            $row['AlbumId'], $row['AlbumTitle'], $row['MarketingBudget']);
    }
}

Python

def read_data_with_storing_index(instance_id, database_id):
    """Reads sample data from the database using an index with a storing
    clause.

    The index must exist before running this sample. You can add the index
    by running the `add_soring_index` sample or by running this DDL statement
    against your database:

        CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle)
        STORING (MarketingBudget)

    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.snapshot() as snapshot:
        keyset = spanner.KeySet(all_=True)
        results = snapshot.read(
            table='Albums',
            columns=('AlbumId', 'AlbumTitle', 'MarketingBudget'),
            keyset=keyset,
            index='AlbumsByAlbumTitle2')

        for row in results:
            print(
                u'AlbumId: {}, AlbumTitle: {}, '
                'MarketingBudget: {}'.format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

result = client.read "Albums", [:AlbumId, :AlbumTitle, :MarketingBudget],
                     index: "AlbumsByAlbumTitle2"

result.rows.each do |row|
  puts "#{row[:AlbumId]} #{row[:AlbumTitle]} #{row[:MarketingBudget]}"
end

Como indexar NULLs

Por padrão, o Cloud Spanner indexa os valores NULL. Por exemplo, lembre-se da definição do índice SingersByFirstLastName na tabela Singers:

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName)

Todas as linhas de Singers são indexadas, mesmo que FirstName ou LastName ou ambos sejam NULL.

colunas com valores NULL são indexadas

Se um índice contiver NULLs, você poderá executar consultas SQL eficientes e leituras que usam NULLs. Por exemplo, use esta instrução de consulta SQL para encontrar todos os Singers com NULL FirstName:

SELECT s.SingerId, s.FirstName, s.LastName
FROM Singers@{FORCE_INDEX=SingersByFirstLastName} AS s
WHERE s.FirstName IS NULL

Ordem de classificação de NULLs

O Cloud Spanner classifica NULLs como o menor valor de qualquer tipo. Para uma coluna na ordem ASC, os valores NULL são classificados primeiro e, para uma coluna na ordem DESC, os valores NULL são classificados por último.

Como desativar a indexação de NULLs

Para desativar a indexação de nulos, adicione a palavra-chave NULL_FILTERED à definição do índice. Índices NULL_FILTERED são particularmente úteis para indexar colunas esparsas, onde a maioria das linhas contém um valor NULL. Nesses casos, o índice NULL_FILTERED pode ser consideravelmente menor e mais barato de manter do que um índice normal que inclui NULLs.

Veja abaixo uma definição alternativa de SingersByFirstLastName que não indexa NULLs:

CREATE NULL_FILTERED INDEX SingersByFirstLastNameNoNulls ON Singers(FirstName, LastName)

A palavra-chave NULL_FILTERED se aplica a todos os valores de coluna de chave de índice. Não é possível especificar a filtragem de NULL por coluna.

Tornar um índice NULL_FILTERED pode inutilizá-lo para certas consultas. Por exemplo, esta consulta é inválida porque não tem expressão de filtragem NULL necessária:

FROM Singers@{FORCE_INDEX=SingersByFirstLastNameNoNulls}
WHERE FirstName = "John"

Essa consulta não pode realmente usar o índice SingersByFirstLastNameNoNulls porque ele não contém nenhuma linha de Singers cujo LastName seja NULL.

Para usar o índice com sucesso, é preciso reescrever a consulta:

SELECT FirstName, LastName
FROM Singers@{FORCE_INDEX=SingersByFirstLastNameNoNulls}
WHERE FirstName = "John" AND LastName IS NOT NULL

Índices exclusivos

Os índices podem ser declarados como UNIQUE. Índices UNIQUE adicionam uma restrição aos dados que estão sendo indexados que proíbe entradas duplicadas de uma determinada chave de índice. Essa restrição é imposta pelo Cloud Spanner no momento do commit da transação. Especificamente, qualquer transação que gere várias entradas de índice para a mesma chave falhará no commit.

Se uma tabela contiver dados não UNIQUE para começar, a tentativa de criar um índice UNIQUE nela falhará.

Uma observação sobre índices UNIQUE NULL_FILTERED

Um índice UNIQUE NULL_FILTERED não impõe a exclusividade da chave de índice quando pelo menos uma das partes fundamentais do índice é NULL.

Por exemplo, se você criou a tabela e o índice a seguir:

CREATE TABLE ExampleTable (
  Key1 INT64 NOT NULL,
  Key2 INT64,
  Key3 INT64,
  Col1 INT64,
) PRIMARY KEY (Key1, Key2, Key3);

CREATE UNIQUE NULL_FILTERED INDEX ExampleIndex ON ExampleTable (Key1, Key2, Col1);

As duas linhas a seguir em ExampleTable têm os mesmos valores de chaves de índice secundário Key1, Key2 e Col1:

1, NULL, 1, 1
1, NULL, 2, 1

Como Key2 é NULL e o índice é NULL_FILTERED, as linhas não estarão presentes no índice ExampleIndex. Como eles não estão inseridos no índice, o índice não os rejeitará por violação de exclusividade em (Key1, Key2, Col1).

Se quiser que o índice imponha a exclusividade dos valores da tupla (Key1, Key2, Col1), é preciso anotar Key2 com NOT NULL na definição da tabela ou criar o índice sem NULL_FILTERED.

Como adicionar um índice

O Cloud Spanner permite que os clientes adicionem um novo índice secundário a uma tabela existente do Cloud Spanner enquanto o banco de dados continua a veicular tráfego. Como qualquer outra alteração de esquema no Cloud Spanner, adicionar um índice a um banco de dados existente não exige que o banco de dados fique off-line e não bloqueia colunas ou tabelas inteiras.

Sempre que um novo índice é adicionado a uma tabela existente, o Cloud Spanner preenche automaticamente o índice para refletir a visualização atualizada dos dados que estão sendo indexados. O Cloud Spanner gerencia este processo de preenchimento para você. Observe que o Cloud Spanner usa recursos extras durante o preenchimento do índice.

Como descartar um índice

Use a instrução DROP INDEX para descartar um índice secundário do esquema.

Para descartar o índice denominado SingersByFirstLastName:

DROP INDEX SingersByFirstLastName
Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Documentação do Cloud Spanner