駅データ.jpの駅データの保存スクリプト&テーブル作成スクリプトを書いた

## 駅データ.jpとは
駅データ.jpとは、その名の通り駅データ（鉄道事業者・路線・駅・接続駅）が取得できるサービスです。基本無料で登録しデータを取得でき、より詳細なデータが欲しいときには有料プランも用意されています。
[駅データ.jp](http://www.ekidata.jp "駅データ.jp")
[駅とか路線のマスターデータの入手方法](http://qiita.com/kou/items/e0d3fab106e4c52a7f26)

## スクリプトの説明
### 使用法
以下のリンクのREADME.mdに記載していますので、省略します。
[GitHub ebihara99999/LineData](https://github.com/ebihara99999/LineData)
### 概要
本記事では、駅データの無料プランで提供されている以下のデータに関するテーブル定義SQLファイルとCSVファイルを挿入するスクリプトを記載します。対応するデータの種類は以下になります。
* 事業者マスタ
* 路線マスタ
* 駅マスタ
* 接続駅マスタ
* 都道府県マスタ

### 動作確認環境
* OS: CentOS 6.6, Amazon Linux AMI release 2015.09
* RDMS: MySQL 5.5
* shell: bash

### テーブル定義
テーブル定義は以下になります。駅データ.jpで用意しているカラムは全て記述しました。但し、有料データで必要になるカラムに関してはnullを許容しています。また、基本的に自分でデータを編集することは考えていないので、外部キー制約などはつけていません。

```sql

CREATE TABLE `station` (
  `station_cd` int(11) NOT NULL,
  `station_g_cd` int(11) NOT NULL,
  `station_name` varchar(80) COLLATE utf8_unicode_ci DEFAULT NULL,
  `station_name_k` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `station_name_r` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `railway_lines_id` int(11) NOT NULL,
  `prefectures_id` int(11) DEFAULT NULL,
  `post` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
  `add` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `lon` decimal(9,6) DEFAULT NULL,
  `lat` decimal(8,6) DEFAULT NULL,
  `open_ymd` datetime DEFAULT NULL,
  `close_ymd` datetime DEFAULT NULL,
  `e_status` smallint(6) DEFAULT NULL,
  `e_sort` int(11) DEFAULT NULL,
  PRIMARY KEY (`station_cd`),
  KEY `index_stations_on_lon_and_lat` (`lon`,`lat`),
  KEY `index_stations_on_station_name` (`station_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `railway_line` (
  `railway_line_cd` int(11) NOT NULL,
  `railway_company_id` int(11) NOT NULL,
  `line_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `line_name_k` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `line_name_h` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `line_color_c` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `line_color_t` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `line_type` int(11) DEFAULT NULL,
  `lon` decimal(9,6) DEFAULT NULL,
  `lat` decimal(8,6) DEFAULT NULL,
  `zoom` int(11) DEFAULT NULL,
  `e_status` smallint(6) DEFAULT NULL,
  `e_sort` int(11) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`railway_line_cd`),
  KEY `index_railway_lines_on_lon_and_lat` (`lon`,`lat`),
  KEY `index_railway_lines_on_line_name` (`line_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `railway_company` (
  `company_cd` int(11) NOT NULL,
  `rr_cd` int(11) NOT NULL,
  `company_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `company_name_k` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `company_name_h` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `company_name_r` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `company_url` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `company_type` int(11) DEFAULT NULL,
  `e_status` smallint(6) DEFAULT NULL,
  `e_sort` int(11) DEFAULT NULL,
  PRIMARY KEY (`company_cd`),
  KEY `index_railway_companies_on_company_name` (`company_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `prefecture` (
  `pref_cd` int(11) NOT NULL,
  `pref_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`pref_cd`),
  UNIQUE KEY `index_prefectures_on_pref_name` (`pref_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `join` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `line_cd` int(11) NOT NULL,
  `station_cd1` int(11) NOT NULL,
  `station_cd2` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_joins_on_line_cd_and_station_cd1_and_station_cd2` (`line_cd`,`station_cd1`,`station_cd2`)
) ENGINE=InnoDB AUTO_INCREMENT=99929 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
```

### データ挿入スクリプト
#### ソース
```bash
#!/bin/bash

############################################################################################################
# This is a script that helps register railway line data offered by "駅データ.jp"(http://www.ekidata.jp).
# Supported only MySQL, not other RDMS.
############################################################################################################


# Check arguments.
# One argument is needed, a database name.
if [ $# -ne 1 ]; then
	echo "need 1 argument, a database name" 2>&1
	exit 1
fi

# Constant definition
DATABASE=$1
CSVDIR=csvfiles/

# Convert ascii code into utf8.
# CSV files are ascii code just when downloaded.
nkf -w8 --overwrite csvfiles/* && nkf -g csvfiles/* | grep "UTF-8" 2>&1 >/dev/null && echo "conversion into UTF-8 succeeded"

# Get CSV files' name.
# Check whether amazon linux or not (CentOS).
if grep -q "Amazon" /etc/system-release; then
  STATION_CSV=`ls -tlr csvfiles/ | awk '{if ( NR>1 ) print $9}' | grep "station"`
  PREF_CSV=`ls -tlr csvfiles/ | awk '{if ( NR>1 ) print $9}' | grep "pref"`
  LINE_CSV=`ls -tlr csvfiles/ | awk '{if ( NR>1 ) print $9}' | grep "line"`
  JOIN_CSV=`ls -tlr csvfiles/ | awk '{if ( NR>1 ) print $9}' | grep "join"`
  COMPANY_CSV=`ls -tlr csvfiles/ | awk '{if ( NR>1 ) print $9}' | grep "company"`
else 
  STATION_CSV=`ls -tlr csvfiles/ | awk '{if ( NR>1 ) print $10}' | grep "station"`
  PREF_CSV=`ls -tlr csvfiles/ | awk '{if ( NR>1 ) print $10}' | grep "pref"`
  LINE_CSV=`ls -tlr csvfiles/ | awk '{if ( NR>1 ) print $10}' | grep "line"`
  JOIN_CSV=`ls -tlr csvfiles/ | awk '{if ( NR>1 ) print $10}' | grep "join"`
  COMPANY_CSV=`ls -tlr csvfiles/ | awk '{if ( NR>1 ) print $10}' | grep "company"`
fi

# Insert line data into table.
mysql -u $MYSQL_USER -p$MYSQL_PASS $DATABASE -h $MYSQL_HOST -e "load data local infile '${CSVDIR}${STATION_CSV}' into table station fields terminated by ',' IGNORE 1 LINES;" && echo "insertion of station data succeeded"
mysql -u $MYSQL_USER -p$MYSQL_PASS $DATABASE -h $MYSQL_HOST -e "load data local infile '${CSVDIR}${PREF_CSV}' into table prefecture fields terminated by ',' IGNORE 1 LINES;" && echo "insertion of prefecture data succeeded"
mysql -u $MYSQL_USER -p$MYSQL_PASS $DATABASE -h $MYSQL_HOST -e "load data local infile '${CSVDIR}${LINE_CSV}' into table railway_line fields terminated by ',' IGNORE 1 LINES;" && echo "insertion of line data succeeded"
mysql -u $MYSQL_USER -p$MYSQL_PASS $DATABASE -h $MYSQL_HOST -e "load data local infile '${CSVDIR}${JOIN_CSV}' into table \`join\` fields terminated by ',' IGNORE 1 LINES;" && echo "insertion of join data succeeded"
mysql -u $MYSQL_USER -p$MYSQL_PASS $DATABASE -h $MYSQL_HOST -e "load data local infile '${CSVDIR}${COMPANY_CSV}' into table railway_company fields terminated by ',' IGNORE 1 LINES;" && echo "insertion of railway_company data succeeded"
```

#### 説明
* 引数チェック
まずは引数チェックをします。オプションではなく直接引数でデータベース名を取ります。

* 定数定義
次に定数定義をします。CSVファイル用のディレクトリを指定しています。

* 文字コード変換
ダウンロードしたCSVファイルはasciiコードだったので（現在は変わっているかもしれません）文字コードを変換しています。この処理は一回行えば良いので条件判定をつけた方がいいですね。

* CSVファイル名取得
CSVファイルは駅データ.jpで更新する可能性があるので、動的に取得するようにしています。
まず、

```bash
if grep -q "Amazon" /etc/system-release; then
```
に関して、```ls```コマンドの表示がOSで異なる場合があるので、Amazon LinuxとCentOSについては判定するようにしています。```grep```のqオプションは「検索結果を表示しない」なので、/dev/nullに結果を投げる必要がなくなりますね（もしかしたらエラー出力は表示されてしまうかもしれません）。
次に

```bash
STATION_CSV=`ls -tlr csvfiles/ | awk '{if ( NR>1 ) print $9}' | grep "station"
````
について、ファイル名を```ls```で表示させ、awkでファイル名一覧を取得しgrepで該当のファイル名を取得しています。CSVファイル名のstation、line、pref、join、companyは変わらないと予想されるので、それらの言葉を用いて取得しています。ちなみに、```awk '{if ( NR>1 ) print $9}'```の```if ( NR>1 )```は```ls -l```の結果の一行目がいらないので実施しています。

* データ挿入

```bash
mysql -u $MYSQL_USER -p$MYSQL_PASS $DATABASE -h $MYSQL_HOST -e "load data local infile '${CSVDIR}${STATION_CSV}' into table station fields terminated by ',' IGNORE 1 LINES;" && echo "insertion of station data succeeded"
```
に関して、mysqlコマンドはeオプションでSQL文をシェルから実行できます。この方法の利点は、上記で```'${CSVDIR}${STATION_CSV}'```と書いているようにシェル変数を埋め込める点です。これで動的に取得したファイル名も使えますね。ところで、```load data local infile```はカンマ区切りがデフォルトでないので```fields terminated by ','```を記述する必要があります。

#### ソースに関するまとめ
~~本ソースはシェルスクリプトなので、コードの綺麗さより見た目の簡単さを意識しました。例えば、ファイル名取得の処理やmysqlコマンドを使ってデータ挿入する処理では、他の言語で書く場合はループを回し冗長性を排除しますが、今回はシェルスクリプトということでループを使用していません。
参考：[スマートな紳士のためのシェルスクリプト（7）：「アット・ア・グランス性」確保のための8つの原則 (1/2)](http://www.atmarkit.co.jp/ait/articles/1208/17/news110.html)~~
追記：上記記事に関しては色々議論があるようですね。確かに、当然どの言語だろうと適切な粒度での冗長性の排除は必要だと思いました。シェルスクリプトは、ど新人の頃に勉強したきりでその頃に参考にしていた資料をバイブルのように思い知識をブラッシュアップして来なかったため、当たり前のことに気付けなかったのだと思います。反省・・・

## 最後に
本記事は以下の（私が書いた）記事の修正版になります。READMEをきちんと書いたり、ソースを修正したのでqiitaに改めて記載することにしました。
[駅データ.jpの駅データ保存スクリプトを書きました。](http://biibiebisuke.hatenablog.com/entry/2015/06/10/002218)