Couchbase Lite機能解説：クエリAPI①ステートメント

## はじめに

ここでは、Couchbase Lite 3.0の新機能であるクエリAPIについて解説します。

なお、Couchbase Mobileについては、[Couchbase Mobileアプリケーション開発へのロードマップ](https://qiita.com/yoshiyuki_kono/items/6a5bbad1b02196a25417)に記事をまとめている他、（これらの記事を元に構成した）以下の電子書籍を無償で頒布しています。

https://techbookfest.org/product/5074954942939136?productVariantID=5042865933647872

また、Couchbase Mobileは、Couchbase LiteとCouchbase Serverとのデータ同期機能を提供します。Couchbase Serverの存在意義、機能詳細、利用方法等については、拙著[NoSQLドキュメント指向データベースCouchbase Serverファーストステップガイド(インプレスR&D刊)](https://www.amazon.co.jp/-/en/%E6%B2%B3%E9%87%8E-%E6%B3%B0%E5%B9%B8/dp/429560030X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=1636588872&sr=1-1)や、[NoSQL/JSONデータベースCouchbase Server理解・活用へのロードマップ](https://qiita.com/yoshiyuki_kono/items/4c9cd1115efc5807b49f)にまとめてある記事をご参考ください。

## 概要

Couchbase Liteを利用する開発者は、SQL類似のクエリを使用して、データベースを検索することができます。クエリAPIは、次に示す形式のクエリステートメントを使用します。

```
SELECT ____ 
FROM ____ 
WHERE ____, 
JOIN ____ 
GROUP BY ____ 
ORDER BY ____
```

それぞれのキーワードについて、以下簡単に記します。これらは、SQL経験者には馴染み深いものだと思います。

- `SELECT`ステートメントには、結果セットとして返されるドキュメントプロパティを指定します
- `FROM`には、ドキュメントを照会するデータベースを指定します
- `WHERE`ステートメントでは、ドキュメントの検索条件を指定します。
- `JOIN`ステートメントは、複数のドキュメントを結合するための条件を指定します。
- `GROUP BY`ステートメントは、結果セットとして返されるアイテムをグループ化するために使用される基準を指定します。
- `ORDER BY`ステートメントは、結果セット内のアイテムの順序付けに使用される基準を指定します。

このように、クエリ形式の構造とセマンティクスは、SQLに基づきます。さらに、SQLには含まれていないJSONデータの操作に関する部分は、Couchbase Serverと同じく、SQL++に基づいています。

### SQL++

SQL++は、カリフォルニア大学で生み出され、Apache AsterixDBでも採用されています。

SQL++の提唱者Yannis Papakonstantinou等による論文「The SQL++ Query Language: Configurable, Unifying and Semi-structured」では、N1QLについて「syntactic sugar over SQL++」という表現が用いられています。

### N1QL

N1QLという名前は、リレーショナルデータモデルにおける第一正規型(first normal form)と関係し、非第一正規型クエリ言語(Non 1st normal form Query Language)の略称であり、ニッケルと発音します。

N1QLは、Couchbase固有の名称ですが、その設計は、SQL++に由来します。



## 定義と実行

`Database`クラスの`createQuery`メソッドを使用して、クエリ文字列から`Query`オブジェクトを作成します。

`Query`オブジェクトの`execute`メソッドを使用してクエリを実行します。

下記コード中の、`db`は、生成・初期化済みの`Database`オブジェクトです。

```JAVA:Java
Query thisQuery = db.createQuery(
  "SELECT META().id AS thisId FROM _ WHERE type = \"hotel\""); 

  return thisQuery.execute().allResults();
```

```Kotlin:Kotlin
val thisQuery = db.createQuery(
      "SELECT META().id AS id FROM _ WHERE type = \"hotel\"") 

return thisQuery.execute().allResults()
```

なお、ここでは、省略表記`_`を使用して現在のデータベースにアクセスしています。

## 構成要素


### SELECT


SELECT句は`SELECT`キーワードで始まります。

- `SELECT ALL ...`は、すべての結果を返します（`ALL`は、デフォルトであり、記載不要）
- `SELECT DISTINCT ...`は、重複した結果を削除します。

上記の`...`の箇所には、JSONドキュメントのプロパティを指定します。`AS`引数を使用して、プロパティにエイリアス名を指定することができます。また、ワイルドカード`*`を使用して、すべての列を指定できます。


### FROM

データソースを指定します。オプションとして、エイリアス（`AS`）を適用することができます。

名前を指定せずに現在のデータベースを使用するには`_`をデータソースとしてを使用します。

以下の例を参照ください。

```SQL
SELECT name FROM db
SELECT store.name FROM db AS store
SELECT store.name FROM db store
SELECT name FROM _
SELECT store.name FROM _ AS store
SELECT store.name FROM _ store
```

### JOIN

指定された基準によってリンクされた複数のデータソースからデータを選択できます。

Couchbase Liteのデータソースはデータベース （ファイル）そのものであり、データベース（ファイル）間の結合はサポートされていないため、JOIN句は、自己結合のために利用されます。

JOIN句は、JOIN演算子で始まり、その後にデータソースが続きます。

次の5つのJOIN演算子がサポートされています：`JOIN`、`LEFT JOIN`、`LEFT OUTER JOIN`、`INNER JOIN`、`CROSS JOIN`

なお、`JOIN`と`INNER JOIN`は同義であり、`LEFT JOIN`と`LEFT OUTER JOIN`は同義です。


結合制約は、`ON`キーワードで始まり、その後に結合制約を定義する式が続きます。

たとえば、次の例を参照してください。
ここでは、航空会社の詳細を、航空会社IDでリンクされたルートの詳細と組み合わせています。

```SQL
SELECT db.prop1, other.prop2 FROM db JOIN db AS other ON db.key = other.key

SELECT db.prop1, other.prop2 FROM db LEFT JOIN db other ON db.key = other.key

SELECT * FROM travel-sample r JOIN travel-sample a ON r.airlineid = a.meta.id WHERE a.country = "France"
```

次の例では、`_airline`ドキュメントとルートドキュメントのドキュメントID（`airlineid`）を使用して、`route`タイプのドキュメントを`airline`タイプのドキュメントと結合しています。

```SQL
SELECT * FROM travel-sample r JOIN travel-sample a ON r.airlineid = a.meta.id WHERE a.country = "France"
```

### WHERE

クエリによって返されるドキュメントの選択基準を指定します。

BOOLEAN値に評価される任意の数の式をチェーンできます。

次の例を参照ください。

```SQL
SELECT name FROM db WHERE department = ‘engineer’ AND group = ‘mobile'
```

### GROUP BY

クエリ結果をグループ化します。

通常、集計関数（`COUNT`、`MAX`、`MIN`、`SUM`、`AVG`など）と一緒に使用されます。
オプションとして、`HAVING`句を使って、集計関数に基づいて結果をフィルタリングできます（例えば、`HAVING count(empnum)>100`のように）。 

次の例を参照ください。

```SQL
SELECT COUNT(empno), city FROM db GROUP BY city

SELECT COUNT(empno), city FROM db GROUP BY city HAVING COUNT(empno) > 100

SELECT COUNT(empno), city FROM db GROUP BY city HAVING COUNT(empno) > 100 WHERE state = ‘CA’
```

### ORDER BY

クエリ結果を並べ替えます。

オプションの`ASC`（昇順）または`DESC`（降順）を使用して並べ替え方向を指定します。デフォルトは`ASC`です。

次の例を参照ください。

```SQL
SELECT name FROM db  ORDER BY name

SELECT name FROM db  ORDER BY name DESC

SELECT name, score FROM db  ORDER BY name ASC, score DESC
```

### LIMIT

クエリによって返される結果の最大数を指定します。

次の例は、10件の結果のみを返します。

```SQL
SELECT name FROM db LIMIT 10 
```



### OFFSET

クエリの結果をスキップする数を指定します。

次の例は、最初の10件の結果を無視して、残りの結果を返します。

```SQL
SELECT name FROM db OFFSET 10 
```

次の例は、最初の10件の結果を無視してから、次の10件の結果を返します。

```SQL
SELECT name FROM db  LIMIT 10 OFFSET 10 
```


#### 関連情報

https://docs.couchbase.com/couchbase-lite/current/android/query-n1ql-mobile.html

https://asterixdb.apache.org/

https://arxiv.org/abs/1405.3631