SQL Server で BULK INSERT をつかってcsvなどのデータを取込む方法

# BULK INSERT とは
csvなどのデータを、テーブルにインポートできるコマンドです。

[BULK INSERT (Transact-SQL) - SQL Server | Microsoft Docs](https://docs.microsoft.com/ja-jp/sql/t-sql/statements/bulk-insert-transact-sql?view=sql-server-ver15)
> SQL Server で、ユーザーが指定した形式で、データベース テーブルまたはビューにデータ ファイルをインポートします。

データのインポートはウィザードを使っても行えるのですが、コマンドを使えばストアドの処理に記載したり自動化ができるので便利です。

## 構文
おおまかな構文は以下のようなかんじです。

```sql
BULK INSERT
	インポート先のテーブル名
FROM
	インポートしたいデータファイルのパス
WITH (
	オプション
)
```

## オプション
BULK INSERT で指定できる主なオプションについてです。

### DATAFILETYPE
インポートしたいデータファイルの型を指定できます。

| DATAFILETYPE の値 | 形式 |
|---|---|
| char (既定値) | 文字形式。 |
| native | ネイティブ (データベース) データ型。 |
| widechar | Unicode 文字。 |
| widenative | ネイティブ (データベース) データ型。ただし、データが Unicode として格納される char、varchar、text 列は除きます。 |

### FIELDTERMINATOR
インポートしたいデータファイルのフィールド ターミネータ（区切り文字）を指定できます。
既定は \t (タブ文字) です。
ターミネータとしてサポートされる主な文字は以下です。

| ターミネータ文字 | 指定方法 |
|---|---|
| タブ | \t |
| 改行文字 | \n |
| キャリッジ リターン/ライン フィード | \r |
| カンマ | , |

詳細については、「[フィールド ターミネータと行ターミネータの指定 (SQL Server)](https://docs.microsoft.com/ja-jp/sql/relational-databases/import-export/specify-field-and-row-terminators-sql-server?view=sql-server-ver15)」を参照してください。

### ROWTERMINATOR
インポートしたいデータファイルの行ターミネータ（行の終わり）を指定できます。
既定は \r\n (改行文字) です。

### FIRSTROW
インポートしたいデータファイルの取込開始行を指定できます。
既定値は、指定したデータファイルの先頭行です。
FIRSTROW は 1 から始まります。

### FORMAT
2017以降のバージョンであれば、FORMAT = 'CSV' と指定すれば、csvファイルを簡単に取り込めます。

### FORMATFILE
フォーマットファイルのパスを指定できます。
フォーマットファイルは次の場合に使用します。

* データファイルに含まれる列の数が、テーブルまたはビューより多い、または少ない。
* 列の順序が異なる。
* 列の区切り記号が異なる。

フォーマットファイルは、bcp ユーティリティを使用して作成し、必要に応じてテキストエディターで修正します。
詳細については、「[bcp ユーティリティ](https://docs.microsoft.com/ja-jp/sql/tools/bcp-utility?view=sql-server-ver15)」と「[フォーマット ファイルの作成](https://docs.microsoft.com/ja-jp/sql/relational-databases/import-export/create-a-format-file-sql-server?view=sql-server-ver15)」を参照してください。

# BULK INSERT の使用例
実際にBULK INSERT を使用してデータを取込んでみましょう。
テーブル [Sales] にデータをインポートします。

テーブル定義

| 列名 | データ型 |
|:--|:--|
| SalesOrderID | int IDENTITY(1,1) |
| RevisionNumber | tinyint |
| OrderDate | datetime |
| Status | tinyint |
| CustomerID | int |
| SubTotal | money |
| ModifiedDate | datetime |
| Description | nvarchar(255) |


テーブル作成クエリ

```sql
CREATE TABLE [bulkInsertTest].[dbo].[Sales](
	 [SalesOrderID] [int] IDENTITY(1,1)
	,[RevisionNumber] [tinyint]
	,[OrderDate] [datetime]
	,[Status] [tinyint]
	,[CustomerID] [int]
	,[SubTotal] [money]
	,[ModifiedDate] [datetime]
	,[Description] [nvarchar](255)
)
```

## カンマ区切りcsvの取込み（FORMATオプション）
SQL Server が2017以降のバージョンであれば、FORMAT = 'CSV' で簡単にデータを取込みできます。

以下のようなデータを取込みます。

* ヘッダーなし
* カンマ区切り

![001.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/04e20671-b8a8-e7aa-3de1-c1e7ea845e5f.jpeg)

csvファイルは「D:\bulkInsertTest\csvData.csv」に配置しました。
取込むためのsql文は以下です。

```sql
BULK INSERT
	[bulkInsertTest].[dbo].[Sales]
FROM
	'D:\bulkInsertTest\csvData.csv'
WITH(
	FORMAT = 'CSV'
)
```

BULK INSERT を実行します。
![002.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/086cda9e-f626-b0f7-aae0-203ad8f5c1f7.jpeg)

SELECT でデータがインポートされた事を確認します。

![003.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/270d1e15-f80d-3976-5ef3-95c045ad16d4.jpeg)

## カンマ区切りcsvの取込み
今度はFORMATオプションを使用せずに取込みます。
一旦テーブルの中身は削除します。

```sql
TRUNCATE TABLE [bulkInsertTest].[dbo].[Sales]
```

オプションで区切り文字などの指定を行います。
今回は、
DATAFILETYPEは「char」
FIELDTERMINATORは「,」
ROWTERMINATORは「\n」を指定します。

```sql
BULK INSERT
	[bulkInsertTest].[dbo].[Sales]
FROM
	'D:\bulkInsertTest\csvData.csv'
WITH(
	 DATAFILETYPE = 'char'
	,FIELDTERMINATOR = ','
	,ROWTERMINATOR = '\n'
)
```

BULK INSERT を実行し、正しくインポートされました。

![004.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/4c5a8ef8-33c1-62a2-9592-4e1a1597b1f8.jpeg)

## ヘッダー付きcsvの取込み
次は以下のような、1行目にヘッダー（カラム名）を含んでいるcsvの取込みをします。

* ヘッダーあり
* カンマ区切り

![005.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/6d3416f7-1611-6206-6d52-77d113ae0b62.jpeg)

csvファイルは「D:\bulkInsertTest\csvDataHeader.csv」に配置しました。

このまま取込むと、ヘッダーもデータとして取込んでしまいます。

![006.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/02ede88e-871f-91f4-9d0a-92bc282c8779.jpeg)

> メッセージ 4864、レベル 16、状態 1、行 3
> 行 1、列 1 (SalesOrderID) の一括読み込みデータ変換エラー (型の不一致または指定されたコードページでは無効な文字)。

SalesOrderID がint型なのに、"SalesOrderID"という文字列を取込もうとして、失敗しました。

ヘッダーを取込みたくない場合は、FIRSTROWオプションを使用します。
取込むためのsql文は以下です。

```sql
BULK INSERT
	[bulkInsertTest].[dbo].[Sales]
FROM
	'D:\bulkInsertTest\csvDataHeader.csv'
WITH(
	 DATAFILETYPE = 'char'
	,FIELDTERMINATOR = ','
	,ROWTERMINATOR = '\n'
	,FIRSTROW = 2	--2行目から取込みたい
)
```

![007.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/07e6188a-9b80-f5ea-ca54-88ac9dcfbb50.jpeg)

## 文字列に改行コードを含むcsvの取込み
文字列の中に改行コードを含んでいる場合のcsvの取込みです。

* ヘッダーなし
* カンマ区切り
* 文字列内に改行あり
* 改行のある列はダブルクォーテーションでくくられている

![008.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/da1b762c-4260-f119-4c30-2b32e151b708.jpeg)

csvファイルは「D:\bulkInsertTest\csvDataLine.csv」に配置しました。

普通に取込むとこのようなエラーが出てしまいます。

![009.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/cabd1a80-63fb-67c6-60c8-86821e5c36e1.jpeg)

SQL Server が2017以降のバージョンであれば、FORMAT = 'CSV' を指定すれば取込みが可能です。

```sql
BULK INSERT
	[bulkInsertTest].[dbo].[Sales]
FROM
	'D:\bulkInsertTest\csvDataLine.csv'
WITH(
	FORMAT = 'CSV'
)
```

![010.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/449a3403-7bfc-31c8-d4da-177c271d739e.jpeg)

ダブルクォーテーションは省いて取込んでくれます。

![011.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/b54866fd-3102-fddc-e766-3a062f7f09c0.jpeg)

FORMATオプションを使用せずに取込む際は少し大変です。
今回のサンプルデータでは、改行コードを含んでいる文字列のカラム [Description] が一番最後であること、[Description] はすべてダブルクォーテーションで囲まれているので、以下のクエリで取込みができました。

```sql
BULK INSERT
	[bulkInsertTest].[dbo].[Sales]
FROM
	'D:\bulkInsertTest\csvDataLine.csv'
WITH(
	 DATAFILETYPE = 'char'
	,FIELDTERMINATOR = ','
	,ROWTERMINATOR = '"\n'
)
```

ただ、冒頭のダブルクォーテーションも一緒に取込まれてしまったり、サンプルデータのように上手いこと行の終わりにダブルクォーテーションがついているような事はまれなので、2017以降のバージョンなのであれば無難にFORMATオプションを使用したほうがよいです。

![012.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/583f4f68-005a-32ac-6669-1fcaf7e3efa4.jpeg)

文字列内の改行コードが\r、行の終わりの改行コードが\nなどと区別出来る場合であれば取込みやすいです。

![013.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/f4b1aa6d-f55e-e9dc-d47b-12f589119c5f.jpeg)

```sql
BULK INSERT
	[bulkInsertTest].[dbo].[Sales]
FROM
	'D:\bulkInsertTest\csvDataLine2.csv'
WITH(
	 DATAFILETYPE = 'char'
	,FIELDTERMINATOR = ','
	,ROWTERMINATOR = '\n'
)
```

ただやはりダブルクォーテーションも取込まれてしまうので、あとで取り除くなどをしてあげる必要があります。

![014.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/59d3e0f7-ebfc-7ce5-11bd-4a61a3020c40.jpeg)

## 固定長テキストファイルの取込み
固定長のテキストファイルの場合は、フォーマットファイルを使用して取込む必要があり、ひと手間かかります。

* ヘッダーなし
* 固定長

![015.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/17eb88cf-b70f-65a0-ec79-32853ccbf74e.jpeg)

csvファイルは「D:\bulkInsertTest\csvDataLen.csv」に配置しました。

bcp ユーティリティを使用してフォーマットファイルを作成します。

bcp ユーティリティとは
[bcp ユーティリティ - SQL Server | Microsoft Docs](https://docs.microsoft.com/ja-jp/sql/tools/bcp-utility?view=sql-server-ver15)

> **b** ulk **c** opy **p** rogram ユーティリティ (bcp) は、Microsoft SQL Server のインスタンスと、ユーザー指定の形式のデータ ファイルとの間でデータの一括コピーを行います。 bcp ユーティリティを使うと、多数の新規行を SQL Server テーブルにインポートしたり、データをテーブルからデータ ファイルにエクスポートしたりできます。

フォーマットファイルとは
[XML 以外のフォーマット ファイル (SQL Server) - SQL Server | Microsoft Docs](https://docs.microsoft.com/ja-jp/sql/relational-databases/import-export/non-xml-format-files-sql-server?view=sql-server-ver15)

bcp ユーティリティがダンロードされていなければ、[bcp ユーティリティのページ](https://docs.microsoft.com/ja-jp/sql/tools/bcp-utility?view=sql-server-ver15)からダウンロードしてください。

コマンドプロンプトでbcpコマンドを実行します。

```bat:コマンドプロンプト
bcp [database_name] format null -f [format_file] -c -t[field_term] -T
```

bcp コマンドの主な引数

| 引数 | 内容 |
| :-- | :-- |
| bcp [database_name] | 取込み先のテーブルを指定します。 |
| format | データ ファイルのパスの代わりにnullを指定します。 |
| -f [format_file] | フォーマットファイルをエクスポートしたいパスを指定します。 |
| -c | このオプションを使用すると、フィールドごとにプロンプトが表示されません。 |
| -t [field_term] | フィールド ターミネータを指定します。今回取込みたいファイルは区切り文字がないので空文字を指定しています。 |
| -T | Windows認証を使用してSQL Serverに接続します。-T を指定しない場合、正常にログインするには -U と -P を指定する必要があります。 |
| -S [server_name] | 接続先となる SQL Server のインスタンスを指定します。サーバーを指定しない場合、 ローカル コンピューター上の SQL Server の既定のインスタンスに接続されます。 |

今回は以下コマンドを実行しました。

```bat:コマンドプロンプト
bcp [bulkInsertTest].[dbo].[Sales] format null -f D:\bulkInsertTest\ff_csvDataLen.fmt -c -t "" -T
```

エクスポートされたフォーマットファイル「ff_csvDataLen.fmt」をテキストエディタで開きます。

![016.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/c2bcec11-24b3-fbca-f581-d7c8881ed360.jpeg)

4つ目の列の数字が固定長数なので、取込みたいcsvの固定長数に編集します。

![017.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/6b7d7d31-39dc-390b-dce0-7383cb626c6a.jpeg)

```fmt
14.0
8
1       SQLCHAR             0       5       ""       1     SalesOrderID                     ""
2       SQLCHAR             0       5       ""       2     RevisionNumber                   ""
3       SQLCHAR             0       20      ""       3     OrderDate                        ""
4       SQLCHAR             0       5       ""       4     Status                           ""
5       SQLCHAR             0       10      ""       5     CustomerID                       ""
6       SQLCHAR             0       15      ""       6     SubTotal                         ""
7       SQLCHAR             0       20      ""       7     ModifiedDate                     ""
8       SQLCHAR             0       30     "\r\n"   8     Description                      Japanese_CI_AS
```

FORMATFILEオプションに上記で作成したfmtファイルを指定して取込みます。

```sql
BULK INSERT
	[bulkInsertTest].[dbo].[Sales]
FROM
	'D:\bulkInsertTest\csvDataLen.csv'
WITH(
	 FORMATFILE = 'D:\bulkInsertTest\ff_csvDataLen.fmt'
)
```

![018.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/03d182e4-390f-f2fc-6a36-5bc4915e5d64.jpeg)

## 列の順序が異なる固定長テキストファイルの取込み
フォーマットファイルを使用すれば、列の順序を変更して取込むこともできます。
以下のようなデータを取込んでみます。

* ヘッダーなし
* 固定長
* 列の「ModifiedDate」と「Description」の順序が逆になっている

![019.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/33734a79-c900-c962-ce4c-60470f610249.jpeg)

csvファイルは「D:\bulkInsertTest\csvDataLen2.csv」に配置しました。

先ほどのフォーマットファイル「ff_csvDataLen.fmt」をテキストエディタで開き、以下のように編集します。

![020.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/3e851f29-6039-55d8-eae3-4dcc29f79064.jpeg)

```fmt
14.0
8
1       SQLCHAR             0       5       ""       1     SalesOrderID                     ""
2       SQLCHAR             0       5       ""       2     RevisionNumber                   ""
3       SQLCHAR             0       20      ""       3     OrderDate                        ""
4       SQLCHAR             0       5       ""       4     Status                           ""
5       SQLCHAR             0       10      ""       5     CustomerID                       ""
6       SQLCHAR             0       15      ""       6     SubTotal                         ""
7       SQLCHAR             0       30      ""       8     Description                      Japanese_CI_AS
8       SQLCHAR             0       20      "\r\n"   7     ModifiedDate                     ""
```

FORMATFILEオプションに上記で作成したfmtファイルを指定して取込みます。

```sql
BULK INSERT
	[bulkInsertTest].[dbo].[Sales]
FROM
	'D:\bulkInsertTest\csvDataLen2.csv'
WITH(
	 FORMATFILE = 'D:\bulkInsertTest\ff_csvDataLen.fmt'
)
```

![021.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/fc6d5280-e404-1915-8bf5-b98addb2c555.jpeg)

フォーマットファイルで指定したとおり、「ModifiedDate」と「Description」の順序を逆にして取込んでくれました。

![022.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/237e6145-0821-376d-9cba-eca6a1078cfc.jpeg)

# 最後に
BULK INSERT を使用する際は、取込みたいデータの区切り文字など注意深く見る必要があります。
上手く使えば作業を自動化できたり、ストアド化して別のツールからデータを取込んだりできるのでとても便利だと思います。

# 参考サイト
[SQL Server BCP ユーティリティのインポートする方法(固定長ファイル)](http://moriroom.my.coocan.jp/site1/?p=2360)
[【SQL SERVER】固定長データのインポート](http://hawkeye-mihawk.blogspot.com/2008/05/sql-server.html)