Databricks Auto Loader にて CSV ファイルの 1 行目をスキップする方法

## 概要

Databricks Auto Loader にて CSV ファイルの 1 行目をスキップする方法を共有します。`!data`のような謎の文字が 1 行目にあり、2 行目から実際の CSV 形式のデータがはじまるファイルをインタフェースしたことがありました。本記事では、そういったファイルを Databricks Auto Loader にて取り込む方法を紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/468a55bd-8546-707c-00a7-12f4667bbf77.png)

Databricks Auto Loader の CSV オプションにおける`skipRows`を指定することで、`!data`の行をスキップできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/34668ebd-d94d-287b-4f52-143c20818dfb.png)

引用元：[Auto Loaderのオプション | Databricks on AWS](https://docs.databricks.com/ja/ingestion/cloud-object-storage/auto-loader/options.html)

## 事前準備

### カタログ、スキーマ、テーブル、及び、 Volume の作成


```sql
%sql
CREATE CATALOG IF NOT EXISTS manabian_test;
```

```python
%sql
CREATE SCHEMA IF NOT EXISTS manabian_test.auto_loader_skip;
CREATE OR REPLACE TABLE manabian_test.auto_loader_skip.table_01
(
  id STRING,
  string_col STRING,
  _rescued_data STRING
);
CREATE VOLUME IF NOT EXISTS manabian_test.auto_loader_skip.volume_01;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/36dcc79b-da3b-2aa2-3e62-8c615a177e74.png)

### 変数を定義

```python
catalog_name = "manabian_test"
schema_name = "auto_loader_skip"
table_name = "table_01"
volume_name = "volume_01"

table_full_name = f"{catalog_name}.{schema_name}.{table_name}"
src_dir = f"/Volumes/{catalog_name}/{schema_name}/{volume_name}/src"
csv_path = f"{src_dir}/src.csv"
checkpoint_dir = f"/Volumes/{catalog_name}/{schema_name}/{volume_name}/checkpoint"
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/f95a749b-b407-bba4-1222-1aaa232af03a.png)

### CSV ファイルを配置

```python
# CSV ファイルを配置
data = """
!data
id,string_col
1,"aaa"
2,"bbb"
3,"ccc"
""".strip()
_=dbutils.fs.put(csv_path, data, True)
```

```python
# CSV ファイルの内容を確認
print(dbutils.fs.head(csv_path))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/5f4e8fc3-be63-a6ee-cf8d-b42e731f0840.png)


## 検証用コードとその結果

### 1. `skipRow`を指定しない場合の動作確認

`!data`をカラム名として参照しており想定外の動作となっていることを確認。

```python
# チェックポイントの初期化
dbutils.fs.rm(checkpoint_dir, True)

# Databricks Auto Loader により読み込む
src_df_01 = (
    spark.readStream.format("cloudFiles")
    .option("cloudFiles.format", "csv")
    .option("cloudFiles.schemaLocation", checkpoint_dir)
    .load(src_dir)
)

# Schema を表示
src_df_01.printSchema()
```

```text
root
 |-- !data: string (nullable = true)
 |-- _rescued_data: string (nullable = true)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/8a2e0787-4164-fb84-353f-071f0f24019c.png)


### 2. `skipRow`を指定した場合の動作確認

データフレームのカラムが想定通りとなっていることを確認。


```python
# チェックポイントの初期化
dbutils.fs.rm(checkpoint_dir, True)

# Databricks Auto Loader により読み込む
src_df_02 = (
    spark.readStream.format("cloudFiles")
    .option("cloudFiles.format", "csv")
    .option("skipRows", 1)
    .option("cloudFiles.schemaLocation", checkpoint_dir)
    .load(src_dir)
)

# Schema を表示
src_df_02.printSchema()
```

```text
root
 |-- id: string (nullable = true)
 |-- string_col: string (nullable = true)
 |-- _rescued_data: string (nullable = true)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/bddac5ee-108b-bbbc-36a3-2a32e72aa5f7.png)

テーブルに対して書き込み、想定通りにテーブルに書き込まれていることを確認。

```python
# データの書き込み
(
    src_df_02.writeStream
    .option("mergeSchema", True)
    .option("checkpointLocation", checkpoint_dir)
    .trigger(availableNow=True)
    .toTable(table_full_name)
)
```

```python
spark.table(table_full_name).display()
````

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/13f8949c-8995-eb6b-0b13-352fa4f35265.png)

## 事後処理

### カタログの削除

```python
%sql
DROP CATALOG IF EXISTS manabian_test CASCADE;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/b2762c37-987c-0d90-69b0-b20aa7d2edc2.png)