Databricks ( Spark ) にて利用できる文字型である char 型と varchar 型 の検証

## 概要
Databricks ( Spark ) にて利用できる文字型である `char` 型 と `varchar` 型 の検証した結果を共有します。

Spark では、文字型として利用できるデータ型として、`string`型だけでなく、`char`型と `varchar`型があります。`char`型と `varchar`型では、`string`型とは異なり、文字の長さを指定できます。ただし、バイト数を指定する方法はなく、文字数として指定する必要があるようです。

> - String type
    - StringType: Represents character string values.
    - VarcharType(length): A variant of StringType which has a length limitation. Data writing will fail if the input string exceeds the length limitation. Note: this type can only be used in table schema, not functions/operators.
    - CharType(length): A variant of VarcharType(length) which is fixed length. Reading column of type CharType(n) always returns string values of length n. Char type column comparison will pad the short one to the longer length.

引用元：[Data Types - Spark 3.3.1 Documentation (apache.org)](https://spark.apache.org/docs/latest/sql-ref-datatypes.html)


> - 文字列型
    - StringType: 文字列値を表します。
    - VarcharType(length): 長さに制限がある StringType のバリアント。入力文字列が長さ制限を超えると、データの書き込みに失敗します。注: このタイプは、関数/演算子ではなく、テーブル スキーマでのみ使用できます。
    - CharType(length): 固定長の VarcharType(length) のバリアント。 CharType(n) 型の列を読み取ると、常に長さ n の文字列値が返されます。 Char タイプの列の比較では、短い方が長い方にパディングされます。

上記の翻訳

Databricks のドキュメント上では`char`型と `varchar`型の記載を確認できませんでしたが、動作はしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/21b36a76-e4c8-9999-706c-2bec812bb1ba.png)

引用元：[データ型 (Databricks SQL) - Azure Databricks - Databricks SQL | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/databricks/sql/language-manual/sql-ref-datatypes#data-type-classification)

`char`型で文字の長さを指定する際には、バイト数を指定する方法ではなく、文字数として指定する方法しなかないことに注意が必要です。オラクルデータベースではバイト単位で指定することが明示されており、そのような仕様をもつ RDB から移行する場合には想定外の仕様となってしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/2747c73b-8f3d-157a-ee26-e7947ed3d9da.png)

引用元：[Oracleデータ型](https://docs.oracle.com/cd/E57425_01/121/ZZPRE/GUID-9F34D892-C67C-4567-94C6-E402BB079FA8.htm)

`char`型へのデータ挿入時には、自動でスペース埋めされないような動作をしており、設定した桁数に満たない場合に想定外の動作をしました。下記例では、`char_col`を`char(4)`として定義しており、4文字に満たない`a`の値を挿入しております。`char_col`列にて、`a`、及び、`a   `でフィルター条件としたクエリを発行した際に、データを取得することができませんでした。`char`型を利用する際には動作を検証してから利用することをおすすめします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/894c574e-f748-c8f6-dcf2-129141df504b.png)


## 動作検証

### 1. データベースを作成

```python
%sql
DROP DATABASE IF EXISTS _qiita CASCADE;
CREATE DATABASE _qiita;
```

### 2. テーブル作成とテーブル定義を確認

テーブル定義を確認する場合には `char` 型と`varchar` 型になっていますが、Spark データフレームとして読むこむと`string`型となりました。

```python
%sql
CREATE OR REPLACE TABLE _qiita.tbl_001
(
  string_col string,
  char_col char(4),
  varchar_col varchar(4)
)
;
```

```python
%sql
DESC EXTENDED _qiita.tbl_001
```

```python
tbl_name = '_qiita.tbl_001'
spark.table(tbl_name).printSchema()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/9781bdc9-ba29-fa12-8644-4f0bd15846ab.png)


### 3. データを挿入後にデータ取得

 `char` 型のカラムでは、`a`と`a   `のフィルター条件でデータ抽出ができました。

```python
%sql
INSERT INTO _qiita.tbl_001
SELECT
  'a'
  ,'a   '
  ,'a'
```

```python
print(spark.table('_qiita.tbl_001').toPandas().to_dict())
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/2b4777e1-731b-e12e-5946-7202728f6224.png)


```python
%sql
SELECT
  *
  FROM
    _qiita.tbl_001
  WHERE 
    char_col = 'a'
```

```python
%sql
SELECT
  *
  FROM
    _qiita.tbl_001
  WHERE 
    char_col = 'a   '
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/f7488c66-3b73-29c0-3a1a-975a3226ffcf.png)


```python
%sql
SELECT
  *
  FROM
    _qiita.tbl_001
  WHERE 
    varchar_col = 'a'
```

```python
%sql
SELECT
  *
  FROM
    _qiita.tbl_001
  WHERE 
    varchar_col = 'a   '
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/8fa49a60-d741-ddd3-e3ff-94f4c247478c.png)

### 4. 日本語の文字を挿入

日本語の文字を、`char(1)`のカラムに挿入できることを確認

```python
%sql
CREATE OR REPLACE TABLE _qiita.tbl_002
(
  string_col string,
  char_col char(1),
  varchar_col varchar(1)
)
USING delta
;
```

```python
%sql
INSERT INTO _qiita.tbl_002
SELECT
  'あ'
  ,'あ'
  ,'あ'
 
```

```python
%sql
SELECT
  *
  FROM 
    _qiita.tbl_002
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/69a7a779-63ac-3443-535e-7be075d152ff.png)


### 4. 指定桁数以上の文字を挿入してエラーであることを確認

SQL でデータ挿入する場合だけでなく、Spark データフレームで書き込む場合にも、想定通りエラーとなりました。

> com.databricks.sql.transaction.tahoe.schema.DeltaInvariantViolationException: Exceeds char/varchar type length limitation. Failed check: (isnull('char_col) OR (length('char_col) <= 1)).

```python
%sql
CREATE OR REPLACE TABLE _qiita.tbl_003
(
  string_col string,
  char_col char(4),
  varchar_col varchar(4)
)
USING delta
;
```

```python
%sql
INSERT INTO _qiita.tbl_003
SELECT
  'a'
  ,'a     '
  ,'a'
```

```python
%sql
INSERT INTO _qiita.tbl_003
SELECT
  'a'
  ,'a'
  ,'a     '
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/6d6fb079-0f2e-8ca7-df21-7975a0df8494.png)

```python
tbl_name = "`_qiita`.`tbl_002`"
tbl_location = (
    spark.sql(f"DESC EXTENDED {tbl_name}")
    .where("col_name = 'Location'")
    .select("data_type")
    .first()[0]
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/29deb068-5bef-f658-cf24-678b6a9f6d2d.png)


```python
data = [
    {
        "string_col": "a",
        "char_col": "あいうえお",
        "varchar_col": "あいうえ",
    }
]
 
df = spark.createDataFrame(data)
```

```python
df.write.mode('append').saveAsTable(tbl_name)
```

```python
df.write.mode('append').save(tbl_location)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/86e8ff40-322f-e0be-2839-497aac1ca621.png)

```python
data = [
    {
        "string_col": "a",
        "char_col": "あいうえ",
        "varchar_col": "あいうえお",
    }
]
 
df = spark.createDataFrame(data)
```

```python
df.write.mode('append').saveAsTable(tbl_name)
```

```python
df.write.mode('append').save(tbl_location)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/7825ff66-59be-a2c4-f9a4-babe8b1c1787.png)


### 5. `char`型のカラムに指定桁数に満たない値を挿入したときの動作を確認

指定桁数に満たない`a`の値を挿入後、`a`と`a   `のフィルター条件でデータ抽出ができないという想定外の動作をしました。フィルター条件をカラムの桁数より多い`a    `の値や`a            `の値をフィルター条件にした場合には想定通りの抽出が行えました。桁数が多い場合には、*Char type column comparison will pad the short one to the longer length.* というドキュメント上の記載の動作をしているようです。


```python
%sql
CREATE OR REPLACE TABLE _qiita.tbl_003
(
  string_col string,
  char_col char(4),
  varchar_col varchar(4)
)
;
INSERT INTO _qiita.tbl_003
SELECT
  'a'
  ,'a'
  ,'a   '
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/34a0ed27-f46e-2746-e0be-ca6fb7166b37.png)

```python
%sql
SELECT
  *
  FROM
    _qiita.tbl_003
  WHERE 
    char_col = 'a'
```

```python
%sql
SELECT
  *
  FROM
    _qiita.tbl_003
  WHERE 
    char_col = 'a   '
``

```python
%sql
SELECT
  *
  FROM
    _qiita.tbl_003
  WHERE 
    char_col = 'a    '
```

```python
%sql
SELECT
  *
  FROM
    _qiita.tbl_003
  WHERE 
    char_col = 'a            '
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/fcec4822-599a-0d5b-c0d3-13f220d5a2fe.png)

```python
%sql
SELECT
  *
  FROM
    _qiita.tbl_003
  WHERE 
    varchar_col = 'a'
```

```python
%sql
SELECT
  *
  FROM
    _qiita.tbl_003
  WHERE 
    varchar_col = 'a   '
```

```python
%sql
SELECT
  *
  FROM
    _qiita.tbl_003
  WHERE 
    varchar_col = 'a           '
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/f872982a-33f6-e340-29be-4ee3f93c0fc8.png)

### 6.  本手順で作成したリソースを削除

```python
%sql
DROP DATABASE IF EXISTS _qiita CASCADE;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/7e608268-684a-3817-afa0-cdc3b9eb705b.png)