この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、Yuraです。 今回もどうぞよろしくお願い申し上げます。
はじめに
MySQLから、Amazon Redshiftへのデータ移行をやってみました。 大抵の場合、テーブルは予め用意しておくと思うので、今回はMySQLのCreate Table文をRedshiftのCreate Table文に変換してみます。
MySQLとRedshiftのデータ型対応について
恥ずかしながら、クラスメソッドで仕事をするここ最近までRedshiftを使ったことがなかったので、 まずはRedShiftにどのようなデータ型があって、MySQLのデータ型とどう紐づければよいのかを調べなければなりませんでした。 こちらの記事 が大変勉強になりました。 調べてみると、AWS Data PipelineにMySQLとRedshiftのデータ型変換スクリプトがあるようなので、 それと同じようにやります。 Amazon RDS から Redshift へのコピー用テンプレート
例えば次のようなMySQL用のCreate Table文があったとして、
CREATE TABLE employees (
id int(11) unsigned NOT NULL,
name varchar(255),
age tinyint,
hire_date date,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
PRIMARY KEY (id)
);
Redshift用に変換すると、こんな感じになります。
CREATE TABLE employees (
id BIGINT NOT NULL,
name VARCHAR(1020),
age SMALLINT,
hire_date DATE,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
PRIMARY KEY (id)
);
プログラムで変換
単純にデータ型のマッピングをするだけなのですが、 SQLの数が多いと面倒なので、Javaを使ってできるようにしました。 プログラムで処理するにあたって、mysqldumpにて出力したSQLを使います。 ↓がmysqldumpで吐き出した、employeesテーブルです。
CREATE TABLE `employees` (
`id` int(11) unsigned NOT NULL,
`name` varchar(255) DEFAULT NULL,
`age` tinyint(4) DEFAULT NULL,
`hire_date` date DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
プログラムは以下のようになります。 SQLConverterのconvertSQLメソッドに変換したいSQLを渡してあげてください。 ざっくり動きを説明するとSQLを読んで、カラム定義オブジェクトを生成して、そのカラム定義オブジェクトを元に変換後のSQLを出力します。
SQLConverter.java
package jp.classmethod.app;
import java.util.ArrayList;
import java.util.List;
public class SQLConverter {
/** REDSHIFT データ型・SMALLINT */
private static final String REDSHIFT_DATATYPE_SMALLINT = "SMALLINT";
/** REDSHIFT データ型・INTEGER */
private static final String REDSHIFT_DATATYPE_INTEGER = "INTEGER";
/** REDSHIFT データ型・BIGINT */
private static final String REDSHIFT_DATATYPE_BIGINT = "BIGINT";
/** REDSHIFT データ型・DECIMAL */
private static final String REDSHIFT_DATATYPE_DECIMAL = "DECIMAL";
/** REDSHIFT データ型・REAL */
private static final String REDSHIFT_DATATYPE_REAL = "REAL";
/** REDSHIFT データ型・DOUBLE */
private static final String REDSHIFT_DATATYPE_DOUBLE = "DOUBLE PRECISION";
/** REDSHIFT データ型・DATE */
private static final String REDSHIFT_DATATYPE_DATE = "DATE";
/** REDSHIFT データ型・TIMESTAMP */
private static final String REDSHIFT_DATATYPE_TIMESTAMP = "TIMESTAMP";
/** REDSHIFT データ型・VARCHAR */
private static final String REDSHIFT_DATATYPE_VARCHAR = "VARCHAR";
/** REDSHIFT データ型・VARCHAR(MAX) */
private static final String REDSHIFT_DATATYPE_VARCHAR_MAX = "VARCHAR(MAX)";
/**
* SQLの変換を行う
* @param SQL
* @return 変換後SQL
*/
public static String convertSQL(String SQL) {
// 単語ずつに分割
String[] words = SQL.split(" ");
String table = null;
List<ColumnDefinitionObject> columns = new ArrayList<>();
int i = 0;
ColumnDefinitionObject cdo = null;
String prevWord = null;
for (String word : words) {
if (word.startsWith("`") && !"CONSTRAINT".equals(prevWord)
&& !"REFERENCES".equals(prevWord) && !"KEY".equals(prevWord)) {
if (table == null) {
// テーブル名を設定
table = word.replaceAll("`", "\"");
} else {
// カラム名を設定
String columnName = word.replaceAll("`", "\"");
cdo = new ColumnDefinitionObject(columnName);
// データ型を取得
String dataType = words[i + 1].split("\\(")[0];
// データ型にカンマがついている場合があるので外す
dataType = dataType.split(",")[0];
cdo.setDataType(dataType);
// 桁数を取得
if (words[i + 1].split("\\(").length > 1) {
String length = words[i + 1].split("\\(")[1].split("\\)")[0];
cdo.setLength(length);
}
// unsignedかどうかのチェックを行う
if ("unsigned".equals(words[i + 2])) {
cdo.setUnsigned(true);
}
columns.add(cdo);
}
}
prevWord = word;
i++;
}
return createSQL(table, columns);
}
/**
* SQLを出力する
* @return 新たなCREATE TABLE文
*/
private static String createSQL(String table, List<ColumnDefinitionObject> columns) {
StringBuilder sql = new StringBuilder("CREATE TABLE " + table + " (");
int i = 1;
for (ColumnDefinitionObject cdo : columns) {
sql.append(cdo.getColumnName() + " ");
sql.append(dataTypeMapping(cdo));
if (i < columns.size()) {
sql.append(", ");
}
i++;
}
sql.append(");");
return sql.toString();
}
/**
* データ型のマッピングを行う
* @param cdo
* @return マッピング結果
*/
private static String dataTypeMapping(ColumnDefinitionObject cdo) {
String result = "";
switch (cdo.getDataType()) {
case TINYINT :
result = REDSHIFT_DATATYPE_SMALLINT;
break;
case SMALLINT :
result = !cdo.isUnsigned()? REDSHIFT_DATATYPE_SMALLINT : REDSHIFT_DATATYPE_INTEGER;
break;
case MEDIUMINT :
result = REDSHIFT_DATATYPE_INTEGER;
break;
case INT :
result = !cdo.isUnsigned()? REDSHIFT_DATATYPE_INTEGER : REDSHIFT_DATATYPE_BIGINT;
break;
case BIGINT :
result = !cdo.isUnsigned()? REDSHIFT_DATATYPE_INTEGER : REDSHIFT_DATATYPE_VARCHAR;
break;
case DECIMAL :
result = REDSHIFT_DATATYPE_DECIMAL + bracket(cdo.getLength());
break;
case FLOAT :
result = REDSHIFT_DATATYPE_REAL;
break;
case DOUBLE :
result = REDSHIFT_DATATYPE_DOUBLE;
break;
case CHAR :
result = REDSHIFT_DATATYPE_VARCHAR + bracket(Integer.parseInt(cdo.getLength()) * 4);
break;
case VARCHAR :
result = REDSHIFT_DATATYPE_VARCHAR + bracket(Integer.parseInt(cdo.getLength()) * 4);
break;
case TINYTEXT :
result = REDSHIFT_DATATYPE_VARCHAR + bracket(255 * 4);
break;
case TEXT :
result = REDSHIFT_DATATYPE_VARCHAR_MAX;
break;
case MEDIUMTEXT :
result = REDSHIFT_DATATYPE_VARCHAR_MAX;
break;
case LONGTEXT :
result = REDSHIFT_DATATYPE_VARCHAR_MAX;
break;
case BINARY :
result = REDSHIFT_DATATYPE_VARCHAR + bracket(255);
break;
case VARBINARY :
result = REDSHIFT_DATATYPE_VARCHAR_MAX;
break;
case TINYBLOB :
result = REDSHIFT_DATATYPE_VARCHAR_MAX;
break;
case BLOB :
result = REDSHIFT_DATATYPE_VARCHAR_MAX;
break;
case MEDIUMBLOB :
result = REDSHIFT_DATATYPE_VARCHAR_MAX;
break;
case LONGBLOB :
result = REDSHIFT_DATATYPE_VARCHAR_MAX;
break;
case ENUM :
result = REDSHIFT_DATATYPE_VARCHAR + bracket(255 * 2);
break;
case SET :
result = REDSHIFT_DATATYPE_VARCHAR + bracket(255 * 2);
break;
case DATE :
result = REDSHIFT_DATATYPE_DATE;
break;
case TIME :
result = REDSHIFT_DATATYPE_VARCHAR + bracket(10 * 4);
break;
case DATETIME :
result = REDSHIFT_DATATYPE_TIMESTAMP;
break;
case TIMESTAMP :
result = REDSHIFT_DATATYPE_TIMESTAMP;
break;
case YEAR :
result = REDSHIFT_DATATYPE_VARCHAR + bracket(4 * 4);
break;
}
return result;
}
/**
* 括弧で囲む
* @param val
* @return 括弧で囲んだval
*/
private static String bracket(Object val) {
if (val == null || "".equals(val) || "0".equals(val)) {
return "";
}
return "(" + val +")";
}
}
ColumnDefinitionObject.java
package jp.classmethod.app;
/**
* カラム定義クラス
*/
public class ColumnDefinitionObject {
/**
* コンストラクタ
* @param columnName
*/
public ColumnDefinitionObject(String columnName) {
this.columnName = columnName;
}
/**
* MySQLのデータ型
*/
public enum MySQLDataType {
TINYINT("TINYINT"),
SMALLINT("SMALLINT"),
MEDIUMINT("MEDIUMINT"),
INT("INT"),
BIGINT("BIGINT"),
DECIMAL("DECIMAL"),
FLOAT("FLOAT"),
DOUBLE("DOUBLE"),
CHAR("CHAR"),
VARCHAR("VARCHAR"),
TINYTEXT("TINYTEXT"),
TEXT("TEXT"),
MEDIUMTEXT("MEDIUMTEXT"),
LONGTEXT("LONGTEXT"),
BINARY("BINARY"),
VARBINARY("VARBINARY"),
TINYBLOB("TINYBLOB"),
BLOB("BLOB"),
MEDIUMBLOB("MEDIUMBLOB"),
LONGBLOB("LONGBLOB"),
ENUM("ENUM"),
SET("SET"),
DATE("DATE"),
TIME("TIME"),
DATETIME("DATETIME"),
TIMESTAMP("TIMESTAMP"),
YEAR("YEAR");
private String dataType;
private MySQLDataType(String dataType) {
this.dataType = dataType;
}
public boolean compareToString(String val) {
return this.dataType.equals(val.toUpperCase().toString());
}
}
/** カラム名 */
private String columnName;
/** データ型 */
private MySQLDataType dataType;
/** 桁数 */
private String length;
/** 符号の有無 */
private boolean unsigend;
/**
* カラム名を取得する
* @return カラム名
*/
public String getColumnName() {
return columnName;
}
/**
* カラム名を取得する
* @return カラム名
*/
public MySQLDataType getDataType() {
return dataType;
}
/**
* 桁数を取得する
* @return カラム名
*/
public String getLength() {
if (length == null || "".equals(length)) {
return "0";
}
return length;
}
/**
* 符号の有無を取得する
* @return カラム名
*/
public boolean isUnsigned() {
return unsigend;
}
/**
* データ型を設定する
* @param dataType
*/
public void setDataType(String dataType) {
for (MySQLDataType mySqlDataType : MySQLDataType.values()) {
if (mySqlDataType.compareToString(dataType)) {
this.dataType = mySqlDataType;
break;
}
}
}
/**
* 符号の有無を設定する
* @param unsigned
*/
public void setUnsigned(boolean unsigned) {
this.unsigend =unsigned;
}
/**
* 桁数を設定する
* @param unsigned
*/
public void setLength(String length) {
this.length = length;
}
}
先ほどのSQLをこのプログラムを使用してRedshift用に変換した結果は以下のようになります。
CREATE TABLE "employees" ("id" BIGINT, "name" VARCHAR(1020), "age" SMALLINT, "hire_date" DATE, "created_at" TIMESTAMP, "updated_at" TIMESTAMP);
見て頂ければわかるようにNOT NULL、主キー、外部キーなどの制約は無視しています。 必要であれば、ソースをちょこっと修正していただければ対応できると思います。