עבודה עם נתוני JSONB

בדף הזה מוסבר איך לעבוד עם סוג הנתונים JSONB ב-Spanner.

‫JSONB הוא סוג נתונים של PostgreSQL שמשמש לאחסון נתונים חצי מובְנים בניב של Spanner PostgreSQL. ‫JSONB מכיל נתונים בפורמט JavaScript Object Notation ‏ (JSON), בהתאם למפרט שמתואר ב-RFC 7159.

מפרטים

סוג הנתונים JSONB של Spanner מאחסן ייצוג מנורמל של מסמך הקלט. המשמעות היא:

  • מירכאות ורווחים לא נשמרים.
  • אין תמיכה בתגובות. עסקאות או שאילתות עם הערות נכשלות.
  • מפתחות האובייקטים ממוינים קודם לפי אורך המפתח, ואז לפי יחס סדר מילוני לפי אורך מפתח האובייקט המקביל. אם יש מפתחות אובייקטים כפולים, רק האחרון נשמר.
  • סוגים פרימיטיביים (string,‏ boolean,‏ number ו-null) שומרים על הסוג והערך שלהם.
    • ערכים מסוג string נשמרים בדיוק כמו שהם.
    • אפסים בסוף המספר נשמרים. פורמט הפלט של ערכים מסוג number לא משתמש בכתיב מדעי.
  • ערכי JSONB null נחשבים לערכים שאינם NULL ב-SQL. לדוגמה:

    SELECT null::jsonb IS NULL;  -- Returns true
    SELECT 'null'::jsonb IS NULL; -- Returns false
    
    SELECT '{"a":null}'::jsonb -> 'a' IS NULL; -- Returns false
    SELECT '{"a":null}'::jsonb -> 'b' IS NULL; -- Returns true
    
    SELECT '{"a":null}'::jsonb -> 'a'; -- Returns a JSONB 'null'
    SELECT '{"a":null}'::jsonb -> 'b'; -- Returns a SQL NULL
    
  • סדר הרכיבים במערך JSONB נשמר.

הגבלות

ההגבלות הבאות חלות על Spanner JSONB:

  • הארגומנטים של הפונקציה to_jsonb יכולים להיות רק מסוגי הנתונים של PostgreSQL שנתמכים ב-Spanner.
  • ערכים מסוג מספר יכולים להכיל 4,932 ספרות לפני הנקודה העשרונית ו-16,383 ספרות אחרי הנקודה העשרונית.
  • הגודל המקסימלי המותר של פורמט האחסון המנורמל הוא 10MB.
  • מסמכי JSONB צריכים להיות מקודדים ב-UTF-8. טרנזקציות או שאילתות עם מסמכי JSONB שמקודדים בפורמטים אחרים מחזירות שגיאה.

השוואה ומיון

אפשר להשוות בין ערכי JSONB כדי לבדוק אם הם שווים או כדי לסדר אותם. אפשר גם להשתמש בערכי JSONB בסעיפים GROUP BY ו-ORDER BY.

כשמשווים בין ערכי JSONB מסוגים שונים, חלים היחסים הבאים:

Null (JSONB null) < String < Number < Boolean < Array < Object

כשמשווים בין ערכי JSONB מאותו סוג, חלים הכללים הבאים:

  • אובייקטים: אובייקטים עם יותר צמדי מפתח/ערך גדולים יותר מאובייקטים עם פחות צמדים. אם לשני האובייקטים יש את אותו מספר של זוגות, הזוגות של מפתח/ערך מושווים בזוגות (קודם המפתחות ואז הערכים) לפי סדר המפתחות שמוגדר במפרט. הזוג הראשון ששונה קובע את הסדר.
  • מערכים: מערכים עם יותר רכיבים גדולים יותר ממערכים עם פחות רכיבים. אם לשני המערכים יש את אותו מספר רכיבים, הרכיבים מושווים לפי אינדקס משמאל לימין. האיבר הראשון ששונה קובע את סדר המערך.
  • ערכים בוליאניים: true גדול מ-false.
  • מספרים: השוואה מתמטית באמצעות השוואת ערכים מספריים.
  • מחרוזות: השוואה לקסיקוגרפית באמצעות כללים סטנדרטיים להשוואת מחרוזות.
  • ערכי Null ב-JSONB: כל הערכים של JSONB null שווים זה לזה וקטנים מכל ערך JSON אחר שאינו null. עם זאת, יש מקרה קצה:

    • מערך ריק ברמה העליונה [] נחשב קטן יותר מ-JSONB null. מכיוון שההגדרה הזו חלה רק על מערכים ברמה העליונה, ההצהרות הבאות מחזירות את הערך true:

      • '[]'::jsonb < 'null'::jsonb
      • 'null'::jsonb < '[[]]'::jsonb
      • '[null]'::jsonb < '[[]]'::jsonb

יצירת טבלה עם עמודות JSONB

אפשר להוסיף עמודת JSONB לטבלה כשיוצרים אותה.

CREATE TABLE Venues (
 VenueId   BIGINT PRIMARY KEY,
 VenueName  VARCHAR(1024),
 VenueAddress VARCHAR(1024),
 VenueFeatures JSONB,
 DateOpened  TIMESTAMPTZ
);

דוגמה לאובייקט VenueFeatures JSONB:

{
    "rating": 4.5,
    "capacity":"1500",
    "construction":"brick",
    "tags": [
        "multi-cuisine",
        "open-seating",
        "stage",
        "public address system"
    ]
}

הוספה והסרה של עמודות JSONB מטבלאות קיימות

אפשר להוסיף עמודה מסוג JSONB ולהסיר אותה באמצעות הצהרות ALTER, באופן הבא:

ALTER TABLE Venues ADD COLUMN VenueDetails JSONB;
ALTER TABLE Venues DROP COLUMN VenueDetails;

בדוגמה הבאה מוסבר איך להוסיף עמודת JSONB בשם VenueDetails לטבלה Venues באמצעות ספריות הלקוח של Spanner.

C++‎

void JsonbAddColumn(google::cloud::spanner_admin::DatabaseAdminClient client,
                    google::cloud::spanner::Database const& database) {
  std::vector<std::string> statements = {
      R"""(
        ALTER TABLE Venues
            ADD COLUMN VenueDetails JSONB
      )""",
  };
  auto metadata =
      client.UpdateDatabaseDdl(database.FullName(), statements).get();
  if (!metadata) throw std::move(metadata).status();
  std::cout << "Added JSONB column to table Venues in database "
            << database.FullName() << "\nNew DDL:\n"
            << metadata->DebugString();
}

C#‎


using Google.Cloud.Spanner.Data;
using System;
using System.Threading.Tasks;

public class AddJsonbColumnAsyncPostgresSample
{
    public async Task AddJsonbColumnAsyncPostgres(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        string alterStatement = $"ALTER TABLE VenueDetails ADD COLUMN Details JSONB";

        using var connection = new SpannerConnection(connectionString);
        using var ddlCmd = connection.CreateDdlCommand(alterStatement);
        await ddlCmd.ExecuteNonQueryAsync();
        Console.WriteLine($"Added the JSONB column named Details to VenueDetails table.");
    }
}

Go


import (
	"context"
	"fmt"
	"io"
	"regexp"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
)

// addJsonBColumn creates a column in the database of type JSONB
func addJsonBColumn(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
	if matches == nil || len(matches) != 3 {
		return fmt.Errorf("addJsonbColumn: invalid database id %s", db)
	}

	ctx := context.Background()
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return err
	}
	defer adminClient.Close()

	op, err := adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
		Database: db,
		Statements: []string{
			"ALTER TABLE Venues ADD COLUMN VenueDetails JSONB",
		},
	})
	if err != nil {
		return err
	}
	if err := op.Wait(ctx); err != nil {
		return err
	}
	fmt.Fprintf(w, "Added VenueDetails column\n")
	return nil
}

Java


import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
import com.google.common.collect.ImmutableList;
import com.google.spanner.admin.database.v1.DatabaseName;
import java.util.concurrent.ExecutionException;

class AddJsonbColumnSample {

  static void addJsonbColumn() throws InterruptedException, ExecutionException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";

    addJsonbColumn(projectId, instanceId, databaseId);
  }

  static void addJsonbColumn(String projectId, String instanceId, String databaseId)
      throws InterruptedException, ExecutionException {
    try (Spanner spanner =
        SpannerOptions.newBuilder()
            .setProjectId(projectId)
            .build()
            .getService();
        DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) {
      // JSONB datatype is only supported with PostgreSQL-dialect databases.
      // Wait for the operation to finish.
      // This will throw an ExecutionException if the operation fails.
      databaseAdminClient.updateDatabaseDdlAsync(
          DatabaseName.of(projectId, instanceId, databaseId),
          ImmutableList.of("ALTER TABLE Venues ADD COLUMN VenueDetails JSONB")).get();
      System.out.printf("Successfully added column `VenueDetails`%n");
    }
  }
}

Node.js

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

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

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

async function pgJsonbAddColumn() {
  // Gets a reference to a Cloud Spanner Database Admin Client object
  const databaseAdminClient = spanner.getDatabaseAdminClient();

  const request = ['ALTER TABLE Venues ADD COLUMN VenueDetails JSONB'];

  // Updates schema by adding a new table.
  const [operation] = await databaseAdminClient.updateDatabaseDdl({
    database: databaseAdminClient.databasePath(
      projectId,
      instanceId,
      databaseId,
    ),
    statements: request,
  });
  console.log(`Waiting for operation on ${databaseId} to complete...`);
  await operation.promise();
  console.log(
    `Added jsonb column to table venues to database ${databaseId}.`,
  );
}
pgJsonbAddColumn();

PHP

use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;
use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseDdlRequest;

/**
 * Add a JSONB column to a table present in a PG Spanner database.
 *
 * @param string $projectId The Google Cloud project ID.
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 * @param string $tableName The table in which the column needs to be added.
 */
function pg_add_jsonb_column(
    string $projectId,
    string $instanceId,
    string $databaseId,
    string $tableName = 'Venues'
): void {
    $databaseAdminClient = new DatabaseAdminClient();
    $databaseName = DatabaseAdminClient::databaseName($projectId, $instanceId, $databaseId);
    $statement = sprintf('ALTER TABLE %s ADD COLUMN VenueDetails JSONB', $tableName);
    $request = new UpdateDatabaseDdlRequest([
        'database' => $databaseName,
        'statements' => [$statement]
    ]);

    $operation = $databaseAdminClient->updateDatabaseDdl($request);

    print('Waiting for operation to complete...' . PHP_EOL);
    $operation->pollUntilComplete();

    print(sprintf('Added column VenueDetails on table %s.', $tableName) . PHP_EOL);
}

Python

def add_jsonb_column(instance_id, database_id):
    """
    Alters Venues tables in the database adding a JSONB column.
    You can create the table by running the `create_table_with_datatypes`
    sample or by running this DDL statement against your database:
    CREATE TABLE Venues (
      VenueId         BIGINT NOT NULL,
      VenueName       character varying(100),
      VenueInfo       BYTEA,
      Capacity        BIGINT,
      OutdoorVenue    BOOL,
      PopularityScore FLOAT8,
      Revenue         NUMERIC,
      LastUpdateTime  SPANNER.COMMIT_TIMESTAMP NOT NULL,
      PRIMARY KEY (VenueId))
    """
    # instance_id = "your-spanner-instance"
    # database_id = "your-spanner-db-id"

    from google.cloud.spanner_admin_database_v1.types import spanner_database_admin

    spanner_client = spanner.Client()
    database_admin_api = spanner_client.database_admin_api

    request = spanner_database_admin.UpdateDatabaseDdlRequest(
        database=database_admin_api.database_path(
            spanner_client.project, instance_id, database_id
        ),
        statements=["ALTER TABLE Venues ADD COLUMN VenueDetails JSONB"],
    )

    operation = database_admin_api.update_database_ddl(request)

    print("Waiting for operation to complete...")
    operation.result(OPERATION_TIMEOUT_SECONDS)

    print(
        'Altered table "Venues" on database {} on instance {}.'.format(
            database_id, instance_id
        )
    )

Ruby

require "google/cloud/spanner"

def spanner_postgresql_jsonb_add_column project_id:, instance_id:, database_id:
  # project_id  = "Your Google Cloud project ID"
  # instance_id = "Your Spanner instance ID"
  # database_id = "Your Spanner database ID"


  # Show how to add JSONB column
  db_admin_client = Google::Cloud::Spanner::Admin::Database.database_admin project: project_id

  db_path = db_admin_client.database_path project: project_id,
                                          instance: instance_id,
                                          database: database_id

  add_column_query = "ALTER TABLE Venues ADD COLUMN VenueDetails JSONB"

  job = db_admin_client.update_database_ddl database: db_path,
                                            statements: [add_column_query]

  job.wait_until_done!

  if job.error?
    puts "Error while adding column. Code: #{job.error.code}. Message: #{job.error.message}"
    raise GRPC::BadStatus.new(job.error.code, job.error.message)
  end

  puts "Added Venues column to VenueDetails table in database #{database_id}"
end

שינוי נתוני JSONB

אפשר לשנות עמודה של JSONB בדיוק כמו כל עמודה אחרת.

לדוגמה:

UPDATE Venues SET VenueFeatures = '{"rating": 4.5, "tags":["multi-cuisine", "open-seating"] }'
  WHERE VenueId = 1;

בדוגמה הבאה מוסבר איך לעדכן נתוני JSONB באמצעות ספריות הלקוח של Spanner.

C++

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.

void JsonbUpdateData(google::cloud::spanner::Client client) {
  auto venue19_details = google::cloud::spanner::JsonB(R"""(
        {"rating": 9, "open": true}
      )""");
  // PG.JSONB takes the last value in the case of duplicate keys.
  auto venue4_details = google::cloud::spanner::JsonB(R"""(
        [
          {"name": null, "available": true},
          {"name": "room 2", "available": false, "name": "room 3"},
          {
            "main hall": {
              "description": "this is the biggest space",
              "size": 200
            }
          }
        ]
      )""");
  auto venue42_details = google::cloud::spanner::JsonB(R"""(
        {
          "name": null,
          "open": {"Monday": true, "Tuesday": false},
          "tags": ["large", "airy"]
        }
      )""");
  auto update_venues = google::cloud::spanner::InsertOrUpdateMutationBuilder(
                           "Venues", {"VenueId", "VenueDetails"})
                           .EmplaceRow(19, venue19_details)
                           .EmplaceRow(4, venue4_details)
                           .EmplaceRow(42, venue42_details)
                           .Build();
  auto commit_result =
      client.Commit(google::cloud::spanner::Mutations{update_venues});
  if (!commit_result) throw std::move(commit_result).status();
  std::cout << "Updated data.\n";
}

C#

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.


using Google.Cloud.Spanner.Data;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public class UpdateDataWithJsonbAsyncPostgresSample
{
    public async Task UpdateDataWithJsonbAsyncPostgres(string projectId, string instanceId, string databaseId)
    {
        List<VenueInformation> venueInformationList = new List<VenueInformation>
        {
            // If you are using .NET Core 3.1 or later, you can use System.Text.Json for serialization instead.
            new VenueInformation
            {
                VenueId = 19,
                Details = JsonConvert.SerializeObject(new
                {
                    rating = 9,
                    open = true,
                })
            },
            new VenueInformation
            {
                VenueId = 4,
                // In the case of repeated field names in the JSON, PostgreSQL JSONB will keep the value of the last field appearance.
                // For instance, in the following example, the value for name will be room 3.
                Details = @"
                {
                    ""name"": ""room 2"",
                    ""available"": false,
                    ""name"": ""room 3""
                }"
            },
            new VenueInformation
            {
                VenueId = 42,
                Details = JsonConvert.SerializeObject(new
                {
                    name = "Central Park",
                    open = new
                    {
                        Monday = true,
                        Tuesday = false,
                    },
                    tags = new string[] {"large", "airy" },
                }),
            },
        };
        // Create connection to Cloud Spanner.
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        await Task.WhenAll(venueInformationList.Select(venue =>
        {
            // Update rows in the Venues table.
            using var cmd = connection.CreateUpdateCommand("VenueInformation", new SpannerParameterCollection
            {
                { "VenueId", SpannerDbType.Int64, venue.VenueId },
                { "Details", SpannerDbType.PgJsonb, venue.Details }
            });
            return cmd.ExecuteNonQueryAsync();
        }));
        Console.WriteLine("Data updated.");
    }

    public struct VenueInformation
    {
        public int VenueId { get; set; }
        public string Details { get; set; }
    }
}

Go

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.


import (
	"context"
	"fmt"
	"io"
	"regexp"

	"cloud.google.com/go/spanner"
)

// updateDataWithJsonBColumn updates database with JsonB type values
func updateDataWithJsonBColumn(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
	if matches == nil || len(matches) != 3 {
		return fmt.Errorf("updateDataWithJsonBColumn: invalid database id %s", db)
	}

	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	type VenueDetails struct {
		Name   spanner.NullString   `json:"name"`
		Rating spanner.NullFloat64  `json:"rating"`
		Open   interface{}          `json:"open"`
		Tags   []spanner.NullString `json:"tags"`
	}

	details_1 := spanner.PGJsonB{Value: []VenueDetails{
		{Name: spanner.NullString{StringVal: "room1", Valid: true}, Open: true},
		{Name: spanner.NullString{StringVal: "room2", Valid: true}, Open: false},
	}, Valid: true}
	details_2 := spanner.PGJsonB{Value: VenueDetails{
		Rating: spanner.NullFloat64{Float64: 9, Valid: true},
		Open:   true,
	}, Valid: true}

	details_3 := spanner.PGJsonB{Value: VenueDetails{
		Name: spanner.NullString{Valid: false},
		Open: map[string]bool{"monday": true, "tuesday": false},
		Tags: []spanner.NullString{{StringVal: "large", Valid: true}, {StringVal: "airy", Valid: true}},
	}, Valid: true}

	cols := []string{"VenueId", "VenueDetails"}
	_, err = client.Apply(ctx, []*spanner.Mutation{
		spanner.Update("Venues", cols, []interface{}{4, details_1}),
		spanner.Update("Venues", cols, []interface{}{19, details_2}),
		spanner.Update("Venues", cols, []interface{}{42, details_3}),
	})

	if err != nil {
		return err
	}
	fmt.Fprintf(w, "Updated data to VenueDetails column\n")

	return nil
}

Java

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.

import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Value;
import com.google.common.collect.ImmutableList;

class UpdateJsonbDataSample {

  static void updateJsonbData() {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";

    try (Spanner spanner =
        SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
      DatabaseClient client =
          spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
      updateJsonbData(client);
    }
  }

  static void updateJsonbData(DatabaseClient client) {
    // PG JSONB takes the last value in the case of duplicate keys.
    // PG JSONB sorts first by key length and then lexicographically with
    // equivalent key length.
    client.write(
        ImmutableList.of(
            Mutation.newInsertOrUpdateBuilder("Venues")
                .set("VenueId")
                .to(4L)
                .set("VenueDetails")
                .to(
                    Value.pgJsonb(
                        "[{\"name\":\"room 1\",\"open\":true,\"name\":\"room 3\"},"
                            + "{\"name\":\"room 2\",\"open\":false}]"))
                .build(),
            Mutation.newInsertOrUpdateBuilder("Venues")
                .set("VenueId")
                .to(19L)
                .set("VenueDetails")
                .to(Value.pgJsonb("{\"rating\":9,\"open\":true}"))
                .build(),
            Mutation.newInsertOrUpdateBuilder("Venues")
                .set("VenueId")
                .to(42L)
                .set("VenueDetails")
                .to(
                    Value.pgJsonb(
                        "{\"name\":null,"
                            + "\"open\":{\"Monday\":true,\"Tuesday\":false},"
                            + "\"tags\":[\"large\",\"airy\"]}"))
                .build()));
    System.out.println("Venues successfully updated");
  }
}

Node.js

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.

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

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

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

async function pgJsonbUpdateData() {
  // 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 venuesTable = database.table('venues');

  const data = [
    {
      VenueId: '19',
      VenueDetails: {rating: 9, open: true},
    },
    {
      VenueId: '4',
      // PG JSONB sorts first by key length and then lexicographically with equivalent key length
      // and takes the last value in the case of duplicate keys
      VenueDetails: `[
      {
        "name": null,
        "available": true
      },
      {
        "name": "room 2",
        "available": false,
        "name": "room 3"
      },
      {
        "main hall": {
          "description": "this is the biggest space",
          "size": 200
        }
      }
    ]`,
    },
    {
      VenueId: '42',
      VenueDetails: {
        name: null,
        open: {
          Monday: true,
          Tuesday: false,
        },
        tags: ['large', 'airy'],
      },
    },
  ];

  try {
    await venuesTable.update(data);
    console.log('Updated data.');
  } catch (err) {
    console.error('ERROR:', err);
  } finally {
    // Close the database when finished.
    await database.close();
  }
}
pgJsonbUpdateData();

PHP

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.

use Google\Cloud\Spanner\SpannerClient;

/**
 * Insert/update data in a JSONB column in a Postgres table.
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 * @param string $tableName The table in which the data needs to be updated.
 */
function pg_jsonb_update_data(
    string $instanceId,
    string $databaseId,
    string $tableName = 'Venues'
): void {
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $database->insertOrUpdateBatch($tableName, [
        [
            'VenueId' => 1,
            'VenueDetails' => '{"rating": 9, "open": true}'
        ],
        [
            'VenueId' => 4,
            'VenueDetails' => '[
                {
                    "name": null,
                    "available": true
                },' .
                // PG JSONB sorts first by key length and then lexicographically with
                // equivalent key length and takes the last value in the case of duplicate keys
                '{
                    "name": "room 2",
                    "available": false,
                    "name": "room 3"
                },
                {
                    "main hall": {
                        "description": "this is the biggest space",
                        "size": 200
                    }
                }
            ]'
        ],
        [
            'VenueId' => 42,
            'VenueDetails' => $spanner->pgJsonb([
                'name' => null,
                'open' => [
                    'Monday' => true,
                    'Tuesday' => false
                ],
                'tags' => ['large', 'airy'],
            ])
        ]
    ]);

    print(sprintf('Inserted/updated 3 rows in table %s', $tableName) . PHP_EOL);
}

Python

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.

def update_data_with_jsonb(instance_id, database_id):
    """Updates Venues tables in the database with the JSONB
    column.
    This updates the `VenueDetails` column which must be created before
    running this sample. You can add the column by running the
    `add_jsonb_column` sample or by running this DDL statement
     against your database:
        ALTER TABLE Venues ADD COLUMN VenueDetails JSONB
    """
    # instance_id = "your-spanner-instance"
    # database_id = "your-spanner-db-id"

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

    """
    PG JSONB takes the last value in the case of duplicate keys.
    PG JSONB sorts first by key length and then lexicographically with
    equivalent key length.
    """

    with database.batch() as batch:
        batch.update(
            table="Venues",
            columns=("VenueId", "VenueDetails"),
            values=[
                (
                    4,
                    JsonObject(
                        [
                            JsonObject({"name": None, "open": True}),
                            JsonObject({"name": "room 2", "open": False}),
                        ]
                    ),
                ),
                (19, JsonObject(rating=9, open=True)),
                (
                    42,
                    JsonObject(
                        {
                            "name": None,
                            "open": {"Monday": True, "Tuesday": False},
                            "tags": ["large", "airy"],
                        }
                    ),
                ),
            ],
        )

    print("Updated data.")

Ruby

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.

require "google/cloud/spanner"

def spanner_postgresql_jsonb_update_data project_id:, instance_id:, database_id:
  # project_id  = "Your Google Cloud project ID"
  # instance_id = "Your Spanner instance ID"
  # database_id = "Your Spanner database ID"

  # Insert JSONB data into table
  spanner = Google::Cloud::Spanner.new project: project_id
  client  = spanner.client instance_id, database_id

  data = [
    {
      VenueId: "19",
      VenueDetails: { rating: 9, open: true }
    },
    {
      VenueId: "4",
      VenueDetails: [
        {
          name: null,
          open: true
        },
        {
          name: "room 2",
          open: false
        },
        {
          main_hall: {
            description: "this is the biggest space",
            size: 200
          }
        }
      ]
    },
    {
      VenueId: "42",
      VenueDetails: {
        name: null,
        open: {
          Monday: true,
          Tuesday: false
        },
        tags: ["large", "airy"]
      }
    }
  ]

  client.upsert "Venues", data
  puts "Inserted data into Venues table"
end

הוספת נתוני JSON לאינדקס

כדי להאיץ את השאילתות של נתוני JSONB, אתם יכולים להשתמש באינדקסים משניים ובאינדקסי חיפוש עם נתוני ה-JSONB. ב-Spanner אי אפשר להשתמש בעמודות מסוג JSONB כמפתחות באינדקסים משניים.

שימוש באינדקס משני

אינדקסים משניים שימושיים כשמסננים לפי ערכים סקלריים בתוך מסמך JSONB. כדי להשתמש באינדקסים משניים עם JSONB, יוצרים עמודה שנוצרה שמחלצת את נתוני הסקלר הרלוונטיים וממירה אותם לסוג נתונים מתאים של SQL. לאחר מכן תוכלו ליצור אינדקס משני על העמודה שנוצרה. האינדקס מאיץ שאילתות שעומדות בדרישות ופועלות על העמודה שנוצרה.

בדוגמה הבאה, יוצרים אינדקס VenuesByCapacity שבו מסד הנתונים משתמש כדי למצוא את המקומות עם קיבולת גדולה מ-1,000. במקום לבדוק כל שורה, Spanner משתמש באינדקס כדי לאתר את השורות הרלוונטיות, וכך משפר את ביצועי השאילתות, במיוחד בטבלאות גדולות.

ALTER TABLE Venues (
ADD COLUMN VenueCapacity BIGINT GENERATED ALWAYS AS ((VenueFeatures->>'capacity')::BIGINT) VIRTUAL,
DateOpened TIMESTAMPTZ
);

CREATE INDEX VenuesByCapacity ON Venues(VenueCapacity);

SELECT VenueName
FROM Venues
WHERE VenueCapacity > 1000;

שימוש באינדקס החיפוש

אינדקס החיפוש שימושי כששולחים שאילתות למסמכי JSONB דינמיים או מגוונים. בניגוד לאינדקסים משניים, אתם יכולים ליצור אינדקסים של חיפוש בכל מסמך JSONB שמאוחסן בעמודת JSONB. אינדקס החיפוש מתאים את עצמו באופן אוטומטי לשינויים במסמכי JSON, בין שורות שונות ולאורך זמן.

בדוגמה הבאה, אתם יוצרים אינדקס חיפוש ב-VenuesByVenueDetails שבו מסד הנתונים משתמש כדי למצוא את המקומות עם פרטים ספציפיים כמו גודל ולוח זמנים לפעילות. במקום לבדוק כל שורה, Spanner משתמש באינדקס כדי לאתר את השורות הרלוונטיות, וכך משפר את ביצועי השאילתות, במיוחד בטבלאות גדולות.

ALTER TABLE Venues
ADD COLUMN VenueDetails_Tokens spanner.tokenlist
  GENERATED ALWAYS AS (spanner.tokenize_jsonb(VenueDetails)) VIRTUAL HIDDEN;

CREATE SEARCH INDEX VenuesByVenueDetails
ON Venues (VenueDetails_Tokens);

SELECT VenueName
FROM Venues
WHERE VenueDetails @> '{"labels": ["large"], "open": {"Friday": true}}'::jsonb;

SELECT VenueName
FROM Venues
WHERE VenueDetails->'open'->'Friday' = 'true'::jsonb
  AND VenueDetails->'type' IN ('"stadium"'::jsonb, '"arena"'::jsonb);

מידע נוסף מופיע במאמר אינדקסי חיפוש ב-JSON.

שליחת שאילתות לנתוני JSONB

אפשר לשלוח שאילתות לעמודות JSONB על סמך הערכים של השדות הבסיסיים. בדוגמה הבאה, הפונקציה מחלצת את VenueId ואת VenueName מ-Venues, כאשר הערך של rating ב-VenueFeatures גדול מ-3.5.

SELECT VenueId, VenueName
FROM Venues
WHERE (VenueFeatures->>'rating')::FLOAT8 > 3.5;

בדוגמה הבאה מוסבר איך לשלוח שאילתות לנתוני JSONB באמצעות ספריות הלקוח של Spanner.

C++

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.

void JsonbQueryWithParameter(google::cloud::spanner::Client client) {
  auto sql = google::cloud::spanner::SqlStatement(
      "SELECT VenueId, VenueDetails FROM Venues"
      "  WHERE CAST(VenueDetails ->> 'rating' AS INTEGER) > $1",
      {{"p1", google::cloud::spanner::Value(2)}});
  using RowType =
      std::tuple<std::int64_t, std::optional<google::cloud::spanner::JsonB>>;
  auto rows = client.ExecuteQuery(std::move(sql));
  for (auto& row : google::cloud::spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "VenueId: " << std::get<0>(*row) << ", ";
    std::cout << "Details: " << std::string(std::get<1>(*row).value()) << "\n";
  }
}

C#

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.


using Google.Cloud.Spanner.Data;
using System.Collections.Generic;
using System.Threading.Tasks;

public class QueryJsonbDataUsingParameterAsyncPostgresSample
{
    public async Task<List<VenueInformation>> QueryJsonbDataUsingParameterAsyncPostgres(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        using var connection = new SpannerConnection(connectionString);
        // Get all the venues with a rating greater than 2.
        /* Details is a column of type JSONB. Some of the data persisted in the Details column has the following structure:
           [{
                 "name": "string",
                 "available": true,
                 "rating": int // This field is optional.
           }] */
        using var command = connection.CreateSelectCommand(
            "SELECT venueid, details FROM VenueInformation WHERE CAST(details ->> 'rating' AS INTEGER) > $1",
            new SpannerParameterCollection
            {
                { "p1", SpannerDbType.Int64, 2 }
            });
        var venues = new List<VenueInformation>();
        using var reader = await command.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            venues.Add(new VenueInformation
            {
                VenueId = reader.GetFieldValue<int>("venueid"),
                Details = reader.GetFieldValue<string>("details")
            });
        }
        return venues;
    }

    public struct VenueInformation
    {
        public int VenueId { get; set; }
        public string Details { get; set; }
    }
}

Go

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.


import (
	"context"
	"fmt"
	"io"
	"regexp"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

// queryWithJsonBParameter queries data on the JSON type column of the database
func queryWithJsonBParameter(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
	if matches == nil || len(matches) != 3 {
		return fmt.Errorf("queryWithJsonBParameter: invalid database id %s", db)
	}
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	type VenueDetails struct {
		Name   spanner.NullString   `json:"name"`
		Rating spanner.NullFloat64  `json:"rating"`
		Open   interface{}          `json:"open"`
		Tags   []spanner.NullString `json:"tags"`
	}

	stmt := spanner.Statement{
		SQL: `SELECT VenueId, VenueDetails FROM Venues WHERE CAST(venuedetails ->> 'rating' AS INTEGER) > $1`,
		Params: map[string]interface{}{
			"p1": 2,
		},
	}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var venueID int64
		var venueDetails spanner.PGJsonB
		if err := row.Columns(&venueID, &venueDetails); err != nil {
			return err
		}
		fmt.Fprintf(w, "The venue details for venue id %v is %v\n", venueID, venueDetails)
	}
}

Java

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.

import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.Value;

class QueryWithJsonbParameterSample {

  static void queryWithJsonbParameter() {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";

    try (Spanner spanner =
        SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
      DatabaseClient client =
          spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
      queryWithJsonbParameter(client);
    }
  }

  static void queryWithJsonbParameter(DatabaseClient client) {
    int rating = 2;
    Statement statement =
        Statement.newBuilder(
                "SELECT VenueId, VenueDetails\n"
                    + "FROM Venues\n"
                    + "WHERE CAST(venuedetails ->> 'rating' "
                    + "AS INTEGER) > $1")
            .bind("p1")
            .to(Value.int64(rating))
            .build();
    try (ResultSet resultSet = client.singleUse().executeQuery(statement)) {
      while (resultSet.next()) {
        System.out.printf(
            "VenueId: %s, VenueDetails: %s%n",
            resultSet.getLong("venueid"), resultSet.getPgJsonb("venuedetails"));
      }
    }
  }
}

Node.js

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.

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

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

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

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

  const select_query = {
    sql: `SELECT venueid, venuedetails FROM Venues
        WHERE CAST(venuedetails ->> 'rating' AS INTEGER) > $1`,
    params: {
      p1: 2,
    },
    types: {
      p1: 'int64',
    },
    json: true,
  };

  // Queries row from the Venues table.
  try {
    const [rows] = await database.run(select_query);

    rows.forEach(row => {
      console.log(
        `VenueId: ${row.venueid}, Details: ${JSON.stringify(
          row.venuedetails,
        )}`,
      );
    });
  } finally {
    // Close the database when finished.
    await database.close();
  }
}
pgJsonbDataType();

PHP

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.


use Google\Cloud\Spanner\Database;
use Google\Cloud\Spanner\SpannerClient;

/**
 * Query data to a jsonb column in a PostgreSQL table.
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 * @param string $tableName The table from which the data needs to be queried.
 */
function pg_jsonb_query_parameter(
    string $instanceId,
    string $databaseId,
    string $tableName = 'Venues'
): void {
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $results = $database->execute(
        sprintf('SELECT venueid, venuedetails FROM %s', $tableName) .
        " WHERE CAST(venuedetails ->> 'rating' AS INTEGER) > $1",
        [
        'parameters' => [
            'p1' => 2
        ],
        'types' => [
            'p1' => Database::TYPE_INT64
        ]
    ]);

    foreach ($results as $row) {
        printf('VenueId: %s, VenueDetails: %s' . PHP_EOL, $row['venueid'], $row['venuedetails']);
    }
}

Python

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.

def query_data_with_jsonb_parameter(instance_id, database_id):
    """Queries sample data using SQL with a JSONB parameter."""
    # instance_id = "your-spanner-instance"
    # database_id = "your-spanner-db-id"

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

    param = {"p1": 2}
    param_type = {"p1": param_types.INT64}

    with database.snapshot() as snapshot:
        results = snapshot.execute_sql(
            "SELECT venueid, venuedetails FROM Venues"
            + " WHERE CAST(venuedetails ->> 'rating' AS INTEGER) > $1",
            params=param,
            param_types=param_type,
        )

        for row in results:
            print("VenueId: {}, VenueDetails: {}".format(*row))

Ruby

במאמר ספריות הלקוח של Spanner מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-Spanner.

כדי לבצע אימות ב-Spanner, צריך להגדיר את Application Default Credentials. למידע נוסף, ראו הגדרת אימות לסביבת פיתוח מקומית.

require "google/cloud/spanner"

def spanner_postgresql_jsonb_query_parameter project_id:, instance_id:, database_id:
  # project_id  = "Your Google Cloud project ID"
  # instance_id = "Your Spanner instance ID"
  # database_id = "Your Spanner database ID"
  spanner = Google::Cloud::Spanner.new project: project_id
  client  = spanner.client instance_id, database_id

  sql_query = <<~QUERY
    SELECT venueid, venuedetails
    FROM Venues
    WHERE CAST(venuedetails ->> 'rating' AS INTEGER) > $1
  QUERY

  # pass parameterized query's params represented by position
  results = client.execute sql_query, params: { p1: 5 }
  puts results.rows.first

  # Read JSONB value from table
  results = client.read "Venues", [:VenueId, :VenueDetails], keys: 19
  puts results.rows.first
end

תכונות JSONB של PostgreSQL שלא נתמכות

התכונות הבאות של PostgreSQL JSONB בקוד פתוח לא נתמכות ב-Spanner JSONB:

  • PrimaryKey ו-ForeignKey
  • הוספה לאינדקס, כולל אינדקס GIN. במקום זאת, אתם יכולים להשתמש באינדקס חיפוש של Spanner, שמאיץ את אותן פעולות JSONB כמו אינדקס GIN. מידע נוסף זמין במאמר הוספה של נתוני JSON לאינדקס.
  • שינוי עמודה מסוג JSONB לסוג נתונים אחר, או שינוי עמודה מסוג נתונים אחר לסוג
  • שימוש בשאילתות עם פרמטרים עם פרמטרים של JSONB ללא סוג בכלים שמשתמשים בפרוטוקול התקשורת של PostgreSQL
  • שימוש בהמרה במנוע השאילתות. בניגוד ל-PostgreSQL בקוד פתוח, אין תמיכה בהמרה מפורשת מ-JSONB לטקסט. כדי להתאים לחתימות של פונקציות, צריך להשתמש בהמרה מפורשת מהסוג JSONB. לדוגמה:

        SELECT concat('abc'::text,  '{"key1":1}'::jsonb);  -- Returns error
        SELECT concat('abc'::text, CAST('{"key1":1}'::jsonb AS TEXT));  -- This works
        SELECT 3 + CAST('5'::jsonb AS INTEGER); -- This works