CQL(Cassandra Query Language)できること、できないこと

#TL;DR
- CQLは一見SQLと同じ構文だが意外と実現できないことが多い
- 制限をしっかり理解した上でCassandraを活用しよう

#対象読者
- Cassandra導入を検討している方

#はじめに
- Cassandraへの問合せ言語であるCQLはSQLに似た文法を持っていますが、SQLでは当たり前に可能な問い合わせができないことに注意が必要です。安易にRDBMSからの移行ができると錯覚すると大変苦労する羽目になるでしょう。
- 本稿では主にSQLとの違いにフォーカスしCQLのできること、できないことをまとめています。Cassandra導入を検討している方、機能検証を行っている方の一助となれば幸いです。

#環境
- 本稿は下記環境での検証に基づいて記載しています。
    - Cassandra 3.11.0.1855
    - CQL spec 3.4.4
    - cqlsh 5.0.1

#前提
### サンプルテーブル
- 以降の説明で記載する条件句記述例は、下記のテーブル定義を前提とします。

|カラム名|データ型|属性|
|---|---|---|
|PK|INT|Partition Key|
|CK1|INT|Clustering Column|
|CK2|INT|Clustering Column|
|MAP1|MAP<TEXT,TEXT>|Entry Index付与|
|MAP2|MAP<TEXT,TEXT>||
|VALUE1|TEXT|Secondary Index付与|
|VALUE2|TEXT||

#Select
### ALLOW FILTERING句について
- 複数のパーティションをまたぐ検索を実行するために必要となるオプションです。全行スキャンが走り大きく性能が劣化するリスクを伴います。**ALLOW FILTERING句は実運用的には利用不可**くらいの感覚で受け止めてください。
- またALLOW FILTERING不要なクエリが高速であるとは必ずしも限りません。本稿はCassandraの抽出クエリのリファレンシャルな情報を提供することを目的とするため詳細は割愛しますが、Cassandraの性能を万全に活かすためには内部構造を理解し、ノード間の物理的なI/Oを最小化するようなクエリ設計が必要となるでしょう。

## できないこと
- まずはできないことをおさえておきましょう。CQLでは以下の構文は利用不可となっています。
    - 条件句
        - != （非等価）
        - NOT （論理否定）
        - OR （論理和）
        - IS NULL/ IS NOT NULL （NULL比較）
    - JOIN （結合）
    - OFFSET （相対位置指定）

## できること
### = （等価）
- 原則どのカラムに対しても設定可能です。ただしPKを指定しない場合やINDEXが付与されていない列に対する等価条件にはALLOW FILTERING句を付与する必要がある点に注意が必要です。

|条件句記述例|ALLOW FILTERING|備考|
|---|---|---|
|where PK=1|不要|
|where CK1=1|要|CK単独での条件のためALLOW FILTERINGが必要|
|where PK=1 AND CK1=1|不要|PKが指定されているためALLOW FILTERINGは不要|
|where CK2=1|要|
|where PK=1 and CK2=1|要|
|where PK=1 and CK1=1 and CK2=1|不要|複合CKの後方カラムを条件とする際にはPK,CK1をあわせて条件とすることでALLOW FILTERINGの付与を回避できる|
|MAP1['key']='V'|不要|Entry Indexが付与されている列は単独カラムでの条件であってもALLOW FILTERINGが不要|
|MAP1['key']='V' and MAP1['keyX']='X'|要|Entry Indexが付与されていても条件を重ねるとALLOW FILTERINGが必要|
|MAP2['key']='V'|要|
|VALUE1='VAL'|不要|Secondary Indexが付与されている列は単独カラムの条件であってもALLOW FILTERINGは不要|
|VALUE2='VAL'|要|

### >,<,>=,<= （比較）
- 等価とほぼ同様の操作が可能です。ただしCollection型に対する比較条件はサポートされていません。またCK列以外の比較条件にはALLOW FILTERINGオプションの付与が必要となります。
- 概してCassandraは範囲検索を苦手としています。範囲取得が必要な際にはCK設計に留意してください。

|条件句記述例|ALLOW FILTERING|備考|
|---|---|---|
|where PK>1|要||
|where CK1>1|要||
|where PK>1 and CK1>1|要||
|where PK=1 and CK1>1|不要|PKを指定した上でのCKの比較条件はALLOW FILTERINGが不要|
|where CK2>1|要||
|where PK=1 and CK2>1|要||
|where PK=1 and CK1=1 and CK2>1|不要|複合CKの後方カラムではPK,CK1が特定された場合のみALLOW FILTERINGなしで比較条件が可能となる|
|where VALUE1>'VAL'|要||
|where PK=1 and VALUE1>'VAL'|要||
|where PK=1 and CK1=1 and VALUE1>'V'|要||
|where PK=1 and CK1=1 and CK2=1 and VALUE1>'V'|要||

### in （包含）
- in句はPK,CK列に対してのみ利用可能です。複合CKの場合にはさらに利用できる条件が厳しくなります。
- 実行できないケースが若干わかりにくいため、下記テーブルには実行可否列を設け実行できないことを明示しています

|条件句記述例|実行可否|ALLOW FILTERING|備考|
|---|---|---|---|
|where PK in (1,2,3)|○|不要||
|where CK1 in (1,2,3)|○|要||
|where PK=1 and CK1 in (1,2,3)|○|不要||
|where PK in (1,2,3) and CK1 in (1,2,3)|○|不要||
|where CK2 in (1,2,3)|×|n/a||
|where PK=1 and CK2 in (1,2,3)|×|n/a|複合CKの後方カラムでin句を利用する際にはPK、および複合CK前方カラムの条件指定が必要となる|
|where PK=1 and CK1=1 and CK2 in (1,2,3)|○|不要||
|where PK in (1,2,3) and CK1 in (1,2,3) and CK2 in (1,2,3)|○|不要||

### like （パターン一致）
- CUSTOM INDEXを付与した列に対してのみ可能です。PK、CKにはCUSTOM INDEXを付与できないことに注意が必要です。
- CUSTOM INDEXをカラムVALUE2に付与する場合の例を以下に示します。

```
CREATE CUSTOM INDEX SAMPLE_IDX ON SAMPLE (VALUE2) USING 'org.apache.cassandra.index.sasi.SASIIndex' WITH OPTIONS = {'analyzer class': 'org.apache.cassandra.index.sasi.analyzer.NonTokenizingAnalyzer', 'case_sensitive': 'false', 'mode': 'CONTAINS', 'analyzed': 'true'};
```

|条件句記述例|ALLOW FILTERING|備考|
|---|---|---|---|
|where VALUE4 like '%VAL%' |不要||

### order by （ソート）
- 非常に限られた列に対してのみソート指定が可能です。PKが特定できること、かつCK（複合CKの場合は最前方カラム）であることがソート指定可能な列の条件となります。

|条件句記述例|実行可否|備考|
|---|---|---|---|
|order by PK|×|||
|order by CK1|×|
|where PK=1 order by CK1|○||
|where PK in (1,2,3) order by CK1|○|cqlshから実行の際にはpaging off;が必要です|
|order by CK2|×|||
|where PK=1 order by CK2|×|||
|where PK=1 and CK1=1 order by CK2|×|||

### group by （グループ化）
- CQLではPK列でのグループ化、PKを指定した上でのCK列でのグループ化をサポートしてます。
- また、標準集計関数として、min, max, avg, sum, countが使用可能です。

|条件句記述例|実行可否|備考|
|---|---|---|---|
|group by PK|○|||
|group by CK1|×|
|where PK=1 group by CK1|○||
|group by CK2|×|
|where PK=1 group by CK2|×||
|where PK=1 and CK1=1 group by CK2|×||

### limit （制限）
- limit句による取得件数を指定した問い合わせが可能です。

# おわりに
以上CQLのできること、できないことを間単にまとめましたが何やらバットノウハウの雰囲気が漂っています・・・
CassandraではPK、CK以外の列に対するイコール検索は避けることが常道だと思われますが、とはいえ実業務ではやむを得ずPK、CK列以外での検索が必要となる場面もあるでしょう。
クエリ表現の制限、ALLOW FILTERING利用のリスクを理解した上で、求められる要件（性能・拡張性・可用性）とうまく折り合いを付けることを心がけてください。