1. 概要

この記事では、N1QLを使用してCouchbaseServerにクエリを実行する方法について説明します。 簡単に言うと、これはSQL forNoSQLデータベースです。SQL/リレーショナルデータベースからNoSQLデータベースシステムへの移行を容易にすることを目的としています。

CouchbaseServerと対話する方法はいくつかあります。 ここでは、Java SDKを使用してデータベースと対話します。これは、Javaアプリケーションで一般的です。

2. Mavenの依存関係

ローカルのCouchbaseServerがすでにセットアップされていることを前提としています。 そうでない場合は、このガイドを使用して開始できます。

次に、CouchbaseJavaSDKの依存関係をpom.xmlに追加しましょう。

<dependency>
    <groupId>com.couchbase.client</groupId>
    <artifactId>java-client</artifactId>
    <version>2.5.0</version>
</dependency>

Couchbase Java SDKの最新バージョンは、 MavenCentralにあります。

また、Jacksonライブラリを使用して、クエリから返された結果をマッピングします。 その依存関係をpom.xmlにも追加しましょう。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.0</version>
</dependency>

ジャクソンライブラリの最新バージョンは、 MavenCentralにあります。

3. CouchbaseServerへの接続

プロジェクトが適切な依存関係でセットアップされたので、JavaアプリケーションからCouchbaseServerに接続しましょう。

まず、Couchbase Serverを起動する必要があります(まだ実行されていない場合)。

CouchbaseServerの起動と停止のガイドはここにあります。

Couchbaseバケットに接続しましょう。

Cluster cluster = CouchbaseCluster.create("localhost");
Bucket bucket = cluster.openBucket("test");

Couchbase Cluster に接続してから、Bucketオブジェクトを取得しました。

Couchbaseクラスター内のバケットの名前はtestであり、CouchbaseWebコンソールを使用して作成できます。 すべてのデータベース操作が完了したら、開いた特定のバケットを閉じることができます。

一方、クラスターから切断することはできます。これにより、最終的にすべてのバケットが閉じられます。

bucket.close();
cluster.disconnect();

4. ドキュメントの挿入

Couchbaseはドキュメント指向のデータベースシステムです。 テストバケットに新しいドキュメントを追加しましょう。

JsonObject personObj = JsonObject.create()
  .put("name", "John")
  .put("email", "[email protected]")
  .put("interests", JsonArray.from("Java", "Nigerian Jollof"));

String id = UUID.randomUUID().toString();
JsonDocument doc = JsonDocument.create(id, personObj);
bucket.insert(doc);

まず、JSON personObj を作成し、いくつかの初期データを提供しました。 キーは、リレーショナルデータベースシステムでは列と見なすことができます。

人物オブジェクトから、バケットに挿入する JsonDocument.create()、を使用してJSONドキュメントを作成しました。 java.util.UUIDクラスを使用してランダムなidを生成することに注意してください。

挿入されたドキュメントは、CouchbaseWebコンソールのhttp:// localhost:8091 で確認するか、bucket.get()idで呼び出すことで確認できます。

System.out.println(bucket.get(id));

5. 基本的なN1QLSELECTクエリ

N1QLはSQLのスーパーセットであり、その構文は当然、似ています。

たとえば、テストバケット内のすべてのドキュメントを選択するためのN1QLは次のとおりです。

SELECT * FROM test

アプリケーションでこのクエリを実行してみましょう。

bucket.bucketManager().createN1qlPrimaryIndex(true, false);

N1qlQueryResult result
  = bucket.query(N1qlQuery.simple("SELECT * FROM test"));

まず、 createN1qlPrimaryIndex()を使用してプライマリインデックスを作成します。以前に作成されている場合は無視されます。 クエリを実行する前に、作成する必要があります。

次に、bucket.query()を使用してN1QLクエリを実行します。

N1qlQueryResult反復可能オブジェクト、したがって、を使用してすべての行を印刷できます forEach()

result.forEach(System.out::println);

返されたresultから、 result.info()を呼び出すことにより、N1qlMetricsオブジェクトを取得できます。 メトリックオブジェクトから、返された結果に関する洞察を得ることができます。たとえば、結果やエラー数などです。

System.out.println("result count: " + result.info().resultCount());
System.out.println("error count: " + result.info().errorCount());

返されたresultで、 result.parseSuccess()を使用して、クエリが構文的に正しく、正常に解析されているかどうかを確認できます。 result.finalSuccess()を使用して、クエリの実行が成功したかどうかを判断できます。

6. N1QLクエリステートメント

さまざまなN1QLクエリステートメントと、JavaSDKを介してそれらを実行するさまざまな方法を見てみましょう。

6.1. SELECTステートメント

NIQLのSELECTステートメントは、標準のSQL SELECTとまったく同じです。 これは3つの部分で構成されています。

  • SELECT 返されるドキュメントの投影を定義します
  • FROM は、ドキュメントをフェッチするためのキースペースについて説明しています。 キースペースは、SQLデータベースシステムのテーブル名と同義です。
  • WHERE は、追加のフィルタリング基準を指定します

Couchbase Serverには、いくつかのサンプルバケット(データベース)が付属しています。 初期設定時に読み込まれなかった場合は、Webコンソールの設定セクションに専用のタブがあります。

travel-sampleバケットを使用します。 travel-sample バケットには、航空会社、ランドマーク、空港、ホテル、およびルートのデータが含まれています。 データモデルはここにあります。

旅行サンプルデータから100の航空会社レコードを選択しましょう。

String query = "SELECT name FROM `travel-sample` " +
  "WHERE type = 'airport' LIMIT 100";
N1qlQueryResult result1 = bucket.query(N1qlQuery.simple(query));

上記のように、N1QLクエリはSQLと非常によく似ています。 キースペース名にはハイフンが含まれているため、バッククォート( `)を付ける必要があることに注意してください。

N1qlQueryResult は、データベースから返された生のJSONデータの単なるラッパーです。 伸びる反復可能ループすることができます。

呼び出す result1.allRows() 内のすべての行を返しますリスト物体。 これは、 Stream APIを使用して結果を処理したり、インデックスを介して各結果にアクセスしたりする場合に便利です。

N1qlQueryRow row = result1.allRows().get(0);
JsonObject rowJson = row.value();
System.out.println("Name in First Row " + rowJson.get("name"));

返された結果の最初の行を取得し、 row.value()を使用して JsonObject を取得します。これにより、行がキーと値のペアにマップされ、キーが対応します。列名に。

したがって、 get()を使用して、最初の行のcolumn、 name、の値を取得しました。 それはそれと同じくらい簡単です。

これまで、単純なN1QLクエリを使用してきました。 N1QLのparameterizedステートメントを見てみましょう。

このクエリでは、ワイルドカード(*)記号を使用して、 travel-sample レコードのすべてのフィールドを選択します。ここで、typeairportです。 。

type は、パラメーターとしてステートメントに渡されます。 次に、返された結果を処理します。

JsonObject pVal = JsonObject.create().put("type", "airport");
String query = "SELECT * FROM `travel-sample` " +
  "WHERE type = $type LIMIT 100";
N1qlQueryResult r2 = bucket.query(N1qlQuery.parameterized(query, pVal));

パラメータをキーと値のペアとして保持するJsonObjectを作成しました。 pValオブジェクトのキー’type’、の値は、query$typeプレースホルダーを置き換えるために使用されます。ストリング。

N1qlQuery.parameterized()は、上記のように、1つ以上のプレースホルダーとJsonObjectを含むクエリ文字列を受け入れます。

上記の前のサンプルクエリでは、列のみを選択します– 名前。 これにより、返された結果を簡単にマッピングできます。 JsonObject

しかし、selectステートメントでワイルドカード(*)を使用しているので、それほど単純ではありません。 返される結果は生のJSON文字列です。

[  
  {  
    "travel-sample":{  
      "airportname":"Calais Dunkerque",
      "city":"Calais",
      "country":"France",
      "faa":"CQF",
      "geo":{  
        "alt":12,
        "lat":50.962097,
        "lon":1.954764
      },
      "icao":"LFAC",
      "id":1254,
      "type":"airport",
      "tz":"Europe/Paris"
    }
  },

したがって、必要なのは、各行を、列名を指定してデータにアクセスできる構造にマップする方法です。

したがって、 N1qlQueryResult を受け入れるメソッドを作成してから、結果のすべての行をJsonNodeオブジェクトにマップしてみましょう。

JsonNode を選択したのは、幅広いJSONデータ構造を処理でき、簡単にナビゲートできるためです。

public static List<JsonNode> extractJsonResult(N1qlQueryResult result) {
  return result.allRows().stream()
    .map(row -> {
        try {
            return objectMapper.readTree(row.value().toString());
        } catch (IOException e) {
            logger.log(Level.WARNING, e.getLocalizedMessage());
            return null;
        }
    })
    .filter(Objects::nonNull)
    .collect(Collectors.toList());
}

Stream APIを使用して、結果の各行を処理しました。 各行をにマッピングしました JsonNode オブジェクトを作成し、結果をリスト JsonNodes。

これで、このメソッドを使用して、最後のクエリから返された結果を処理できます。

List<JsonNode> list = extractJsonResult(r2);
System.out.println(
  list.get(0).get("travel-sample").get("airportname").asText());

前に示したJSON出力の例から、すべての行には、 SELECT クエリで指定されたキースペース名(この場合は travel-sample )に相関するキーがあります。

そのため、結果の最初の行であるJsonNodeを取得しました。 次に、ノードをトラバースして airportname キーに到達し、テキストとして出力されます。

以前に共有された生のJSON出力の例は、返された結果の構造に従って、より明確になります。

6.2。N1QLDSLを使用したSELECTステートメント

クエリの構築に生の文字列リテラルを使用する以外に、使用しているJavaSDKに付属しているN1QLDSLを使用することもできます。

たとえば、上記の文字列クエリはDSLを使用して次のように定式化できます。

Statement statement = select("*")
  .from(i("travel-sample"))
  .where(x("type").eq(s("airport")))
  .limit(100);
N1qlQueryResult r3 = bucket.query(N1qlQuery.simple(statement));

DSLは流暢で、簡単に解釈できます。 データ選択のクラスとメソッドは、com.couchbase.client.java.query.Selectクラスにあります。

i()、eq()、x()、s()などの式メソッドは、com.couchbase.client.java.query.dsl.Expressionクラスにあります。 DSLの詳細についてはこちらをご覧ください。

N1QL selectステートメントには、OFFSET、GROUP BY、およびORDERBY句を含めることもできます。 構文は標準SQLの構文とほとんど同じであり、その参照を見つけることができます。 ここ

N1QLのWHERE句は、その定義に論理演算子 AND OR 、およびNOTを含めることができます。 これに加えて、N1QLには、>、==、!=、 IS NULL othersなどの比較演算子が用意されています。

保存されたドキュメントへのアクセスを容易にする他の演算子もあります。文字列演算子を使用してフィールドを連結し、単一の文字列を形成できます。ネストされた演算子を使用して配列をスライスし、チェリーピックフィールドまたは要素。

これらの動作を見てみましょう。

このクエリは、 city 列を選択し、Airportname列とfaa列をtravel-sampleからportname_faaとして連結します。 country列が‘States’‘で終わり、空港の Latitudeが70以上であるバケット:

String query2 = "SELECT t.city, " +
  "t.airportname || \" (\" || t.faa || \")\" AS portname_faa " +
  "FROM `travel-sample` t " +
  "WHERE t.type=\"airport\"" +
  "AND t.country LIKE '%States'" +
  "AND t.geo.lat >= 70 " +
  "LIMIT 2";
N1qlQueryResult r4 = bucket.query(N1qlQuery.simple(query2));
List<JsonNode> list3 = extractJsonResult(r4);
System.out.println("First Doc : " + list3.get(0));

N1QLDSLを使用して同じことを行うことができます。

Statement st2 = select(
  x("t.city, t.airportname")
  .concat(s(" (")).concat(x("t.faa")).concat(s(")")).as("portname_faa"))
  .from(i("travel-sample").as("t"))
  .where( x("t.type").eq(s("airport"))
  .and(x("t.country").like(s("%States")))
  .and(x("t.geo.lat").gte(70)))
  .limit(2);
N1qlQueryResult r5 = bucket.query(N1qlQuery.simple(st2));
//...

N1QLの他のステートメントを見てみましょう。 このセクションで習得した知識に基づいて構築します。

6.3. INSERTステートメント

N1QLの挿入ステートメントの構文は次のとおりです。

INSERT INTO `travel-sample` ( KEY, VALUE )
VALUES("unique_key", { "id": "01", "type": "airline"})
RETURNING META().id as docid, *;

travel-sample がキースペース名であるのに対し、 unique_key は、それに続く値オブジェクトに必要な非重複キーです。

最後のセグメントは、何が返されるかを指定するRETURNINGステートメントです。

この場合、 id 挿入されたドキュメントの docid。 ワイルドカード(*)は、追加されたドキュメントの他の属性も、とは別に返される必要があることを示します。 docid。 以下のサンプル結果を参照してください。

Couchbase Web Consoleの[クエリ]タブで次のステートメントを実行すると、travel-sampleバケットに新しいレコードが挿入されます。

INSERT INTO `travel-sample` (KEY, VALUE)
VALUES('cust1293', {"id":"1293","name":"Sample Airline", "type":"airline"})
RETURNING META().id as docid, *

Javaアプリからも同じことをしましょう。 まず、次のような生のクエリを使用できます。

String query = "INSERT INTO `travel-sample` (KEY, VALUE) " +
  " VALUES(" +
  "\"cust1293\", " +
  "{\"id\":\"1293\",\"name\":\"Sample Airline\", \"type\":\"airline\"})" +
  " RETURNING META().id as docid, *";
N1qlQueryResult r1 = bucket.query(N1qlQuery.simple(query));
r1.forEach(System.out::println);

これにより、挿入されたドキュメントのiddocidとして個別に返され、完全なドキュメント本文が個別に返されます。

{  
  "docid":"cust1293",
  "travel-sample":{  
    "id":"1293",
    "name":"Sample Airline",
    "type":"airline"
  }
}

ただし、Java SDKを使用しているため、 JsonDocument を作成し、バケットAPIを介してバケットに挿入することでオブジェクト方式で実行できます。

JsonObject ob = JsonObject.create()
  .put("id", "1293")
  .put("name", "Sample Airline")
  .put("type", "airline");
bucket.insert(JsonDocument.create("cust1295", ob));

insert()を使用する代わりに、upsert()を使用して、同じ一意の識別子cust1295を持つ既存のドキュメントがある場合にドキュメントを更新できます。

現在のように、 insert()を使用すると、同じ一意のIDがすでに存在する場合に例外がスローされます。

ただし、 insert()が成功すると、挿入されたデータの一意のIDとエントリを含むJsonDocumentが返されます。

N1QLを使用した一括挿入の構文は次のとおりです。

INSERT INTO `travel-sample` ( KEY, VALUE )
VALUES("unique_key", { "id": "01", "type": "airline"}),
VALUES("unique_key", { "id": "01", "type": "airline"}),
VALUES("unique_n", { "id": "01", "type": "airline"})
RETURNING META().id as docid, *;

SDKに下線を引くReactiveJavaを使用して、JavaSDKで一括操作を実行できます。 バッチプロセスを使用して、10個のドキュメントをバケットに追加しましょう。

List<JsonDocument> documents = IntStream.rangeClosed(0,10)
  .mapToObj( i -> {
      JsonObject content = JsonObject.create()
        .put("id", i)
        .put("type", "airline")
        .put("name", "Sample Airline "  + i);
      return JsonDocument.create("cust_" + i, content);
  }).collect(Collectors.toList());

List<JsonDocument> r5 = Observable
  .from(documents)
  .flatMap(doc -> bucket.async().insert(doc))
  .toList()
  .last()
  .toBlocking()
  .single();

r5.forEach(System.out::println);

まず、10個のドキュメントを生成し、それらを List; に配置し、次にRxJavaを使用して一括操作を実行しました。

最後に、各挿入の結果を印刷します–これは蓄積されてリスト。

Java SDKで一括操作を実行するためのリファレンスは、ここにあります。 また、挿入ステートメントのリファレンスはここにあります。

6.4. UPDATEステートメント

N1QLにはUPDATEステートメントもあります。 一意のキーで識別されるドキュメントを更新できます。 updateステートメントを使用して、属性の値を SET (更新)するか、属性を UNSET (削除)することができます。

最近travel-sampleバケットに挿入したドキュメントの1つを更新しましょう。

String query2 = "UPDATE `travel-sample` USE KEYS \"cust_1\" " +
  "SET name=\"Sample Airline Updated\" RETURNING name";
N1qlQueryResult result = bucket.query(N1qlQuery.simple(query2));
result.forEach(System.out::println);

上記のクエリでは、バケット内のcust_1エントリのname属性をSampleAirline Updateed、に更新し、更新された名前を返すようにクエリに指示します。 。

前述のように、同じIDで JsonDocument を作成し、 Bucket APIのupsert()を使用してドキュメントを更新することでも同じことができます。 :

JsonObject o2 = JsonObject.create()
  .put("name", "Sample Airline Updated");
bucket.upsert(JsonDocument.create("cust_1", o2));

この次のクエリでは、 UNSET コマンドを使用して、 name 属性を削除し、影響を受けるドキュメントを返します。

String query3 = "UPDATE `travel-sample` USE KEYS \"cust_2\" " +
  "UNSET name RETURNING *";
N1qlQueryResult result1 = bucket.query(N1qlQuery.simple(query3));
result1.forEach(System.out::println);

返されるJSON文字列は次のとおりです。

{  
  "travel-sample":{  
    "id":2,
    "type":"airline"
  }
}

欠落しているname属性に注意してください–これはドキュメントオブジェクトから削除されています。 N1QL更新構文リファレンスはここにあります。

そこで、新しいドキュメントの挿入とドキュメントの更新について見ていきます。 次に、CRUDの頭字語の最後の部分であるDELETEを見てみましょう。

6.5. DELETEステートメント

DELETE クエリを使用して、以前に作成したドキュメントの一部を削除してみましょう。 一意のIDを使用して、 USEKEYSキーワードでドキュメントを識別します。

String query4 = "DELETE FROM `travel-sample` USE KEYS \"cust_50\"";
N1qlQueryResult result4 = bucket.query(N1qlQuery.simple(query4));

N1QL DELETE ステートメントも、WHERE句を取ります。 したがって、条件を使用して、削除するレコードを選択できます。

String query5 = "DELETE FROM `travel-sample` WHERE id = 0 RETURNING *";
N1qlQueryResult result5 = bucket.query(N1qlQuery.simple(query5));

バケットAPIからremove()を直接使用することもできます。

bucket.remove("cust_2");

はるかに簡単ですよね? はい。ただし、N1QLを使用してそれを行う方法もわかりました。 DELETE 構文のリファレンスドキュメントは、ここにあります。

7. N1QL関数とサブクエリ

N1QLは、構文だけでSQLに似ているだけではありません。 それはいくつかの機能にまで及びます。 SQLには、クエリ文字列内で使用できる COUNT()などの関数があります。

N1QLにも同様に、クエリ文字列で使用できる関数があります。

たとえば、このクエリは、travel-sampleバケットにあるランドマークレコードの総数を返します。

SELECT COUNT(*) as landmark_count FROM `travel-sample` WHERE type = 'landmark'

上記の前の例では、UPDATEステートメントでMETA関数を使用して、更新されたドキュメントのidを返しました。

末尾の空白を削除したり、小文字と大文字を作成したり、文字列にトークンが含まれているかどうかを確認したりできる文字列メソッドがあります。 これらの関数のいくつかをクエリで使用してみましょう。

これらの関数のいくつかをクエリで使用してみましょう。

INSERT INTO `travel-sample` (KEY, VALUE) 
VALUES(LOWER(UUID()), 
  {"id":LOWER(UUID()), "name":"Sample Airport Rand", "created_at": NOW_MILLIS()})
RETURNING META().id as docid, *

上記のクエリは、travel-sampleバケットに新しいエントリを挿入します。 UUID()関数を使用して、 LOWER()関数を使用して小文字に変換された一意のランダムIDを生成します。

NOW_MILLIS()メソッドを使用して、現在の時刻をミリ秒単位でcreated_at属性の値として設定しました。 N1QL関数の完全なリファレンスは、ここにあります。

サブクエリは時々便利であり、N1QLはそれらを提供します。 travel-sample バケットを引き続き使用して、特定の航空会社のすべてのルートの目的地の空港を選択し、それらが存在する国を取得しましょう。

SELECT DISTINCT country FROM `travel-sample` WHERE type = "airport" AND faa WITHIN 
  (SELECT destinationairport 
  FROM `travel-sample` t WHERE t.type = "route" and t.airlineid = "airline_10")

上記のクエリのサブクエリは括弧で囲まれ、airline_10に関連付けられているすべてのルートのdestinationairport属性をコレクションとして返します。

destinationairport 属性は、travel-sampleバケット内のairportドキュメントのfaa属性と相関しています。 WITHIN キーワードは、N1QLのコレクション演算子の一部です。

これで、airline_10のすべてのルートの目的地の空港の国がわかりました。 その国のホテルを探して、何か面白いことをしましょう。

SELECT name, price, address, country FROM `travel-sample` h 
WHERE h.type = "hotel" AND h.country WITHIN
  (SELECT DISTINCT country FROM `travel-sample` 
  WHERE type = "airport" AND faa WITHIN 
  (SELECT destinationairport FROM `travel-sample` t 
  WHERE t.type = "route" and t.airlineid = "airline_10" )
  ) LIMIT 100

前のクエリは、最も外側のクエリのWHERE制約のサブクエリとして使用されました。 DISTINCT キーワード(SQLと同じことを行います)が重複しないデータを返すことに注意してください。

ここでのすべてのクエリ例は、この記事の前半で示したように、SDKを使用して実行できます。

8. 結論

N1QLは、Couchbaseのようなドキュメントベースのデータベースを別のレベル全体にクエリするプロセスを取ります。 このプロセスを簡素化するだけでなく、リレーショナルデータベースシステムからの切り替えも非常に簡単になります。

この記事ではN1QLクエリを見てきました。 主なドキュメントはここにあります。 また、Spring DataCouchbaseについてはこちらで学ぶことができます。

いつものように、完全なソースコードはGithub利用できます。