MySQLへCSVなどのテキスト形式のデータファイルを一括でインポートします。
今回はLinuxなどのコマンドとして提供されているmysqlimport
を利用します。通常MySQLをインストールした際に自動的に付いてきますのですぐに実行することが可能です。
$ mysqlimport -u username -p --local dbname tablename.tsv
シェルスクリプト内で実行する場合はこちらが手軽ですね。また大抵の場合、INSERT文を大量に発行するよりも高速にデータを入れることができます。
環境準備
テスト用テーブル
ここではサンプルとして書籍の情報を扱うDBとテーブルを作成し、データをインポートします。
/* DB作成 */ CREATE DATABASE book; USE book; /* テーブル作成 */ CREATE TABLE bookshelf( -- 書棚テーブル id int, title varchar(128), price int, PRIMARY KEY(id) );
サンプルデータ
インポートするためのテキストファイルをタブ区切り形式で用意します。文字コードはUTF-8
、改行コードはLF
にしてあります(Windows上で動作しているMySQLへインポートする場合はCRLF
)。またmy.cnfなどで文字コードの設定を変更している場合はそれに合わせます。1行目に各カラムのタイトルは付けません。
今回は1月28日時点のAmazonのコミック売上げランキングから抜き出してきました。どうでも良いですが0円の書籍はKindleのお試し版みたいですねw typoではありませんw
$ cat bookshelf.tsv 1 鬼滅の刃 19 484 2 戦争は女の顔をしていない1 990 3 若林くんが寝かせてくれない1 0 4 ゆるキャン△1 0 5 とんでもスキルで異世界放浪メシ5 614 6 異世界迷宮でハーレムを5 614 7 ダーウィンズゲーム1 0 8 幼女戦記17 614 9 若林くんが寝かせてくれない2 0 10 NEW GAME!10 792
注意点として、mysqlimport
コマンドを利用する場合はテーブル名とインポートするファイル名を同じ名前にする必要があります。拡張子は自由に設定することができます。
インポートする
実行
mysqlimportコマンドでインポートする場合は以下のように入力します。
$ mysqlimport -u foo -p --local book bookshelf.tsv Enter password: book.bookshelf: Records: 10 Deleted: 0 Skipped: 0 Warnings: 0
各オプションは以下の通り。
-u
の後にユーザー名を指定-p
を指定するとEnterを押した直後にパスワードを聞かれます。- パスワードをオプションに続けて直接書くと
.bash_history
などのログに記録されてしまうので止めた方が良いです。
- パスワードをオプションに続けて直接書くと
--local
は所定のディレクトリ以外の場所のファイルをインポートする場合に必要です。詳細は後述します。- データベース名(book)を指定
- 最後にインポートするファイルのパス
インポートの実行にはFILE権限が必要です。 blog.katsubemakito.net
確認
MySQLへログインして正常にデータが入っているか確認しておきます。
$ mysql -u foo -p Enter password: mysql> use book; mysql> select * from bookshelf; +----+------------------------------------------------+-------+ | id | title | price | +----+------------------------------------------------+-------+ | 1 | 鬼滅の刃 19 | 484 | | 2 | 戦争は女の顔をしていない1 | 990 | | 3 | 若林くんが寝かせてくれない1 | 0 | | 4 | ゆるキャン△1 | 0 | | 5 | とんでもスキルで異世界放浪メシ5 | 614 | | 6 | 異世界迷宮でハーレムを5 | 614 | | 7 | ダーウィンズゲーム1 | 0 | | 8 | 幼女戦記17 | 614 | | 9 | 若林くんが寝かせてくれない2 | 0 | | 10 | NEW GAME!10 | 792 | +----+------------------------------------------------+-------+ 10 rows in set (0.00 sec)
大丈夫そうですね。
CSVをインポート
カラムをカンマ(,)で区切ったファイル形式の方のCSVをインポートします。
サンプルデータ
先ほどのファイルをカンマ区切りに変換しました。また文字列のカラムはダブルコーテーションで囲うことにします。
$ cat bookshelf.csv 1,"鬼滅の刃 19",484 2,"戦争は女の顔をしていない1",990 3,"若林くんが寝かせてくれない1",0 4,"ゆるキャン△1",0 5,"とんでもスキルで異世界放浪メシ5",614 6,"異世界迷宮でハーレムを5",614 7,"ダーウィンズゲーム1",0 8,"幼女戦記17",614 9,"若林くんが寝かせてくれない2",0 10,"NEW GAME!10",792
実行
このCSVファイルをインポートするにはmysqlimport
にオプションを2つ追加するだけです。
$ mysqlimport -u foo -p --local --fields-terminated-by="," --fields-enclosed-by='"' book bookshelf.csv
追加されたオプションは以下の通り。
--fields-terminated-by
で区切り文字を指定(デフォルトはタブ)--fields-enclosed-by
は文字列などを囲う記号を指定(デフォルトは未指定)
タブ区切りもCSV?
余談ですが最近のCSVはタブや半角スペースで区切った物もCSV(Character-Separated Values)と呼ぶので、最初に取り上げたタブ区切りテキストもCSVではあるのですが、それはまた別の話w
類似したフォーマットとして、タブで区切られた tab-separated values (TSV)や、欧文間隔 (いわゆる半角スペース) で区切られた space-separated values (SSV) などがあり、これらをまとめて character-separated values (CSV)、delimiter-separated values (DSV) とも呼ばれることも多い。
Comma-Separated Values - Wikipedia
オプション
ヘッダ行をスキップ --ignore-lines
1行目にカラム名がある場合、このままだとカラム名までもデータとして取り込まれるか、テーブルのデータ型と合わない場合は弾かれてしまいます。
$ cat bookshelf.tsv ID タイトル 価格 1 鬼滅の刃 19 484 2 戦争は女の顔をしていない1 990 3 若林くんが寝かせてくれない1 0
--ignore-lines
オプションでカラム名が書かれたヘッダ行をスキップすることができます。
$ mysqlimport -u foo -p --local --ignore-lines=1 book bookshelf.tsv
正確には--ignore-lines=
はファイルの先頭から指定した行数をスキップするオプションですので、ファイルの途中からインポートするといったことにも応用できます。
エスケープ
カラムの中に区切り文字など処理場まずい文字が登場する場合にはバックスラッシュ(円記号)でエスケープしてやります。
$ cat bookshelf.csv 1,鬼滅の刃\,19,484 $ mysqlimport -u foo -p --local --fields-terminated-by="," book bookshelf.csv
この場合はダブルコーテーションなどで囲い--fields-enclosed-by
オプションを指定するやり方でも解決できます。
$ cat bookshelf.csv 1,"鬼滅の刃,19",484 $ mysqlimport -u foo -p --local --fields-terminated-by="," --fields-enclosed-by='"' book bookshelf.csv
ホスト/ポートを指定 -h, --port
デフォルトだとlocalhost
の3306番のポートへ接続しますが、-h
または--host
オプションでホストを、--port
オプションでポートを指定することができます。
$ mysqlimport -u foo -p --local -h db1.example.com --port 13306 book bookshelf.csv
サービスの規模が大きくなってくるとDBサーバは別に建てることが多いですし、手元にあるデータを遠隔地にあるサーバへ直接インポートしたい時にも重宝します。
注意点としては、事前にMySQLへログイン元のホストでユーザーを新規作成しておく必要があります。MySQLはログイン元のホストが異なると、ユーザー名が同じでも全く別のユーザーとして扱われます。ユーザー作成後にGRANTもお忘れなく。 blog.katsubemakito.net
AUTO INCREMENTの扱い --columns
特定のカラムを自動採番したい場合、データファイル上はその列を空にしておきます。
CREATE TABLE bookshelf( id int AUTO INCREMENT, -- NEW! title varchar(128), price int, PRIMARY KEY(id) );
idがあった箇所を消しておくだけです。
$ cat bookshelf.tsv 鬼滅の刃 19 484 戦争は女の顔をしていない1 990 若林くんが寝かせてくれない1 0 (中略)
もしカラムを自体が無かったことにしてしまうと、以下のように何だかよくわからないことになってしまいます。
$ cat bookshelf.tsv 鬼滅の刃 19 484 戦争は女の顔をしていない1 990 (中略) $ mysqlimport -u foo -p --local book bookshelf.tsv Enter password: book.bookshelf: Records: 10 Deleted: 0 Skipped: 0 Warnings: 20 $ mysql -u foo -p mysql> use book; mysql> select * from bookshelf; +----+-------+-------+ | id | title | price | +----+-------+-------+ | 1 | 484 | NULL | | 2 | 990 | NULL | (中略) | 9 | 0 | NULL | | 10 | 792 | NULL | +----+-------+-------+
もしどうしてもデータファイル上からカラムを取り払ってしまいたい場合は、--columns
で指定することで回避できます。
$ cat bookshelf.tsv 鬼滅の刃 19 484 戦争は女の顔をしていない1 990 (中略) $ mysqlimport -u foo -p --local --columns=title,price book bookshelf.tsv Enter password: book.bookshelf: Records: 10 Deleted: 0 Skipped: 0 Warnings: 0
テーブルを空にしてからインポート --delete
--delete
オプションを指定すると、テーブルのデータを削除してからインポートしてくれます。
$ mysqlimport -u foo -p --local --delete book bookshelf.csv
これはTRUNCATE bookshelf
ではなく、DELETE FROM bookshelf
のようでAUTO INCREMENTの値はリセットされません。
全テーブルをロックしてからインポート
インポートする際に
--lock-tables
トラブルシューティング
--localオプションを付けていない
MySQL5.7.16以降から特定のディレクトリ以外にあるファイルをインポートしようとすると、以下のようなエラーとなります。
mysqlimport: Error: 1290, The MySQL server is running with the --secure-file-priv option so it cannot execute this statement, when using table: bookshelf
MySQLのシステム変数secure_file_priv
で指定されているディレクトリ以外にあるファイルを気軽にインポート出来なくする制約のようです。Linux環境の多くは以下の通り/var/lib/mysql-files/
がデフォルトで割り当てられています。
mysql> SHOW GLOBAL VARIABLES LIKE 'secure_file_priv'; +------------------+-----------------------+ | Variable_name | Value | +------------------+-----------------------+ | secure_file_priv | /var/lib/mysql-files/ | +------------------+-----------------------+ 1 row in set (0.00 sec)
解決策としては--local
オプションを付けて実行するか、secure_file_priv
で指定されているディレクトリにデータファイルを置く必要があります。
Warningsの具体的な内容を確認したい
どうもmysqlimport
を利用した際のWarningsの内容はどこにも出力されないようです。もしWarningsの値も確認したい場合はLOAD DATA INFILE
構文を利用する必要があります。
設定も確認したんですけどね。どなたかご存知でしたら教えてくださいませ。
mysql> SHOW GLOBAL VARIABLES LIKE 'log_warnings'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | log_warnings | 2 | +---------------+-------+ mysql> SHOW GLOBAL VARIABLES LIKE 'log_error'; +---------------+---------------------+ | Variable_name | Value | +---------------+---------------------+ | log_error | /var/log/mysqld.log | +---------------+---------------------+