Couchbase Server N1QLクエリ解説（RDBとの差分の観点から）


### 基本

####　エスケープ文字

ユーザー定義語句（バケット名、フィールド名）は、「`」を使ってエスケープすることによって、予約語との衝突を回避することができます。
Couchbase Serverでは、「-」は、予約語に含まれるため、「-」を含むバケット名を利用する場合、下記のようにバケット名をエスケープする必要があります。

```SQL
SELECT ... FROM `travel-sample`
```

#### 構造

N1QLは、JSONデータを構成する文字列、数値、真偽値を（直接的に、つまり文字列としてエスケープされた形ではなく）含むことができます。以下に具体的な例を見てみます。


##### コンストラクションオペレーター

以下の記号は、コンストラクションオペレーターと呼ばれます。
`{`, `}`, `:`, `[`, `]`


##### ネステッドオペレーター・エクスプレッション

以下の表現は、ネステッドオペレーター・エクスプレッションと呼ばれrます。

- ドットノーテーション （`.`）
- 配列表現（インデックス、スライス） 

#### ドキュメントキー



##### `USE KEYS`

```SQL
SELECT *
FROM `travel-sample`
USE KEYS "airport_1254";
```

```SQL
SELECT *
FROM `travel-sample`
USE KEYS ["airport_1254","airport_1255"];
```

`USE KEYS`　のみを利用してクエリを行う場合、インデックスを作成する必要がありません。

注意: Couchbase Serverでは、ドキュメントキーが判明している場合には、ドキュメントキーを指定してドキュメントを取り出すことが、（RDBにおける、主キーでの検索のように）最も効果的であることは間違いありません。一方でドキュメントキーが判明しているケースにおいては、性能を追求する場合、必ずしもクエリ用いて(Queryサービスを介して）ドキュメントを取り出す必要はなく、Dataサービスに対して直接リクエストすることで性能を最適化することが可能です（特に上記の例のような単純な利用の場合。一方、サブクエリを伴うような複雑なクエリの場合など、`USE KEYS`句を効果的に利用できる場面があります）。

##### `META().id`

上記の`USE KEYS`　を利用したクエリは、下記のように`META().id` を使って書き直すことができます。

```SQL
SELECT *
FROM `travel-sample`
WHERE META().id IN ["airport_1254","airport_1255"];
```

`META().id` を利用してクエリを行う場合、`META().id`を用いたインデックスが作成されている必要があります。

`USE KEYS`の構文では、後続する鉤括弧の中のドキュメントキーは完全一致が想定されていますが、`META().id` を利用する場合、N1QLクエリの一部として、`LIKE`　と組み合わせることができ部分一致検索を行うことができる他、比較演算子による大小比較など、クエリの名で用いる際により自由度が高い表現と言えます。

また、検索条件としてのみではなく、`SELECT META().id FROM ... WHERE ...`のように、条件に一致するドキュメントキーを検索するために用いることもできます。  

上記のクエリ中の`META().id` の部分は、下記のように、バケット名を含んだ表現を用いることができます。このクエリのように、クエリの中でキースペース（バケット名）が一意に決まっている場合、指定は必須ではありません。複数のキースペースが混在している中で、ドキュメントのキーを用いる（取り出す）場合には、キースペースを指定した表現を用います。

```SQL
SELECT *
FROM `travel-sample`
WHERE META(`travel-sample`).id IN ["airport_1254","airport_1255"];
```

### N1QLキーワード

##### フィールドの操作

`RAW` | `ELEMENT` | `VALUE`

これらは全て同義語です（以下では、`RAW` を用います）。

N1QLで検索された結果は、JSONデータとして、フィールド名と値のペアとなることが基本ですが、`RAW`キーワードを`SELECT`句と共に用いることで、JSONデータの値のみを取り出すことができます。



##### `RAW`を用いない例

クエリ

```SQL
SELECT city
FROM `travel-sample`
WHERE type = "airport"
ORDER BY city
LIMIT 5;
```

結果

```JSON
[
  {  "city": "Abbeville"  },
  {  "city": "Aberdeen"  },
  {  "city": "Aberdeen"  },
  {  "city": "Aberdeen"  },
  {  "city": "Abilene"  }
]
```

##### `RAW` を用いた例

クエリ

```SQL
SELECT RAW city
FROM `travel-sample`
WHERE type = "airport"
ORDER BY city
LIMIT 5;
```

結果

```JSON
[
  "Abbeville",
  "Aberdeen",
  "Aberdeen",
  "Aberdeen",
  "Abilene„
]
```

`DISTINCT`　と組み合わせた利用例

```SQL
SELECT DISTINCT RAW city
FROM `travel-sample`
WHERE type="airport"
ORDER BY city LIMIT 5;
```

```JSON
[
  "Abbeville",
  "Aberdeen",
  "Abilene",
  "Adak Island",
  "Addison"
]

```





#### 配列

コレクション形データを操作するための、コレクションオペレーターとして以下があります。

`ANY`, `EVERY`, `ARRAY`, `FIRST`, `EXISTS`, `IN`, `WITHIN`

#### 選択項目としての配列利用

`ARRAY`　または`FIRST`から始まり、`END`で基本単位となります。
`IN`　または`WITHIN`と`FOR`により、コレクションの要素へのループを表現しています。

```SQL
( ARRAY | FIRST ) var1 FOR var1 ( IN | WITHIN ) expr1　END
```

`WHEN`　句により、条件指定を付け加えることが可能です。

```SQL
( ARRAY | FIRST ) var1 FOR var1 ( IN | WITHIN ) expr1　[ ( WHEN cond1 [ AND cond2 ] ) ] END
```

下記の通り、`,`により、上記説明した内容を複数並べて用いることができます。

```SQL
( ARRAY | FIRST ) var1 FOR var1 ( IN | WITHIN ) expr1　
   [ ,  var2 ( IN | WITHIN ) expr2 ]*
   [ ( WHEN cond1 [ AND cond2 ] ) ] END
```



#### 検索条件としての配列利用

`ANY`　または`EVERY`から始まり、`END`までで基本単位となります。`SATISFIES`で条件を指定します。

```SQL
( ANY | EVERY ) var1 ( IN | WITHIN ) expr1
   [ , var2 ( IN | WITHIN ) expr2 ]*
   SATISFIES condition  END
```

注意：　 この構文は、配列から、真となった要素を取り出しているのではないことに注意してください。これは、SQLのWHERE条件が検索に使ったカラムを取り出すものではないのと同様です。

##### `ANY`

配列に条件で指定した要素が一つでも含まれる場合、真となります。

```SQL
SELECT *
FROM purchases
WHERE ANY item IN purchases.lineItems SATISFIES
item.count >= 5 END
```

##### `EVERY`

配列の全ての要素が、指定した条件に一致する場合、真となります。

```SQL
SELECT *
FROM purchases
WHERE EVERY item IN purchases.lineItems SATISFIES
item.count >= 5 END
```

##### `IN`または`WITHIN`

`IN`　句が、指定された配列のトップレベルの要素を検索するのに対して、`WITHIN`句は、現在の配列とその子、および子孫を含めて検索対象とします。

#### ドキュメントキーによる結合　:　`NEST`

`NEST`は、外部の子ドキュメントを親の下に埋め込む特別なタイプの`JOIN`です。
ドキュメント・キーを介した参照関係でデータをモデル化した際に用いる事ができます。`ON KEYS`により、対象とするドキュメントのキーを指定することができます。


```JSON:ドキュメントキー：o1
{"order_id":1234,
      "type":"Order",
      "customer_id":"34567",
      "total_price":"65.5",
      "lineitems":["o11","o12","o13"]
}
```


```JSON:ドキュメントキー：ol1
{"lineitem_id":o11,
      "type":"lineitem",
      "item_id":"789",
      "qty":"3",
      "itemprice":"5.99",
      "base_price":"17.97",
      "tax":"0.75",
      "total_price":"18.22"
}
```

```JSON:ドキュメントキー：ol2
{"lineitem_id":o12,
      "type":"lineitem",
      "item_id":"234",
      "qty":"5",
      "itemprice":"10.00",
      "base_price":"50.00",
      "tax":"0.75",
      "total_price":"50.75"
}

```
```SQL
SELECT ordr.order_id, 
ARRAY {“item_id”: l.item_id, “quantity”:l.qty} FOR l IN line END as items
FROM `retailsample` ordr
NEST `retailsample` line
ON KEYS ordr.lineitems 
```

結果は、下記のように、一つのドキュメントになります。

```JSON
[
  {
    "items":[
      {"item_id":"789", "qty":"3"},
      {"item_id":"234", "qty":"5"}
      ];
    "order_id":"1234"
  },
  {
    "items":[
      {"item_id":"899", "qty":"8"},
      {"item_id":"651", "qty":"2"}
      ];
    "order_id":"9812"
  },
]

```
#### サブドキュメントのフラット化: `UNNEST`

`UNNEST`は、ネストされたオブジェクトを最上位ドキュメントとして表示するために用います。

下記のようなネストされた構造を持つデータを想定します（受注明細データを中に含む受注伝票データ）。

```JSON
{
  "ordId": "ORDER-0001",
  "status": "Shipped",
  "items": [
     {
     "prodId": "AAA-222",
     "qty": 1
     },
    {
     "prodId": "BBB-333",
     "qty": 2  
    },
    {
     "prodId": "CCC-444",
     "qty": 3 
    }
  ]
}
```

`UNNEST`は、このようなネストされた（サブドキュメントを含む）ドキュメントへのクエリの結果を下記のようなテーブル構造として利用したい時に使われます。

|ordId|Status|prodId|qty|
|---|---|---|---|
|ORDER-0001|Shipped|AAA-222|1|
|ORDER-0001|Shipped|BBB-333|2|
|ORDER-0001|Shipped|CCC-444|3|


クエリでは、`UNNEST`　をサブドキュメントに対して指定し、`as` で別名をつけたものを`SELECT` 句の中で使用します。

```SQL
SELECT ord.ordId, ord.status, item.* FROM orders ord UNNEST items as item
```


下記のようなフラットな構造のJSONが取り出されます。

```JSON
[
{ “ordId”: “ORDER-0001”, “status”: “Shipped”, “prodId”: “AAA-222”, “qty”: 1 },
{ “ordId”: “ORDER-0001”, “status”: “Shipped”, “prodId”: “BBB-333”, “qty”: 2 },
{ “ordId”: “ORDER-0001”, “status”: “Shipped”, “iprodId”: “CCC-444”, “qty”: 3 }
]
```

(JSON中の要素の出現順はこの通りではありません)

情報: JSONオブジェクトのフィールドは並び順に意味を持ちません。




### SQLと比べた場合の制約

- `FULL [OUTER] JOIN`  はサポートされていません
- `CROSS JOIN` はサポートされていません
- `RIGHT [OUTER] JOIN` は、JOIN連結の最初の (または唯一の) クエリである必要があります



### 参考情報：コミュニティエディションとエンタープライズエディションの違い

クエリに関連した、コミュニティエディションには含まれない、エンタープライズエディションの機能として、以下があります。


###### 機能
		
- ウィンドウ関数
- FLEXインデックス（クエリからの全文検索インデックス利用）

###### 性能・最適化

- コストベースオプティマイザ（CBO）	
- 無制限のクエリ同時実行			
- N1QLアグリゲートプッシュダウン

##### マネジメント

- N1QLリクエストの監査			
- クエリのモニタリング

#### 参考資料

[チートシート](https://docs.couchbase.com/files/Couchbase-N1QL-CheatSheet.pdf)