特徴量エンジンKaskadaの宣言型クエリ言語 Fenl~エンティティ

# はじめに

先日、以下の記事を発表しました。

https://qiita.com/yoshiyuki_kono/items/7dc7bdad5e02ff986f5d

新しいテクノロジーに触れる時はいつでも楽しいものです。

まだまだ勉強中ですが、公式ドキュメントの学習過程の記録として、以下の記事をまとめてみました。

本稿情報のソースとして、下記ドキュメントを参照ください。

https://kaskada.io/docs-site/kaskada/main/fenl/entities.html

# エンティティ
エンティティは、Kaskada が特徴エンジニアリングで使用するためにデータを編成する方法です。エンティティは、Kaskadaシステム(実行環境)内部での特定の種類のオブジェクトの表現だと言えます。

## エンティティとは何か?

 - エンティティは、Kaskadaシステム内の「（普通※）**名詞**」を表します（※個々のエンティティの実体を「固有名詞」を持つもの、と捉えることができます。）
 - 一般な表現を用いると、エンティティは、システムに取り込まれたデータセットから識別できるオブジェクトの任意の**カテゴリ**と考えることができます。
 - エンティティの一般的な例は、「ユーザー」または「ベンダー」です。
 - 何かに名前またはその他の**一意の識別子**を与えることができる場合、それはおそらくエンティティとして表現できます(リレーショナル データベースでは、エンティティとは、テーブルのセット内の同じキーによって識別されるものです)
 - Kaskadaの「テーブル」について、すでに認識されていれば、エンティティは、テーブルと対応していると捉えることができます。

## エンティティキーとは何か?
エンティティはものの種類のカテゴリを表しますが、「エンティティ キー」はそのカテゴリ内の特定のアイテムを表します。以下は、エンティティの例と特定のエンティティ インスタンスを示した表です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/24731e95-3ca2-b7cf-a90e-36988558a21c.png)


## どのようにエンティティを使うのか?

エンティティが Fenl式の中で、どのように扱われるかを示すために、2 つのテーブルで構成される単純化されたデータセットから始めます。

この`Purchase`表は購入トランザクションを説明しています。

```javascript
{ customer_id: string, time: datetime, product_id: string, amount: number }
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/90129fbb-a96a-ac43-2b66-67ccb309cc79.png)


この`ProductReview`表は、購入した製品に対する顧客の評価を示しています

```javascript
{ customer_id: string, time: datetime, product_id: string, stars: number }
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/86038fed-3b48-038e-8f4d-7c610a973627.png)


## エンティティごとの集計
すべての集計関数 (すなわち`sum`、`count`など) は、集計対象式のエンティティにスコープされます。

たとえば、以下の例では、「購入」データから顧客ごとの結果（購入のカウント/数）が生成されます。

```javascript
Purchase | count()
```


## クロステーブル操作

2 つのテーブルが同じエンティティについてのデータを保持している場合、結合条件を指定することなく結合できます。
エンティティキーは暗黙的な結合キーとして機能します。

たとえば、ここで、`Purchase`と `ProductReview`テーブルの両方のエンティティは「顧客」です。そのため、定型的な結合コードを使用せずに、各テーブルの集計を結合できます。

```javascript
{
  p_count: Purchase | count(),
  c_avg_rating: ProductReview.stars | mean(),
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/9f99165c-66d4-6eea-f499-5cba6992ee2c.png)


## エンティティの変更

一部の値は複数のエンティティに関連します。たとえば、 `ProductReview`は 製品をレビューした「顧客」とレビューされた「製品」の両方の観点から捉えることができます。このような場合には、式が暗黙に前提するエンティティを、新しいエンティティキーを指定することで変更できます。

```javascript
ProductReview | with_key(ProductReview.product_id)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/16d870f9-385a-765f-93fd-fdf163a35719.png)



式のエンティティを変更しても、式によって生成される値には影響しません。変更は、結果がエンティティ キーに依存する操作 (集計など) で使用される場合にのみ表れます。

```javascript
ProductReview
  | with_key(ProductReview.product_id)
  | mean()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/e5419d10-31ce-3d34-0c9d-b1d2d8944f60.png)




## さまざまなエンティティとの連携
多くの場合、異なるエンティティに関連付けられた値を組み合わせる必要があります。これは、特定のキーで式の値を検索することで実現できます。

`lookup` 関数は 2 つの引数を取ります。最初の引数 (キー式) は検索されるエンティティ キーを記述し、2 番目の引数 (外部式) は検索される値を記述します。

```javascript
let avg_review_by_product = ProductReview
  | with_key(ProductReview.product_id)
  | mean()

in {
  p_count: Purchase | count(),
  c_avg_rating: ProductReview.stars | mean(),
  p_avg_rating: avg_review_by_product | lookup($input, Purchase.product_id)
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/854b31ef-da2c-0ece-8ca4-8c53611871b5.png)



`lookup` 式は、キー式が `null` 以外の値を生成するたびに、外部式の値を生成します。

### Note:タイムトラベル
他のすべての Fenl 式と同様に、ルックアップは「temporal」なものです。これは、検索式によって生成される値が、生成時に検索される値を正確に反映していることを意味します。Kaskada では、現実世界と同様に、情報が時間を逆行することはできません。

## クエリ結果内のエンティティ
すべての Fenl 式はエンティティに関連付けられ、すべての Fenl 値はエンティティ キーに関連付けられます。

Fenl クエリは、クエリ式によって生成されたすべての非 null 値を返します。エンティティがテーブル内に存在しても、特定のクエリに対して値を生成しない場合があります。

```javascript
let total = Purchase.amount | sum()
in { total: total | if(total >= 0) }
```

NULL値はクエリ結果から省略されるため、この式では合計が負のエンティティに対してゼロ行が生成される可能性があります。 NULL値をキャプチャするには、条件をレコード内に移動します。そうすると、値はNULLになりますが、それを囲むレコードはNULLになりません。

```javascript
let total = Purchase.amount | sum()
in { total: total | if(total >= 0) }
```