ミューテーションを使用したデータの挿入、更新、削除

このページでは、ミューテーションを使用してデータを挿入、更新、削除する方法を説明します。ミューテーションは gRPC または REST を使用して commit することもできますが、クライアント ライブラリから API にアクセスする方が一般的な方法です。

このページでは、挿入、更新、削除の基本的なタスクについて説明します。詳しい例については、入門チュートリアルをご覧ください。

テーブルに新しい行を挿入する

C#

データを挿入するには、connection.CreateInsertCommand() メソッドを使用します。このメソッドは、新しい SpannerCommand を作成して、行をテーブルに挿入します。SpannerCommand.ExecuteNonQueryAsync() メソッドを使うと、テーブルに新しい行を追加できます。

次のコードは、データの挿入方法を示しています。

public class Singer
{
    public int singerId { get; set; }
    public string firstName { get; set; }
    public string lastName { get; set; }
}

public class Album
{
    public int singerId { get; set; }
    public int albumId { get; set; }
    public string albumTitle { get; set; }
}
public static async Task InsertSampleDataAsync(
    string projectId, string instanceId, string databaseId)
{
    const int firstSingerId = 1;
    const int secondSingerId = 2;
    string connectionString =
    $"Data Source=projects/{projectId}/instances/{instanceId}"
    + $"/databases/{databaseId}";
    List<Singer> singers = new List<Singer> {
        new Singer {singerId = firstSingerId, firstName = "Marc",
            lastName = "Richards"},
        new Singer {singerId = secondSingerId, firstName = "Catalina",
            lastName = "Smith"},
        new Singer {singerId = 3, firstName = "Alice",
            lastName = "Trentor"},
        new Singer {singerId = 4, firstName = "Lea",
            lastName = "Martin"},
        new Singer {singerId = 5, firstName = "David",
            lastName = "Lomond"},
    };
    List<Album> albums = new List<Album> {
        new Album {singerId = firstSingerId, albumId = 1,
            albumTitle = "Total Junk"},
        new Album {singerId = firstSingerId, albumId = 2,
            albumTitle = "Go, Go, Go"},
        new Album {singerId = secondSingerId, albumId = 1,
            albumTitle = "Green"},
        new Album {singerId = secondSingerId, albumId = 2,
            albumTitle = "Forever Hold your Peace"},
        new Album {singerId = secondSingerId, albumId = 3,
            albumTitle = "Terrified"},
    };
    // Create connection to Cloud Spanner.
    using (var connection = new SpannerConnection(connectionString))
    {
        await connection.OpenAsync();

        // Insert rows into the Singers table.
        var cmd = connection.CreateInsertCommand("Singers",
            new SpannerParameterCollection {
                {"SingerId", SpannerDbType.Int64},
                {"FirstName", SpannerDbType.String},
                {"LastName", SpannerDbType.String}
        });
        await Task.WhenAll(singers.Select(singer =>
        {
            cmd.Parameters["SingerId"].Value = singer.singerId;
            cmd.Parameters["FirstName"].Value = singer.firstName;
            cmd.Parameters["LastName"].Value = singer.lastName;
            return cmd.ExecuteNonQueryAsync();
        }));

        // Insert rows into the Albums table.
        cmd = connection.CreateInsertCommand("Albums",
            new SpannerParameterCollection {
                {"SingerId", SpannerDbType.Int64},
                {"AlbumId", SpannerDbType.Int64},
                {"AlbumTitle", SpannerDbType.String}
        });
        await Task.WhenAll(albums.Select(album =>
        {
            cmd.Parameters["SingerId"].Value = album.singerId;
            cmd.Parameters["AlbumId"].Value = album.albumId;
            cmd.Parameters["AlbumTitle"].Value = album.albumTitle;
            return cmd.ExecuteNonQueryAsync();
        }));
        Console.WriteLine("Inserted data.");
    }
}

Go

データの書き込みには Mutation を使用します。Mutation はミューテーション オペレーションのコンテナです。Mutation は挿入、更新、削除など、Cloud Spanner データベース中のさまざまな行やテーブルにアトミックに適用される一連の操作を表します。

Mutation.InsertOrUpdate() を使用して INSERT_OR_UPDATE ミューテーションを作成できます。これによって新しい行が追加されます。行がすでに存在している場合は列値が更新されます。あるいは、Mutation.Insert() メソッドを使用して、新しい行を追加する INSERT ミューテーションを作成します。

Client.Apply() はデータベースに対しミューテーションをアトミックに適用します。

次のコードは、データを書き込む方法を示します。

func write(ctx context.Context, w io.Writer, client *spanner.Client) error {
	singerColumns := []string{"SingerId", "FirstName", "LastName"}
	albumColumns := []string{"SingerId", "AlbumId", "AlbumTitle"}
	m := []*spanner.Mutation{
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{1, "Marc", "Richards"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{2, "Catalina", "Smith"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{3, "Alice", "Trentor"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{4, "Lea", "Martin"}),
		spanner.InsertOrUpdate("Singers", singerColumns, []interface{}{5, "David", "Lomond"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{1, 1, "Total Junk"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{1, 2, "Go, Go, Go"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 1, "Green"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 2, "Forever Hold Your Peace"}),
		spanner.InsertOrUpdate("Albums", albumColumns, []interface{}{2, 3, "Terrified"}),
	}
	_, err := client.Apply(ctx, m)
	return err
}

Java

データの書き込みには Mutation オブジェクトを使用します。Mutation オブジェクトは変異オペレーションのためのコンテナです。Mutation は挿入、更新、削除など、Cloud Spanner データベース内のさまざまな行やテーブルに、Cloud Spanner によってアトミックに適用される一連の操作を表します。

Mutation クラスの newInsertBuilder() メソッドは、テーブルに新しい行を挿入する INSERT ミューテーションを作成します。行がすでに存在する場合、書き込みは失敗します。または、newInsertOrUpdateBuilder メソッドを使用して INSERT_OR_UPDATE ミューテーションを構成できます。これにより、行がすでに存在している場合に列値が更新されます。

DatabaseClient クラスの write() メソッドはミューテーションを書き込みます。1 つのバッチ内のすべてのミューテーションはアトミックに適用されます。

次のコードは、データを書き込む方法を示します。

static final List<Singer> SINGERS =
    Arrays.asList(
        new Singer(1, "Marc", "Richards"),
        new Singer(2, "Catalina", "Smith"),
        new Singer(3, "Alice", "Trentor"),
        new Singer(4, "Lea", "Martin"),
        new Singer(5, "David", "Lomond"));

static final List<Album> ALBUMS =
    Arrays.asList(
        new Album(1, 1, "Total Junk"),
        new Album(1, 2, "Go, Go, Go"),
        new Album(2, 1, "Green"),
        new Album(2, 2, "Forever Hold Your Peace"),
        new Album(2, 3, "Terrified"));
static void writeExampleData(DatabaseClient dbClient) {
  List<Mutation> mutations = new ArrayList<>();
  for (Singer singer : SINGERS) {
    mutations.add(
        Mutation.newInsertBuilder("Singers")
            .set("SingerId")
            .to(singer.singerId)
            .set("FirstName")
            .to(singer.firstName)
            .set("LastName")
            .to(singer.lastName)
            .build());
  }
  for (Album album : ALBUMS) {
    mutations.add(
        Mutation.newInsertBuilder("Albums")
            .set("SingerId")
            .to(album.singerId)
            .set("AlbumId")
            .to(album.albumId)
            .set("AlbumTitle")
            .to(album.albumTitle)
            .build());
  }
  dbClient.write(mutations);
}

Node.js

データの書き込みには Table オブジェクトを使用します。Table.insert() メソッドを使用して、テーブルに新しい行を追加します。同一のバッチ内の挿入はすべてアトミックに適用されます。

次のコードは、データを書き込む方法を示します。

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

// Instantiate Spanner table objects
const singersTable = database.table('Singers');
const albumsTable = database.table('Albums');

// Inserts rows into the Singers table
// Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so
// they must be converted to strings before being inserted as INT64s
try {
  await singersTable.insert([
    {SingerId: '1', FirstName: 'Marc', LastName: 'Richards'},
    {SingerId: '2', FirstName: 'Catalina', LastName: 'Smith'},
    {SingerId: '3', FirstName: 'Alice', LastName: 'Trentor'},
    {SingerId: '4', FirstName: 'Lea', LastName: 'Martin'},
    {SingerId: '5', FirstName: 'David', LastName: 'Lomond'},
  ]);

  await albumsTable.insert([
    {SingerId: '1', AlbumId: '1', AlbumTitle: 'Total Junk'},
    {SingerId: '1', AlbumId: '2', AlbumTitle: 'Go, Go, Go'},
    {SingerId: '2', AlbumId: '1', AlbumTitle: 'Green'},
    {SingerId: '2', AlbumId: '2', AlbumTitle: 'Forever Hold your Peace'},
    {SingerId: '2', AlbumId: '3', AlbumTitle: 'Terrified'},
  ]);

  console.log('Inserted data.');
} catch (err) {
  console.error('ERROR:', err);
} finally {
  await database.close();
}

PHP

Database::insertBatch メソッドを使ってデータを書き込みます。insertBatch はテーブルに新しい行を追加します。1 つのバッチ内の挿入はすべてアトミックに適用されます。

次のコードは、データを書き込む方法を示します。

use Google\Cloud\Spanner\SpannerClient;

/**
 * Inserts sample data into the given database.
 *
 * The database and table must already exist and can be created using
 * `create_database`.
 * Example:
 * ```
 * insert_data($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function insert_data($instanceId, $databaseId)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $operation = $database->transaction(['singleUse' => true])
        ->insertBatch('Singers', [
            ['SingerId' => 1, 'FirstName' => 'Marc', 'LastName' => 'Richards'],
            ['SingerId' => 2, 'FirstName' => 'Catalina', 'LastName' => 'Smith'],
            ['SingerId' => 3, 'FirstName' => 'Alice', 'LastName' => 'Trentor'],
            ['SingerId' => 4, 'FirstName' => 'Lea', 'LastName' => 'Martin'],
            ['SingerId' => 5, 'FirstName' => 'David', 'LastName' => 'Lomond'],
        ])
        ->insertBatch('Albums', [
            ['SingerId' => 1, 'AlbumId' => 1, 'AlbumTitle' => 'Total Junk'],
            ['SingerId' => 1, 'AlbumId' => 2, 'AlbumTitle' => 'Go, Go, Go'],
            ['SingerId' => 2, 'AlbumId' => 1, 'AlbumTitle' => 'Green'],
            ['SingerId' => 2, 'AlbumId' => 2, 'AlbumTitle' => 'Forever Hold Your Peace'],
            ['SingerId' => 2, 'AlbumId' => 3, 'AlbumTitle' => 'Terrified']
        ])
        ->commit();

    print('Inserted data.' . PHP_EOL);
}

Python

データの書き込みは Batch オブジェクトを使用して行います。Batch オブジェクトは、ミューテーション オペレーションのコンテナです。ミューテーションは挿入、更新、削除など、Cloud Spanner データベース中のさまざまな行やテーブルにアトミックに適用される一連の操作を表します。

Batch クラスの insert() メソッドは、バッチに 1 つ以上の挿入ミューテーションを追加するために使用されます。1 つのバッチ内のすべてのミューテーションはアトミックに適用されます。

次のコードは、データを書き込む方法を示します。

def insert_data(instance_id, database_id):
    """Inserts sample data into the given database.

    The database and table must already exist and can be created using
    `create_database`.
    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.batch() as batch:
        batch.insert(
            table='Singers',
            columns=('SingerId', 'FirstName', 'LastName',),
            values=[
                (1, u'Marc', u'Richards'),
                (2, u'Catalina', u'Smith'),
                (3, u'Alice', u'Trentor'),
                (4, u'Lea', u'Martin'),
                (5, u'David', u'Lomond')])

        batch.insert(
            table='Albums',
            columns=('SingerId', 'AlbumId', 'AlbumTitle',),
            values=[
                (1, 1, u'Total Junk'),
                (1, 2, u'Go, Go, Go'),
                (2, 1, u'Green'),
                (2, 2, u'Forever Hold Your Peace'),
                (2, 3, u'Terrified')])

    print('Inserted data.')

Ruby

データの書き込みには、Client オブジェクトを使用します。Client#commit メソッドは、データベース内の列、行、テーブルにまたがる時点の単一の論理ポイントでアトミックに実行される書き込みトランザクションを作成し、commit します。

次のコードは、データを書き込む方法を示します。

# 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

client.commit do |c|
  c.insert "Singers", [
    { SingerId: 1, FirstName: "Marc",     LastName: "Richards" },
    { SingerId: 2, FirstName: "Catalina", LastName: "Smith"    },
    { SingerId: 3, FirstName: "Alice",    LastName: "Trentor"  },
    { SingerId: 4, FirstName: "Lea",      LastName: "Martin"   },
    { SingerId: 5, FirstName: "David",    LastName: "Lomond"   }
  ]
  c.insert "Albums", [
    { SingerId: 1, AlbumId: 1, AlbumTitle: "Total Junk"              },
    { SingerId: 1, AlbumId: 2, AlbumTitle: "Go, Go, Go"              },
    { SingerId: 2, AlbumId: 1, AlbumTitle: "Green"                   },
    { SingerId: 2, AlbumId: 2, AlbumTitle: "Forever Hold Your Peace" },
    { SingerId: 2, AlbumId: 3, AlbumTitle: "Terrified"               }
  ]
end

puts "Inserted data"

テーブル内の行を更新する

現在の予算の条件を満たしながら、あるアルバムのマーケティング予算を別のアルバムに移す場合について考えてみましょう。新しく書き込む値を判別するために、テーブル内のデータを読み取る必要があります。このため、読み取り / 書き込みトランザクションを使用して、読み取りと書き込みをアトミックに実行します。

C#

.NET Standard 2.0(または .NET 4.5)以上の場合は、.NET フレームワークの TransactionScope() を使用して、トランザクションを実行できます。サポートされているすべての .NET バージョンについて、トランザクションを作成するには、SpannerConnection.BeginTransactionAsync の結果を SpannerCommandTransaction プロパティとして設定します。

トランザクションは次の 2 つの方法で実行できます。

.NET Standard 2.0

public static async Task ReadWriteWithTransactionAsync(
    string projectId,
    string instanceId,
    string databaseId)
{
    // This sample transfers 200,000 from the MarketingBudget
    // field of the second Album to the first Album. Make sure to run
    // the addColumn and writeDataToNewColumn samples first,
    // in that order.

    string connectionString =
    $"Data Source=projects/{projectId}/instances/{instanceId}"
    + $"/databases/{databaseId}";

    using (TransactionScope scope = new TransactionScope(
        TransactionScopeAsyncFlowOption.Enabled))
    {
        decimal transferAmount = 200000;
        decimal secondBudget = 0;
        decimal firstBudget = 0;

        // Create connection to Cloud Spanner.
        using (var connection =
            new SpannerConnection(connectionString))
        {
            // Create statement to select the second album's data.
            var cmdLookup = connection.CreateSelectCommand(
            "SELECT * FROM Albums WHERE SingerId = 2 AND AlbumId = 2");
            // Excecute the select query.
            using (var reader = await cmdLookup.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    // Read the second album's budget.
                    secondBudget =
                      reader.GetFieldValue<decimal>("MarketingBudget");
                    // Confirm second Album's budget is sufficient and
                    // if not raise an exception. Raising an exception
                    // will automatically roll back the transaction.
                    if (secondBudget < transferAmount)
                    {
                        throw new Exception("The second album's "
                            + $"budget {secondBudget} "
                            + "is less than the "
                            + "amount to transfer.");
                    }
                }
            }
            // Read the first album's budget.
            cmdLookup = connection.CreateSelectCommand(
            "SELECT * FROM Albums WHERE SingerId = 1 and AlbumId = 1");
            using (var reader = await cmdLookup.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    firstBudget =
                      reader.GetFieldValue<decimal>("MarketingBudget");
                }
            }

            // Specify update command parameters.
            var cmd = connection.CreateUpdateCommand("Albums",
                new SpannerParameterCollection {
                {"SingerId", SpannerDbType.Int64},
                {"AlbumId", SpannerDbType.Int64},
                {"MarketingBudget", SpannerDbType.Int64},
            });
            // Update second album to remove the transfer amount.
            secondBudget -= transferAmount;
            cmd.Parameters["SingerId"].Value = 2;
            cmd.Parameters["AlbumId"].Value = 2;
            cmd.Parameters["MarketingBudget"].Value = secondBudget;
            await cmd.ExecuteNonQueryAsync();
            // Update first album to add the transfer amount.
            firstBudget += transferAmount;
            cmd.Parameters["SingerId"].Value = 1;
            cmd.Parameters["AlbumId"].Value = 1;
            cmd.Parameters["MarketingBudget"].Value = firstBudget;
            await cmd.ExecuteNonQueryAsync();
            scope.Complete();
            Console.WriteLine("Transaction complete.");
        }
    }
}

.NET Standard 1.5

public static async Task ReadWriteWithTransactionCoreAsync(
    string projectId,
    string instanceId,
    string databaseId)
{
    // This sample transfers 200,000 from the MarketingBudget
    // field of the second Album to the first Album. Make sure to run
    // the addColumn and writeDataToNewColumn samples first,
    // in that order.
    string connectionString =
        $"Data Source=projects/{projectId}/instances/{instanceId}"
        + $"/databases/{databaseId}";

    decimal transferAmount = 200000;
    decimal secondBudget = 0;
    decimal firstBudget = 0;

    Console.WriteLine(".NetCore API sample.");

    // Create connection to Cloud Spanner.
    using (var connection =
        new SpannerConnection(connectionString))
    {
        await connection.OpenAsync();

        // Create a readwrite transaction that we'll assign
        // to each SpannerCommand.
        using (var transaction =
                await connection.BeginTransactionAsync())
        {
            // Create statement to select the second album's data.
            var cmdLookup = connection.CreateSelectCommand(
             "SELECT * FROM Albums WHERE SingerId = 2 AND AlbumId = 2");
            cmdLookup.Transaction = transaction;
            // Excecute the select query.
            using (var reader = await cmdLookup.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    // Read the second album's budget.
                    secondBudget =
                       reader.GetFieldValue<decimal>("MarketingBudget");
                    // Confirm second Album's budget is sufficient and
                    // if not raise an exception. Raising an exception
                    // will automatically roll back the transaction.
                    if (secondBudget < transferAmount)
                    {
                        throw new Exception("The second album's "
                                + $"budget {secondBudget} "
                                + "contains less than the "
                                + "amount to transfer.");
                    }
                }
            }
            // Read the first album's budget.
            cmdLookup = connection.CreateSelectCommand(
             "SELECT * FROM Albums WHERE SingerId = 1 and AlbumId = 1");
            cmdLookup.Transaction = transaction;
            using (var reader = await cmdLookup.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    firstBudget =
                      reader.GetFieldValue<decimal>("MarketingBudget");
                }
            }

            // Specify update command parameters.
            var cmd = connection.CreateUpdateCommand("Albums",
                new SpannerParameterCollection
                {
                    {"SingerId", SpannerDbType.Int64},
                    {"AlbumId", SpannerDbType.Int64},
                    {"MarketingBudget", SpannerDbType.Int64},
                });
            cmd.Transaction = transaction;
            // Update second album to remove the transfer amount.
            secondBudget -= transferAmount;
            cmd.Parameters["SingerId"].Value = 2;
            cmd.Parameters["AlbumId"].Value = 2;
            cmd.Parameters["MarketingBudget"].Value = secondBudget;
            await cmd.ExecuteNonQueryAsync();
            // Update first album to add the transfer amount.
            firstBudget += transferAmount;
            cmd.Parameters["SingerId"].Value = 1;
            cmd.Parameters["AlbumId"].Value = 1;
            cmd.Parameters["MarketingBudget"].Value = firstBudget;
            await cmd.ExecuteNonQueryAsync();

            await transaction.CommitAsync();
        }
        Console.WriteLine("Transaction complete.");
    }
}

Go

ReadWriteTransaction タイプを使用して、読み取りや書き込みトランザクションのコンテキストで一連の作業を実行します。 Client.ReadWriteTransaction()ReadWriteTransaction オブジェクトを返します。

サンプルでは、データの行を取得するために ReadWriteTransaction.ReadRow() が使用されています。

また、サンプルでは ReadWriteTransaction.BufferWrite() も使用されています。これにより、トランザクションが commit されるときに適用される更新のセットにミューテーションのリストが追加されます。

サンプルでは、Cloud Spanner のテーブルまたはインデックスの行キーを表す Key タイプも使用されます。

func writeWithTransaction(ctx context.Context, w io.Writer, client *spanner.Client) error {
	_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		getBudget := func(key spanner.Key) (int64, error) {
			row, err := txn.ReadRow(ctx, "Albums", key, []string{"MarketingBudget"})
			if err != nil {
				return 0, err
			}
			var budget int64
			if err := row.Column(0, &budget); err != nil {
				return 0, err
			}
			return budget, nil
		}
		album2Budget, err := getBudget(spanner.Key{2, 2})
		if err != nil {
			return err
		}
		const transferAmt = 200000
		if album2Budget >= transferAmt {
			album1Budget, err := getBudget(spanner.Key{1, 1})
			if err != nil {
				return err
			}
			album1Budget += transferAmt
			album2Budget -= transferAmt
			cols := []string{"SingerId", "AlbumId", "MarketingBudget"}
			txn.BufferWrite([]*spanner.Mutation{
				spanner.Update("Albums", cols, []interface{}{1, 1, album1Budget}),
				spanner.Update("Albums", cols, []interface{}{2, 2, album2Budget}),
			})
			fmt.Fprintf(w, "Moved %d from Album2's MarketingBudget to Album1's.", transferAmt)
		}
		return nil
	})
	return err
}

Java

TransactionRunner インターフェースを使用して、読み取りや書き込みトランザクションのコンテキストで一連の作業を実行します。このインターフェースにはメソッド run() が含まれます。これは読み取りや書き込みトランザクションを実行し、必要に応じて再試行するために使用されます。DatabaseClient クラスの readWriteTransaction メソッドは、1 回の論理トランザクションを実行するための TransactionRunner オブジェクトを返します。

TransactionRunner.TransactionCallable クラスには、トランザクションを 1 回試行する run() メソッドが含まれます。run() は、トランザクションのコンテキストである TransactionContext オブジェクトを受け取ります。

サンプルでは、readRow() 呼び出しの結果を格納するのに便利な Struct クラスが使用されています。またサンプルでは、Cloud Spanner のテーブルまたはインデックスの行キーを表す Key クラスも使用されてます。

トランザクションを実行するコードを次に示します。

static void writeWithTransaction(DatabaseClient dbClient) {
  dbClient
      .readWriteTransaction()
      .run(
          new TransactionCallable<Void>() {
            @Override
            public Void run(TransactionContext transaction) throws Exception {
              // Transfer marketing budget from one album to another. We do it in a transaction to
              // ensure that the transfer is atomic.
              Struct row =
                  transaction.readRow("Albums", Key.of(2, 2), Arrays.asList("MarketingBudget"));
              long album2Budget = row.getLong(0);
              // Transaction will only be committed if this condition still holds at the time of
              // commit. Otherwise it will be aborted and the callable will be rerun by the
              // client library.
              long transfer = 200000;
              if (album2Budget >= transfer) {
                long album1Budget =
                    transaction
                        .readRow("Albums", Key.of(1, 1), Arrays.asList("MarketingBudget"))
                        .getLong(0);
                album1Budget += transfer;
                album2Budget -= transfer;
                transaction.buffer(
                    Mutation.newUpdateBuilder("Albums")
                        .set("SingerId")
                        .to(1)
                        .set("AlbumId")
                        .to(1)
                        .set("MarketingBudget")
                        .to(album1Budget)
                        .build());
                transaction.buffer(
                    Mutation.newUpdateBuilder("Albums")
                        .set("SingerId")
                        .to(2)
                        .set("AlbumId")
                        .to(2)
                        .set("MarketingBudget")
                        .to(album2Budget)
                        .build());
              }
              return null;
            }
          });
}

Node.js

Database.runTransaction() を使用して、トランザクションを実行します。

トランザクションを実行するコードを次に示します。

// This sample transfers 200,000 from the MarketingBudget field
// of the second Album to the first Album, as long as the second
// Album has enough money in its budget. Make sure to run the
// addColumn and updateData samples first (in that order).

// 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 transferAmount = 200000;

database.runTransaction(async (err, transaction) => {
  if (err) {
    console.error(err);
    return;
  }
  let firstBudget, secondBudget;
  const queryOne = {
    columns: [`MarketingBudget`],
    keys: [[2, 2]], // SingerId: 2, AlbumId: 2
  };

  const queryTwo = {
    columns: ['MarketingBudget'],
    keys: [[1, 1]], // SingerId: 1, AlbumId: 1
  };

  Promise.all([
    // Reads the second album's budget
    transaction.read('Albums', queryOne).then(results => {
      // Gets second album's budget
      const rows = results[0].map(row => row.toJSON());
      secondBudget = rows[0].MarketingBudget;
      console.log(`The second album's marketing budget: ${secondBudget}`);

      // Makes sure the second album's budget is large enough
      if (secondBudget < transferAmount) {
        throw new Error(
          `The second album's budget (${secondBudget}) is less than the transfer amount (${transferAmount}).`
        );
      }
    }),

    // Reads the first album's budget
    transaction.read('Albums', queryTwo).then(results => {
      // Gets first album's budget
      const rows = results[0].map(row => row.toJSON());
      firstBudget = rows[0].MarketingBudget;
      console.log(`The first album's marketing budget: ${firstBudget}`);
    }),
  ])
    .then(() => {
      console.log(firstBudget, secondBudget);
      // Transfers the budgets between the albums
      firstBudget += transferAmount;
      secondBudget -= transferAmount;

      console.log(firstBudget, secondBudget);

      // Updates the database
      // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they
      // must be converted (back) to strings before being inserted as INT64s.
      transaction.update('Albums', [
        {
          SingerId: '1',
          AlbumId: '1',
          MarketingBudget: firstBudget.toString(),
        },
        {
          SingerId: '2',
          AlbumId: '2',
          MarketingBudget: secondBudget.toString(),
        },
      ]);
    })
    .then(() => {
      // Commits the transaction and send the changes to the database
      return transaction.commit();
    })
    .then(() => {
      console.log(
        `Successfully executed read-write transaction to transfer ${transferAmount} from Album 2 to Album 1.`
      );
    })
    .catch(err => {
      console.error('ERROR:', err);
    })
    .then(() => {
      // Closes the database when finished
      return database.close();
    });
});

PHP

Database::runTransaction を使用して、トランザクションを実行します。

トランザクションを実行するコードを次に示します。

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Transaction;
use UnexpectedValueException;

/**
 * Performs a read-write transaction to update two sample records in the
 * database.
 *
 * This will transfer 200,000 from the `MarketingBudget` field for the second
 * Album to the first Album. If the `MarketingBudget` for the second Album is
 * too low, it will raise an exception.
 *
 * Before running this sample, you will need to run the `update_data` sample
 * to populate the fields.
 * Example:
 * ```
 * read_write_transaction($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function read_write_transaction($instanceId, $databaseId)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $database->runTransaction(function (Transaction $t) use ($spanner) {
        $transferAmount = 200000;

        // Read the second album's budget.
        $secondAlbumKey = [2,2];
        $secondAlbumKeySet = $spanner->keySet(['keys' => [$secondAlbumKey]]);
        $secondAlbumResult = $t->read(
            'Albums',
            $secondAlbumKeySet,
            ['MarketingBudget'],
            ['limit' => 1]
        );

        $firstRow = $secondAlbumResult->rows()->current();
        $secondAlbumBudget = $firstRow['MarketingBudget'];
        if ($secondAlbumBudget < $transferAmount) {
            // Throwing an exception will automatically roll back the transaction.
            throw new UnexpectedValueException(
                'The second album\'s budget is lower than the transfer amount: ' . $transferAmount
            );
        }

        $firstAlbumKey = [1,1];
        $firstAlbumKeySet = $spanner->keySet(['keys' => [$firstAlbumKey]]);
        $firstAlbumResult = $t->read(
            'Albums',
            $firstAlbumKeySet,
            ['MarketingBudget'],
            ['limit' => 1]
        );

        // Read the first album's budget.
        $firstRow = $firstAlbumResult->rows()->current();
        $firstAlbumBudget = $firstRow['MarketingBudget'];

        // Update the budgets.
        $secondAlbumBudget -= $transferAmount;
        $firstAlbumBudget += $transferAmount;
        printf('Setting first album\'s budget to %s and the second album\'s ' .
            'budget to %s.' . PHP_EOL, $firstAlbumBudget, $secondAlbumBudget);

        // Update the rows.
        $t->updateBatch('Albums', [
            ['SingerId' => 1, 'AlbumId' => 1, 'MarketingBudget' => $firstAlbumBudget],
            ['SingerId' => 2, 'AlbumId' => 2, 'MarketingBudget' => $secondAlbumBudget],
        ]);

        // Commit the transaction!
        $t->commit();

        print('Transaction complete.' . PHP_EOL);
    });
}

Python

Database クラスの run_in_transaction() メソッドを使用して、トランザクションを実行します。

トランザクションを実行するコードを次に示します。

def read_write_transaction(instance_id, database_id):
    """Performs a read-write transaction to update two sample records in the
    database.

    This will transfer 200,000 from the `MarketingBudget` field for the second
    Album to the first Album. If the `MarketingBudget` is too low, it will
    raise an exception.

    Before running this sample, you will need to run the `update_data` sample
    to populate the fields.
    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    def update_albums(transaction):
        # Read the second album budget.
        second_album_keyset = spanner.KeySet(keys=[(2, 2)])
        second_album_result = transaction.read(
            table='Albums', columns=('MarketingBudget',),
            keyset=second_album_keyset, limit=1)
        second_album_row = list(second_album_result)[0]
        second_album_budget = second_album_row[0]

        transfer_amount = 200000

        if second_album_budget < transfer_amount:
            # Raising an exception will automatically roll back the
            # transaction.
            raise ValueError(
                'The second album doesn\'t have enough funds to transfer')

        # Read the first album's budget.
        first_album_keyset = spanner.KeySet(keys=[(1, 1)])
        first_album_result = transaction.read(
            table='Albums', columns=('MarketingBudget',),
            keyset=first_album_keyset, limit=1)
        first_album_row = list(first_album_result)[0]
        first_album_budget = first_album_row[0]

        # Update the budgets.
        second_album_budget -= transfer_amount
        first_album_budget += transfer_amount
        print(
            'Setting first album\'s budget to {} and the second album\'s '
            'budget to {}.'.format(
                first_album_budget, second_album_budget))

        # Update the rows.
        transaction.update(
            table='Albums',
            columns=(
                'SingerId', 'AlbumId', 'MarketingBudget'),
            values=[
                (1, 1, first_album_budget),
                (2, 2, second_album_budget)])

    database.run_in_transaction(update_albums)

    print('Transaction complete.')

Ruby

Client クラスの transaction メソッドを使用して、トランザクションを実行します。

トランザクションを実行するコードを次に示します。

# 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
transfer_amount = 200_000

client.transaction do |transaction|
  first_album  = transaction.read("Albums", [:MarketingBudget], keys: [[1, 1]]).rows.first
  second_album = transaction.read("Albums", [:MarketingBudget], keys: [[2, 2]]).rows.first

  raise "The second album does not have enough funds to transfer" if second_album[:MarketingBudget] < transfer_amount

  new_first_album_budget  = first_album[:MarketingBudget] + transfer_amount
  new_second_album_budget = second_album[:MarketingBudget] - transfer_amount

  transaction.update "Albums", [
    { SingerId: 1, AlbumId: 1, MarketingBudget: new_first_album_budget  },
    { SingerId: 2, AlbumId: 2, MarketingBudget: new_second_album_budget }
  ]
end

puts "Transaction complete"

テーブル内の行を削除する

各クライアント ライブラリには、行を削除するための方法として、以下の複数の方法があります。

  • テーブル内のすべての行を削除する。
  • 行に対するキー列の値を指定して、単一行を削除する。
  • キー範囲を作成して、行のグループを削除する。
  • インターリーブされたテーブル内の行を削除するには、そのテーブルがスキーマ定義に ON DELETE CASCADE を含む場合、親行を削除する。

C#

行を削除するには connection.CreateDeleteCommand() メソッドを使用します。このメソッドは行を削除する新しい SpannerCommand を作成します。SpannerCommand.ExecuteNonQueryAsync() メソッドでテーブルから行を削除します。

このサンプルでは、Singers テーブルの行を個別に削除します。Albums テーブルが Singers テーブル内でインターリーブされ、ON DELETE CASCADE が定義されているため、Albums テーブル内の行が削除されます。

public static async Task DeleteSampleDataAsync(
    string projectId, string instanceId, string databaseId)
{
    const int firstSingerId = 1;
    const int secondSingerId = 2;
    string connectionString =
        $"Data Source=projects/{projectId}/instances/{instanceId}"
        + $"/databases/{databaseId}";
    List<Singer> singers = new List<Singer> {
        new Singer {singerId = firstSingerId, firstName = "Marc",
            lastName = "Richards"},
        new Singer {singerId = secondSingerId, firstName = "Catalina",
            lastName = "Smith"},
        new Singer {singerId = 3, firstName = "Alice",
            lastName = "Trentor"},
        new Singer {singerId = 4, firstName = "Lea",
            lastName = "Martin"},
        new Singer {singerId = 5, firstName = "David",
            lastName = "Lomond"},
    };
    // Create connection to Cloud Spanner.
    using (var connection = new SpannerConnection(connectionString))
    {
        await connection.OpenAsync();

        // Delete rows from the Singers table.
        var cmd = connection.CreateDeleteCommand("Singers",
            new SpannerParameterCollection {
                {"SingerId", SpannerDbType.Int64}
            });
        await Task.WhenAll(singers.Select(singer =>
        {
            cmd.Parameters["SingerId"].Value = singer.singerId;
            return cmd.ExecuteNonQueryAsync();
        }));

        Console.WriteLine("Deleted data.");
    }
}

Go

行を削除するには Mutation を使用します。Mutation.Delete() メソッドを使用して、行を削除する DELETE ミューテーションを作成します。Client.Apply() メソッドは、ミューテーションをデータベースにアトミックに適用します。

このサンプルでは、Albums テーブル内の行を個別に削除してから、KeyRange を使用して Singers テーブル内のすべての行を削除します。

func delete(ctx context.Context, w io.Writer, client *spanner.Client) error {
	// Delete each of the albums by individual key,
	// then delete all the singers using a key range.
	m := []*spanner.Mutation{
		spanner.Delete("Albums", spanner.Key{1, 1}),
		spanner.Delete("Albums", spanner.Key{1, 2}),
		spanner.Delete("Albums", spanner.Key{2, 1}),
		spanner.Delete("Albums", spanner.Key{2, 2}),
		spanner.Delete("Albums", spanner.Key{2, 3}),
		spanner.Delete("Singers", spanner.KeyRange{Start: spanner.Key{1}, End: spanner.Key{5}, Kind: spanner.ClosedClosed}),
	}
	_, err := client.Apply(ctx, m)
	return err
}

Java

行を削除するには Mutation.delete() メソッドを使用します。

このサンプルでは、KeySet.all() メソッドを使用して Albums テーブルのすべての行を削除します。Albums テーブルの行を削除した後、このサンプルでは、KeySet.singleKey() メソッドで作成されたキーを使用して Singers テーブルの行を個別に削除します。

static void deleteExampleData(DatabaseClient dbClient) {
  List<Mutation> mutations = new ArrayList<>();

  // KeySet.all() can be used to delete all the rows in a table.
  mutations.add(Mutation.delete("Albums", KeySet.all()));

  // KeySet.singleKey() can be used to delete one row at a time.
  for (Singer singer : SINGERS) {
    mutations.add(
        Mutation.delete(
            "Singers", KeySet.singleKey(Key.newBuilder().append(singer.singerId).build())));
  }

  dbClient.write(mutations);
  System.out.printf("Records deleted.\n");
}

Node.js

行を削除するには table.deleteRows() メソッドを使用します。

このサンプルでは、table.deleteRows() メソッドを使用して Singers テーブルからすべての行を削除します。Albums テーブルが Singers テーブル内にインターリーブされ、ON DELETE CASCADE が定義されているため、Albums テーブル内の行が削除されます。

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

// Instantiate Spanner table object
const singersTable = database.table('Singers');

// Deletes rows from the Singers table and the Albums table,
// because Albums table is defined with ON DELETE CASCADE.
try {
  const keys = [1, 2, 3, 4, 5];
  await singersTable.deleteRows(keys);
  console.log('Deleted data.');
} catch (err) {
  console.error('ERROR:', err);
} finally {
  await database.close();
}

PHP

行を削除するには Database::delete() method を使用します。Database::delete() メソッドのページにサンプルがあります。

Python

行を削除するには Batch.delete() メソッドを使用します。

このサンプルでは、KeySet オブジェクトを使用して、Albums テーブルと Singers テーブルのすべての行を個別に削除します。

def delete_data(instance_id, database_id):
    """Deletes sample data from the given database.

    The database, table, and data must already exist and can be created using
    `create_database` and `insert_data`.
    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    singers_to_delete = spanner.KeySet(
        keys=[[1], [2], [3], [4], [5]])
    albums_to_delete = spanner.KeySet(
        keys=[[1, 1], [1, 2], [2, 1], [2, 2], [2, 3]])

    with database.batch() as batch:
        batch.delete('Albums', albums_to_delete)
        batch.delete('Singers', singers_to_delete)

    print('Deleted data.')

Ruby

行を削除するには Client#delete メソッドを使用します。Client#delete のページにサンプルがあります。

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

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

Cloud Spanner のドキュメント