Amazon Redshift: COPY処理時の『列名指定』について

2014.11.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Amazon Redshiftでは、COPY処理実施の際、テーブルの列名指定を行う事が可能です。当エントリでは小ネタとしてその点について少し深掘った感じで触れて見たいと思います。

COPY処理実行時、テーブル列名を指定しない場合

まずは検証用テーブル。それぞれ要素が揃った任意のテーブルを1つ、用意してみました。

CREATE TABLE public.spec_table (
  id INT NOT NULL,
  name VARCHAR(30) NOT NULL,
  sales BIGINT NOT NULL,
  valid_flg CHAR(1) NOT NULL,
  update_date DATE,
  update_user CHAR(20)
);

通常であればこれに対応するファイルは以下の様なものとなり、

normal.csv

ID,NAME,SALES,VALID_FLG,UPDATE_DATE,UPDATE_USER
1,AAAAA,10000,1,2014/10/28 00:00:00,tesruser_aaa
2,BBBBB,20000,0,2014/10/28 00:00:00,tesruser_bbb
3,CCCCC,30000,1,2014/10/28 00:00:00,tesruser_ccc
4,DDDDD,40000,0,2014/10/28 00:00:00,tesruser_ddd
5,EEEEE,50000,1,2014/10/28 00:00:00,tesruser_eee

こんな感じのCOPY文で投入が完了します。

COPY public.spec_table FROM 's3://<BUCKET_NAME>/<FOLDER_NAME>/normal.csv'
CREDENTIALS 'aws_access_key_id=XXXXXXXXXX;aws_secret_access_key=YYYYYYYYYY'
CSV
DATEFORMAT 'YYYY/MM/DD HH24:MI:SS'
IGNOREHEADER  AS 1;

COPY処理実行時、テーブル列名を指定する場合(テーブル列数>ファイルの項目列数)

冒頭で言及した様に、実はCOPY文、以下の様にテーブル列名を指定する事が出来ます。

COPY public.spec_table 
(ID, NAME, SALES, VALID_FLG, UPDATE_DATE, UPDATE_USER)
FROM 's3://<BUCKET_NAME>/<FOLDER_NAME>/normal.csv'
CREDENTIALS 'aws_access_key_id=XXXXXXXXXX;aws_secret_access_key=YYYYYYYYYY'
CSV
DATEFORMAT 'YYYY/MM/DD HH24:MI:SS'
IGNOREHEADER  AS 1;

上記SQL文の場合ですとファイルの項目名とテーブル名の列が完全に一致してますのであんまり旨味は無いですが、これまで運用していたクラスタのテーブルに対して仕様変更が発生、仮にファイルがこういう形(テーブル名の項目数>ファイルの項目列数、という状況)で提供されるようになり、ファイル側の変更がオンプレミス側で厳しい状況にある、となった場合、

not-enough.csv

ID,NAME,SALES,VALID_FLG
101,AAAAA,10000,1
102,BBBBB,20000,0
103,CCCCC,30000,1
104,DDDDD,40000,0
105,EEEEE,50000,1

こういうふうに項目を指定する事で、テーブルに投入する事が出来ます。ファイルに関する修正・ETLを施す事無く、COPY文実行時のタイミングでその変更を吸収出来るわけです。

COPY public.spec_table FROM 's3://<BUCKET_NAME>/<FOLDER_NAME>/normal.csv'
(ID, NAME, SALES, VALID_FLG)
FROM 's3://tableau-labo-virginia/not-enough.csv'
CREDENTIALS 'aws_access_key_id=XXXXXXXXXX;aws_secret_access_key=YYYYYYYYYY'
CSV
DATEFORMAT 'YYYY/MM/DD HH24:MI:SS'
IGNOREHEADER  AS 1;

指定の無かった項目についてはNULL値が設定されます。

# SELECT * FROM public.spec_table ORDER BY id;
 id  | name  | sales | valid_flg | update_date | update_user 
-----+-------+-------+-----------+-------------+-------------
 101 | AAAAA | 10000 | 1         |             | 
 102 | BBBBB | 20000 | 0         |             | 
 103 | CCCCC | 30000 | 1         |             | 
 104 | DDDDD | 40000 | 0         |             | 
 105 | EEEEE | 50000 | 1         |             | 
(5 rows)

ちなみにCREATE TABLE文で以下の様にNOT NULL制約が設定されている項目に対して上記の様な項目指定無しで投入を試みると、

CREATE TABLE public.spec_table (
  id INT NOT NULL,
  name VARCHAR(30) NOT NULL,
  sales BIGINT NOT NULL,
  valid_flg CHAR(1) NOT NULL,
  update_date DATE NOT NULL,
  update_user CHAR(20) NOT NULL
);

以下の様に怒られてしまいました。デフォルト値を指定しなさいと指摘されているので、

ERROR:  NOT NULL column without DEFAULT must be included in column list

以下の様にデフォルト値をテーブル作成時に設定しておくと、4項目指定(ID, NAME, SALES, VALID_FLG)のCOPY文も通ります。

CREATE TABLE public.spec_table (
  id INT NOT NULL,
  name VARCHAR(30) NOT NULL,
  sales BIGINT NOT NULL,
  valid_flg CHAR(1) NOT NULL,
  update_date DATE NOT NULL DEFAULT '1999/12/31',
  update_user CHAR(20) NOT NULL DEFAULT 'default-user'
);

結果は以下の様な形に。

# SELECT * FROM public.spec_table ORDER BY id;
 id  | name  | sales | valid_flg | update_date |     update_user      
-----+-------+-------+-----------+-------------+----------------------
 101 | AAAAA | 10000 | 1         | 1999-12-31  | default-user        
 102 | BBBBB | 20000 | 0         | 1999-12-31  | default-user        
 103 | CCCCC | 30000 | 1         | 1999-12-31  | default-user        
 104 | DDDDD | 40000 | 0         | 1999-12-31  | default-user        
 105 | EEEEE | 50000 | 1         | 1999-12-31  | default-user        
(5 rows)

COPY処理実行時、テーブル列名を指定する場合(テーブル列数<ファイルの項目列数)

では上記とは逆のケースだとどうなるでしょうか。以下6項目で構成されているテーブルに対し、

CREATE TABLE public.spec_table (
  id INT NOT NULL,
  name VARCHAR(30) NOT NULL,
  sales BIGINT NOT NULL,
  valid_flg CHAR(1) NOT NULL,
  update_date DATE NOT NULL DEFAULT '1999/12/31 23:59:59',
  update_user CHAR(20) NOT NULL DEFAULT 'default-user'
);

項目列数が余計に存在していた場合、どういう挙動を示すか?というようなケースです。

too-much.csv

ID,NAME,SALES,VALID_FLG,UPDATE_DATE,UPDATE_USER,EXTRA1,EXTRA2
20101,AAAAA,10000,1,2014/10/28 00:00:00,tesruser_aaa,xxx,yyy
20102,BBBBB,20000,0,2014/10/28 00:00:00,tesruser_bbb,xxx,yyy
20103,CCCCC,30000,1,2014/10/28 00:00:00,tesruser_ccc,xxx,yyy
20104,DDDDD,40000,0,2014/10/28 00:00:00,tesruser_ddd,xxx,yyy
20105,EEEEE,50000,1,2014/10/28 00:00:00,tesruser_eee,xxx,yyy

こちらについてはエラーとなってしまいました。

COPY public.spec_table FROM 's3://<BUCKET_NAME>/<FOLDER_NAME>/normal.csv'
(ID, NAME, SALES, VALID_FLG)
FROM 's3://tableau-labo-virginia/not-enough.csv'
CREDENTIALS 'aws_access_key_id=XXXXXXXXXX;aws_secret_access_key=YYYYYYYYYY'
CSV
DATEFORMAT 'YYYY/MM/DD HH24:MI:SS'
IGNOREHEADER  AS 1;
ERROR:  Load into table 'spec_table' failed.  Check 'stl_load_errors' system table for details.

エラーを見るとそのまんま、項目が多いよ!と怒られてしまいました。この辺りを吸収するオプション指定も無さそうなので、テーブル名に対してファイルの項目名が多いような場合はやはりファイル生成の時点で項目を合わせる、若しくは別テーブルに一旦ロードしておいて、そのテーブルから必要な項目を抜き出した形で投入(INSERT 〜 SELECT)するしか無さそうですね。

# SELECT
#   TRIM(line_number) AS line_number,
#   TRIM(raw_line) AS raw_line,
#   TRIM(err_code) AS err_code,
#   TRIM(err_reason) AS err_reason
# FROM
#   stl_load_errors ORDER BY starttime DESC LIMIT 1;
 line_number |                           raw_line                           | err_code |      err_reason       
-------------+--------------------------------------------------------------+----------+-----------------------
 2           | 20101,AAAAA,10000,1,2014/10/28 00:00:00,tesruser_aaa,xxx,yyy | 1202     | Extra column(s) found
(1 row)

まとめ

以上、COPY文実行時に於ける列名指定オプションに関する解説でした。通常であれば投入ファイルの列名とテーブルの列名は一致していると思われますので今回の様なオプション指定もそこまで出番が無いのかなとは思いますが、こういった利用方法もあるというのを頭の片隅に置いといて頂けると幸いです。こちらからは以上です。