Bigtable 用の GoogleSQL の概要

GoogleSQL ステートメントを使用して Bigtable データに対してクエリを実行できます。GoogleSQL は ANSI 準拠の構造化クエリ言語(SQL)であり、BigQuery や Spanner などの他の Google Cloud サービスにも実装されています。

このドキュメントでは、Bigtable 用の GoogleSQL の概要について説明します。Bigtable で使用できる SQL クエリの例を示し、Bigtable テーブル スキーマとの関係について説明します。このドキュメントをお読みになる前に、Bigtable ストレージ モデルスキーマ設計のコンセプトを理解しておく必要があります。

クエリは Google Cloud コンソールの Bigtable Studio で作成して実行できます。または、Java 用の Bigtable クライアント ライブラリを使用してプログラムで実行することもできます。詳細については、Bigtable クライアント ライブラリで SQL を使用するをご覧ください。

SQL クエリは、NoSQL データ リクエストと同じ方法でクラスタノードによって処理されます。したがって、Bigtable データに対して実行する SQL クエリを作成するときにも、全テーブル スキャンや複雑なフィルタの回避など、同じベスト プラクティスが適用されます。詳細については、読み取りとパフォーマンスをご覧ください。

ユースケース

Bigtable 用の GoogleSQL は、低レイテンシのアプリケーション開発に最適です。また、Google Cloud コンソールで SQL クエリを実行すると、テーブルのスキーマをすばやく視覚的に把握する、特定のデータが書き込まれたことを確認する、またはデータに関して発生する可能性がある問題をデバッグすることができます。

Bigtable 用の GoogleSQL の現在のリリースでは、次のような一般的な SQL 構造(ただし、これらに限定されません)はサポートされていません。

  • SELECT 以外のデータ操作言語(DML)ステートメント(INSERTUPDATEDELETE など)
  • データ定義言語(DDL)ステートメント(CREATEALTERDROP など)
  • データ アクセス制御ステートメント
  • サブクエリ JOINUNIONGROUP BYUNNESTCTEs のクエリ構文

サポートされている関数、演算子、データ型、クエリ構文などの詳細については、Bigtable 用の GoogleSQL リファレンス ドキュメントをご覧ください。

主なコンセプト

このセクションでは、GoogleSQL を使用して Bigtable データをクエリする場合に留意すべき主なコンセプトについて説明します。

SQL レスポンスの列ファミリー

Bigtable では、テーブルに 1 つ以上の列ファミリーが含まれ、列ファミリーは列をグループ化するために使用されます。GoogleSQL で Bigtable テーブルをクエリする場合、テーブルのスキーマの構成要素は次のとおりです。

  • クエリ対象のテーブルの行キーに対応する _key という名前の特別な列
  • 対象の行の列ファミリーのデータを格納するテーブル内の Bigtable 列ファミリーごとに 1 つの列

Map データ型

Bigtable 用の GoogleSQL には、列ファミリーに対応するように特別に設計されたデータ型 MAP<key, value> が含まれています。

デフォルトでは、マップ列の各行には Key-Value ペアが含まれます。ここで、キーはクエリ対象のテーブルの Bigtable 列修飾子であり、値はその列の最新の値です。

次の SQL クエリの例では、columnFamily という名前のマップから、行キーの値と修飾子の最新の値を含むテーブルが返されます。

  SELECT _key, columnFamily['qualifier'] FROM myTable

Bigtable スキーマで複数のセル(またはデータのバージョン)を列に保存する場合は、with_history などの時間フィルタを SQL ステートメントに追加できます。

この場合、列ファミリーを表すマップはネストされ、配列として返されます。配列内の各キーは、キーとしてのタイムスタンプ、値としてのセルデータで構成されるマップです。形式は MAP<key, ARRAY<STRUCT<timestamp, value>>> です。

次の例では、1 つの行に対する「info」列ファミリー内のすべてのセルを返します。

  SELECT _key, info FROM users(with_history => TRUE) WHERE _key = 'user_123';

返されるマップは次のようになります。クエリ対象のテーブルでは、info は列ファミリー、user_123 は行キー、citystate は列修飾子です。配列内の各タイムスタンプと値のペア(STRUCT)は、対象行のそれらの列内に存在するセルを表し、タイムスタンプの降順で並べ替えられます。

/*----------+------------------------------------------------------------------+
 |   _key   |                              info                                |
 +----------+------------------------------------------------------------------+
 | user_123 | {"city":{<t5>:"Brooklyn", <t0>:"New York"}, "state":{<t0>:"NY"}} |
 +----------+------------------------------------------------------------------*/

スパース テーブル

Bigtable の主な特徴は、柔軟なデータモデルです。Bigtable テーブルでは、行で列が使用されていない場合、その列にはデータは保存されません。1 行に 1 つの列が含まれる場合もあれば、次の行に 100 個の列が含まれる場合もあります。一方、リレーショナル データベースのテーブルではすべての行にすべての列が含まれ、NULL 値は通常、その列にデータがない行の列に格納されます。

ただし、GoogleSQL を使用して Bigtable テーブルに対してクエリを実行すると、未使用の列は空のマップで表され、NULL 値として返されます。これらの NULL 値は、クエリの述語として使用できます。たとえば、WHERE family['column1'] IS NOT NULL のような述語を使用すると、行で column1 が使用されている場合にのみ、行を返すことができます。

バイト

文字列を指定すると、GoogleSQL はデフォルトで STRING 値から BYTES 値に暗黙的にキャストします。つまり、たとえば、バイト シーケンス b'qualifier' ではなく、文字列 'qualifier' を指定できます。

Bigtable はデフォルトですべてのデータをバイトとして扱うため、ほとんどの Bigtable 列には型情報は含まれません。ただし、GoogleSQL では、CAST 関数を使用して読み取り時にスキーマを定義できます。キャストの詳細については、変換関数をご覧ください。

時間フィルタ

次の表に、テーブルの時間要素にアクセスする際に使用できる引数を示します。引数は、フィルタされる順序で示されています。たとえば、with_historylatest_n より前に適用されます。有効なタイムスタンプを指定する必要があります。

引数 説明
as_of Timestamp。指定されたタイムスタンプ以下のタイムスタンプを持つ最新の値を返します。
with_history Boolean。最新の値をスカラーとして返すか、タイムスタンプ付きの値を STRUCT として返すかを制御します。
after_or_equal Timestamp。入力後のタイムスタンプを持つ値(この値を含む)。with_history => TRUE が必須です
before Timestamp。入力より前のタイムスタンプがある値(この値は含まれない)。with_history => TRUE が必須です
latest_n 整数。列修飾子(マップキー)ごとに返されるタイムスタンプ付き値の数。1 以上にする必要があります。with_history => TRUE が必須です。

その他の例については、高度なクエリ パターンをご覧ください。

基礎的なクエリ

このセクションでは、基本的な Bigtable SQL クエリの例とそれらの仕組みについて説明します。その他のサンプルクエリについては、Bigtable 用の GoogleSQL のクエリパターンの例をご覧ください。

最新バージョンを取得する

Bigtable では各列に複数のバージョンのデータを格納できますが、Bigtable 用の GoogleSQL はデフォルトで、各行のデータの最新バージョン(最新のセル)を返します。

user1 がニューヨーク州内で 2 回、ブルックリン市内で 1 回転居したことを示している次のサンプル データセットについて考えてみましょう。この例では、address が列ファミリーであり、列修飾子は streetcitystate です。列内のセルは空行で区切ります。

address
_key street city state
user1 2023/01/10-14:10:01.000:
'113 Xyz Street'

2021/12/20-09:44:31.010:
'76 Xyz Street'

2005/03/01-11:12:15.112:
'123 Abc Street'
2021/12/20-09:44:31.010:
'Brooklyn'

2005/03/01-11:12:15.112:
'Queens'
2005/03/01-11:12:15.112:
'NY'

user1 の各列の最新バージョンを取得するには、次のような SELECT ステートメントを使用します。

   SELECT * FROM myTable WHERE _key = 'user1'

レスポンスには現在の住所が含まれます。これは、JSON として出力された最新の番地、市町村、都道府県の値(別の時刻に書き込まれた値)を組み合わせたものです。タイムスタンプはレスポンスには含まれません。

_key address
user1 {street:'113 Xyz Street', city:'Brooklyn', state: :'NY'}

すべてのバージョンを取得する

データの古いバージョン(セル)を取得するには、with_history フラグを使用します。次の例に示すように、列と式にエイリアスを行うこともできます。

  SELECT _key, columnFamily['qualifier'] AS col1
  FROM myTable(with_history => TRUE)

行の現在の状態につながるイベントをより深く理解するには、履歴全体を取得して各値のタイムスタンプを取得します。たとえば、user1 がいつ現在の住所に引っ越したのか、そしてどこから引っ越したのかを把握するには、次のクエリを実行します。

  SELECT
    address['street'][0].value AS moved_to,
    address['street'][1].value AS moved_from,
    FORMAT_TIMESTAMP('%Y-%m-%d', address['street'][0].timestamp) AS moved_on,
  FROM myTable(with_history => TRUE)
  WHERE _key = 'user1'

SQL クエリで with_history フラグを使用すると、レスポンスは MAP<key, ARRAY<STRUCT<timestamp, value>>> として返されます。配列の各項目は、指定された行、列ファミリー、列のタイムスタンプ付き値です。タイムスタンプは日付の新しい順に並べられるため、最新のデータが常に返される最初のアイテムになります。

クエリ レスポンスは次のとおりです。

moved_to moved_from moved_on
113 Xyz Street 76 Xyz Street 2023/01/10

次のクエリで示すように、配列関数を使用して各行のバージョン数を取得することもできます。

  SELECT _key, ARRAY_LENGTH(MAP_ENTRIES(address)) AS version_count
  FROM myTable(with_history => TRUE)

指定した時刻のデータを取得する

as_of フィルタを使用すると、特定の時点の行の状態を取得できます。たとえば、2022 年 1 月 10 日午後 1 時 14 分時点の user のアドレスを確認する場合は、次のクエリを実行します。

  SELECT address
  FROM myTable(as_of => TIMESTAMP('2022/01/10-13:14:00'))
  WHERE _key = 'user1'

結果には、2022 年 1 月 10 日午後 1 時 14 分時点で把握されている最後の住所が表示されます。これは、2021 年 12 月 20 日 09:44:31.010 の更新の番地と、2005 年 3 月 1 日 11:12:15.112 の更新の都道府県を組み合わせたものです。

address
{street:'76 Xyz Street', city:'Brooklyn', state: :'NY'}

Unix タイムスタンプを使用しても同じ結果が得られます。

  SELECT address
  FROM myTable(as_of => TIMESTAMP_FROM_UNIX_MILLIS(1641820440000))
  WHERE _key = 'user1'

煙警報と一酸化炭素警報のオン / オフの状態を示す次のデータセットについて考えてみましょう。列ファミリーは alarmType であり、列修飾子は smokecarbonMonoxide です。各列のセルは空行で区切られています。


alarmType
_key smoke carbonMonoxide
building1#section1 2023/04/01-09:10:15.000:
'off'

2023/04/01-08:41:40.000:
'on'

2020/07/03-06:25:31.000:
'off'

2020/07/03-06:02:04.000:
'on'
2023/04/01-09:22:08.000:
'off'

2023/04/01-08:53:12.000:
'on'
building1#section2 2021/03/11-07:15:04.000:
'off'

2021/03/11-07:00:25.000:
'on'

次のクエリを使用して、2023 年 4 月 1 日午前 9 時に煙警報が稼働していた building1 のセクションと、その時点での一酸化炭素警報のステータスを確認できます。

  SELECT _key AS location, sensorType['carbonMonoxide'] AS CO_sensor
  FROM alarms(as_of => TIMESTAMP('2023/04/01-09:00:00.000'))
  WHERE _key LIKE 'building1%' and sensorType['smoke'] = 'on'

結果は次のようになります。

location CO_sensor
building1#section1 'on'

時系列データのクエリ

Bigtable の一般的なユースケースは、時系列データのストレージです。気象センサーの気温と湿度の測定値を示す次のサンプル データセットについて考えてみましょう。列ファミリー ID は metrics であり、列修飾子は temperaturehumidity です。列内のセルは空の行で区切られ、各セルはタイムスタンプ付きのセンサー測定値を表します。


metrics
_key 温度 湿度
sensorA#20230105 2023/01/05-02:00:00.000:
54

2023/01/05-01:00:00.000:
56

2023/01/05-00:00:00.000:
55
2023/01/05-02:00:00.000:
0.89

2023/01/05-01:00:00.000:
0.9

2023/01/05-00:00:00.000:
0.91
sensorA#20230104 2023/01/04-23:00:00.000:
56

2023/01/04-22:00:00.000:
57
2023/01/04-23:00:00.000:
0.9

2023/01/04-22:00:00.000:
0.91

時間フィルタ afterbeforeafter_or_equal を使用して、タイムスタンプ値の特定の範囲を取得できます。次の例では、after を使用しています。

   SELECT metrics['temperature'] AS temp_versioned
   FROM
   sensorReadings(after => TIMESTAMP('2023/01/04-23:00:00'),
         before => TIMESTAMP('2023/01/05-01:00:00'))
   WHERE _key LIKE 'sensorA%'

クエリでは、次の形式でデータが返されます。

temp_versioned
{timestamp: '2023/01/05-01:00:00.000', value:56}
{timestamp: '2023/01/05-00:00:00.000', value: 55}
{timestamp: '2023/01/04-23:00:00.000', value:56}

JSON をクエリする

JSON 関数を使用すると、運用ワークロード用に Bigtable 値として保存されている JSON を操作できます。

たとえば、次のクエリを使用して、session 列ファミリーの最新のセルから行キーとともに JSON 要素 abc の値を取得できます。

  SELECT _key, JSON_VALUE(session['payload'],'$.abc') AS abc FROM analytics

特殊文字と予約済みの単語をエスケープする

Bigtable では、テーブルと列の名前を柔軟に指定できます。その結果、SQL クエリでは、特殊文字や予約済み単語が原因でテーブル名をエスケープすることが必要な場合があります。

たとえば、次のクエリは、テーブル名にピリオドが含まれているため有効な SQL ではありません。

  -- ERROR: Table name format not supported

  SELECT * FROM my.table WHERE _key = 'r1'

ただし、アイテムをバッククォート(`)文字で囲むことで、この問題を解決できます。

  SELECT * FROM `my.table` WHERE _key = 'r1'

SQL の予約済みキーワードを識別子として使用する場合も、同様にエスケープできます。

  SELECT * FROM `select` WHERE _key = 'r1'

Bigtable クライアント ライブラリで SQL を使用する

Java と Python 用の Bigtable クライアント ライブラリでは、executeQuery API を使用した SQL によるデータのクエリがサポートされています。次の例は、クエリを発行してデータにアクセスする方法を示しています。

Java

この機能を使用するには、java-bigtable バージョン 2.41.0 以降を使用する必要があります。使用方法の詳細については、Javadoc の executeQueryStatementResultSet をご覧ください。

  static void query(BigtableDataClient client) {
    try (ResultSet resultSet =
        client.executeQuery(
            Statement.of(
                "SELECT cf1['bytesCol'] AS bytesCol, CAST(cf2['stringCol'] AS STRING) AS stringCol, cf3 FROM myTable WHERE _key='mykey'"))) {
      while (resultSet.next()) {
        ByteString byteValue = resultSet.getBytes("bytesCol");
        String stringValue = resultSet.getString("stringCol");
        Map<ByteString, ByteString> cf3Value =
            resultSet.getMap("cf3", SqlType.mapOf(SqlType.bytes(), SqlType.bytes()));
        // Do something with the data
      }
    }
  }

Python asyncio

この機能を使用するには、python-bigtable バージョン 2.26.0 以降を使用する必要があります。

  from google.cloud.bigtable.data import BigtableDataClientAsync

  async def execute_query(project_id, instance_id, table_id):
      async with BigtableDataClientAsync(project=project_id) as client:
          query = (
            "SELECT cf1['bytesCol'] AS bytesCol, CAST(cf2['stringCol'] AS STRING) AS stringCol,"
            " cf3 FROM {table_id} WHERE _key='mykey'"
          )
          async for row in await client.execute_query(query, instance_id):
            print(row["_key"], row["bytesCol"], row["stringCol"], row["cf3"])

次のステップ