セカンダリ インデックス

1 つ以上のキー以外の列で検索を行う場合、セカンダリ インデックスを使用するとデータを迅速に検索できます。

たとえば、Singers の主キー列のインデックスは自動的に生成されるため、インデックスの作成に特別な操作を行う必要はありません。Singers テーブルの SingerId の範囲で FirstName 列の値を検索できます。しかし、テーブル全体をスキャンせずに、FirstNameLastName の特定の範囲で SingerId のセットをすばやく検索するには、Singers テーブルにセカンダリ インデックスを作成する必要があります。

セカンダリ インデックスを作成する

スキーマでセカンダリ インデックスを定義するには、CREATE INDEX ステートメントを使用します。たとえば次のようなものがあります。

データベースのすべての Singers に姓名でインデックスを作成する:

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName)

データベース内のすべての SongsSongName の値でインデックスを作成する:

CREATE INDEX SongsBySongName ON Songs(SongName)

特定の歌手の曲だけにインデックスを作成するには、テーブル Singers のインデックスをインターリーブします。

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

特定のアルバムの曲のみにインデックスを作成する:

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

SongName の降順でインデックスを作成する:

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

上記の DESC アノテーションは SongName にのみ適用されます。他のインデックス キーの降順でインデックスを作成するには、DESCSingerId DESC, AlbumId DESC というアノテーションを付けます。

インデックスの作成には数分から数時間かかります。セカンダリ インデックスの作成に必要な時間は、次のものによって変わります。

  • データセットのサイズ
  • インスタンスのノードの数
  • インスタンスの負荷

セカンダリ インデックスの主キーの最初の部分に commit タイムスタンプ列を使用しないでください。commit タイムスタンプ列を主キーの最初の部分として使用すると、ホットスポットが作成され、データ処理のパフォーマンスが低下します。

projects.instances.databases.operations.cancel メソッドを使用すると、実行時間の長いインデックス作成オペレーションをキャンセルできます。

インデックスを使用したクエリ

Albums テーブルに MarketingBudget 列を追加し、インデックス 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);

このインデックスを使用してデータを読み取るには、SQL クエリ ステートメントを実行するか、Cloud Spanner の読み取りインターフェースを使用します。このインデックスからデータをフェッチする最も一般的な方法は、SQL クエリ インターフェースを使用する方法です。これにより、インデックスが作成された任意のテーブル列のデータをクエリで取得できます。

インデックス ディレクティブ

Cloud Spanner でインデックス MyTableIndex を使用してテーブル MyTable にクエリを実行する場合、SQL ステートメントでインデックス ディレクティブを使用してインデックス名を指定する必要があります。非常にまれですが、Cloud Spanner がインデックスを自動的に選択する場合もあります。特に、インデックスに格納されていない列をリクエストするクエリの場合、Cloud Spanner はセカンダリ インデックスを自動的に選択しません。インデックス ディレクティブの構文は次のとおりです。

FROM MyTable@{FORCE_INDEX=MyTableIndex}

インデックス ディレクティブを使用する SQL クエリ ステートメントの例:

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

インデックス ディレクティブを使用する場合の注意事項:

  • Cloud Spanner のすべてのインデックスには、ベーステーブルのすべてのキー列、インデックスが作成されたすべての列、インデックス定義のオプションの STORINGで指定されたすべての列が保存されます。
  • SQL クエリでインデックス ディレクティブを使用した場合、クエリに必要な列がインデックスに保存されていないと、Cloud Spanner の SQL クエリ プロセッサが列の読み取りを行います。クエリ プロセッサは、インデックスとベーステーブルを結合してこれらの列を取得します。
    • たとえば、上記の AlbumsByAlbumTitle インデックスの定義では、MarketingBudget 列がインデックスに保存されていませんが、このインデックスを使用する SQL クエリで選択されています。この列をフェッチするため、Cloud Spanner はベーステーブルの MarketingBudget 列を検索し、インデックスのデータと結合してクエリの結果を返します。この余分な結合を回避するには、MarketingBudget 列をインデックスに保存します。詳細については、後述の STORING 句をご覧ください。
  • インデックス ディレクティブはヒントではありません。次の場合にはエラーが発生します。

SQL のベスト プラクティスで説明しているように、SQL クエリでセカンダリ インデックスを使用すると、一般的なクエリの処理速度が向上する可能性があります。次のコード スニペットは、インデックス AlbumsByAlbumTitle を使用して AlbumIdAlbumTitleMarketingBudget の値をフェッチするクエリを作成して実行する方法を示しています。

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

インデックスを使用した読み取り

インデックスからデータをフェッチする場合、Cloud Spanner のインターフェースでインデックスを使用した読み取りを実行することもできます。たとえば、以下のコードはインデックス AlbumsByAlbumTitle を使用して、AlbumIdAlbumTitle の値をフェッチします。

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

注:

  • SQL クエリ ステートメントを実行する前の例と異なり、この例では Cloud Spanner の読み取りインターフェースを使用しています。このインターフェースでは、インデックスとデータテーブルを結合して、インデックスにない値を検索することはできません。つまり、上の例のインデックスを使用して列 MarketingBudget を読み取ることはできません。この列の値を読み取るには、下の例のようにインデックス定義に STORING (MarketingBudget) 句を追加するか、インデックス読み取りで返された主キー値を使用して Albums テーブルを手動で読み取ります。
  • デフォルトでは、インデックスから読み取った値はインデックス キーの昇順で返されます。値をインデックス キーの降順で返すには、次のようにします。
    1. インデックス キーに DESC というアノテーションを付けます。
      CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle DESC)
      • DESC アノテーションは、単一のインデックス キーに適用されます。リストに複数のキーがあり、値をキーの降順で返したい場合には、各キーに DESC というアノテーションを付けます。
    2. このインデックスに KeyRange で読み取りを実行する場合、範囲も降順にする必要があります(開始キーの値を終了キーの値よりも大きくします)。

STORING 句

前述のように、STORING 句を使用すると、列のコピーをインデックスに保存できます。これにより、インデックスを使用してクエリと読み取り呼び出しを実行すると、次のメリットがあります(追加ストレージの費用がかかります)。

  • インデックスを使用し、STORING 句に保存された列を選択する SQL クエリで、ベーステーブルへの余分な結合が不要になる。
  • インデックスを使用する読み取り呼び出しで、STORING 句に保存された列を読み取ることができる。

たとえば、MarketingBudget 列のコピーをインデックスを保存する AlbumsByAlbumTitle の代替バージョンを作成したとします(STORING 句は太字になっています)。

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

これにより、インデックス ディレクティブ クエリと AlbumsByAlbumTitle2 を使用して MarketingBudget 列を選択すると、SQL プロセッサは列をインデックスから直接読み取ります。ベーステーブルでインデックスを結合し、テーブルキーでベーステーブルから列を取得することはありません。

これで、ベーステーブルから手動で検索せずに、読み取り呼び出しで直接 MarketingBudget 列を読み取ることができます。

C#

storing インデックスを使用してデータを読み取るため、インデックスを明示的に指定するクエリを実行します。

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

NULL のインデックス作成

デフォルトでは、Cloud Spanner は NULL 値のインデックスを作成します。たとえば、テーブル Singers のインデックス SingersByFirstLastName の定義を思い出してください。

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName)

FirstName または LastName のいずれか、あるいは両方が NULL でも、Singers のすべての行にインデックスが作成されます。

NULL 値を持つ列は、インデックス作成されています

インデックスに NULL が含まれている場合、NULL を使用する効率的な SQL クエリと読み取りを実行できます。たとえば、次の SQL クエリ ステートメントを使用すると、NULL FirstName を持つすべての Singers を取得できます。

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

NULL の並べ替え順

Cloud Spanner は、指定された型で NULL を最小値として並べ替えを行います。ASC 順の列の場合、NULL 値は先頭になります。DESC 順の場合、NULL は最後になります。

NULL のインデックス作成を無効にする

Null のインデックス作成を無効にするには、インデックスの定義に NULL_FILTERED キーワードを追加します。NULL_FILTERED インデックスは、大半の行が NULL 値のスパース列のインデックスを作成する場合に特に便利です。この場合、NULL_FILTERED インデックスは NULL を含む通常のインデックスよりもサイズが小さく、維持コストが安くなります。

NULL のインデックスを作成しない SingersByFirstLastName の代替定義は次のとおりです。

CREATE NULL_FILTERED INDEX SingersByFirstLastNameNoNulls ON Singers(FirstName, LastName)

NULL_FILTERED キーワードは、すべてのインデックス キー列の値に適用されます。列単位で NULL フィルタリングを指定することはできません。

インデックスを NULL_FILTERED にすると、特定のクエリで使用できなくなる場合があります。たとえば、必要な NULL フィルタリング式がないため、次のクエリは無効になります。

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

LastNameNULL のインデックスに Singers 行がないため、このクエリは SingersByFirstLastNameNoNulls インデックスを使用できません。

インデックスを正しく使用するには、次のようにクエリを書き直す必要があります。

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

一意のインデックス

インデックスは UNIQUE として宣言できます。UNIQUE インデックスを使用すると、インデックスを作成するデータに制約が追加され、指定したインデックス キーでの重複が禁止されます。この制約は、トランザクションの commit 時に Cloud Spanner が適用します。同じキーに複数のインデックス エントリが存在するトランザクションは commit に失敗します。

テーブルの先頭に UNIQUE 以外のデータが存在する場合、UNIQUE インデックスを作成しようとすると失敗します。

UNIQUE NULL_FILTERED インデックスに関する注意事項

インデックスのキーペアの 1 つ以上が NULL の場合、UNIQUE NULL_FILTERED インデックスでインデックス キーの一意性は維持されません。

たとえば、次のテーブルとインデックスを作成したとします。

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);

ExampleTable の次の 2 行では、セカンダリ インデックス キー Key1Key2Col1 に同じ値が設定されています。

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

Key2NULL でインデックスが NULL_FILTERED のため、インデックス ExampleIndex に行がありません。これらはインデックスに挿入されないため、(Key1, Key2, Col1) の一意性に違反していても、インデックスはこれらの値を拒否しません。

インデックスでタプル(Key1Key2Col1)の値の一意性を適用するには、テーブル定義で Key2NOT NULL というアノテーションを付けるか、NULL_FILTERED を使用せずにインデックスを作成する必要があります。

インデックスを追加する

Cloud Spanner では、データベースがトラフィックを送信している間に、クライアントが既存の Cloud Spanner テーブルに新しいセカンダリ インデックスを追加できます。Cloud Spanner での他のスキーマの更新と同様に、既存のデータベースにインデックスを追加するときに、データベースをオフラインにする必要はありません。列またはテーブル全体をロックする必要もありません。

既存のテーブルに新しいインデックスが追加されると、Cloud Spanner はインデックスを自動的にバックフィル(入力)し、インデックスを作成しているデータの最新状態を反映します。このバックフィル プロセスは Cloud Spanner が管理します。Cloud Spanner は、インデックスのバックフィル中に追加のリソースを使用します。

インデックスを削除する

スキーマからセカンダリ インデックスを削除するには、DROP INDEX ステートメントを使用します。

SingersByFirstLastName という名前のインデックスを削除するには、次のようにします。

DROP INDEX SingersByFirstLastName
このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Cloud Spanner のドキュメント