insert into products (name, price, product_no) values ('Cheese', 9.99, 1);
(2)仅指定列值
insert into products values (1, 'Cheese', 9.99);
(3)使用SELECT语句
insert into products select * from tmp_products where price < 100;
insert into products (name, price, product_no) select * from tmp_products where price < 100;
如果列有缺省值,可以在插入语句中省略该列名而使用缺省值。
insert into products (name, product_no) select name, product_no from tmp_products where price < 100;
(4)显式一次插入多行
db1=# create table t (a int);
CREATE TABLE
db1=# insert into t values (1),(2),(3);
INSERT 0 3
db1=# select * from t;
a
---
1
2
3
(3 rows)
如果需要快速插入大量数据,最好使用后面介绍的外部表或COPY命令,这些数据装载机制比INSERT更有效。
db1=# -- 整理pg_class系统表
db1=# vacuum pg_class;
VACUUM
db1=# -- 整理并分析pg_class系统表
db1=# vacuum analyze pg_class;
VACUUM
db1=# -- 整理并分析pg_class系统表的指定列
db1=# vacuum analyze pg_class (relname,relam,relpages,reltuples,relkind,relnatts,relacl,reloptions);
VACUUM
(2)配置空余空间映射
db1=# vacuum full pg_class;
NOTICE: 'VACUUM FULL' is not safe for large tables and has been known to yield unpredictable runtimes.
HINT: Use 'VACUUM' instead.
VACUUM
free space map的大小由以下服务器配置参数所控制:
[gpadmin@hdp3 ~]$ hawq config -s max_fsm_pages
GUC : max_fsm_pages
Value : 200000
max_fsm_relations(整数)设置free space map跟踪的最大表数。每个表槽位占用7字节左右的共享内存。缺省值为1000。设置该参数后需要重启HAWQ使其生效。
[gpadmin@hdp3 ~]$ hawq config -s max_fsm_relations
GUC : max_fsm_relations
Value : 1000
3. 其它操作
[gpadmin@hdp3 ~]$ hawq config -s gp_external_max_segs
GUC : gp_external_max_segs
Value : 64
用户可能需要设置虚拟段的数量,例如一些用于处理外部表数据文件,一些执行其它的数据库处理。hawq_rm_nvseg_perquery_perseg_limit和hawq_rm_nvseg_perquery_limit参数控制并行虚拟段的数量,它们限制集群中一个gpfdist外部表上执行查询时使用的最大虚拟段数。
[gpadmin@hdp4 ~]$ gpfdist &
[gpadmin@hdp4 ~]$ gpfdist -d /home/gpadmin/load_data/ -p 8081 -l /home/gpadmin/log &
[gpadmin@hdp4 ~]$ gpfdist -d /home/gpadmin/load_data1/ -p 8081 -l /home/gpadmin/log1 &
[gpadmin@hdp4 ~]$ gpfdist -d /home/gpadmin/load_data2/ -p 8082 -l /home/gpadmin/log2 &
yum install apr
yum install libyaml
HAWQ没有提供停止gpfdist的特殊命令,直接使用kill停止gpfdist进程。
[gpadmin@hdp4 ~]$ ps -ef |grep gpfdist |grep -v grep | awk '{print $2}'|xargs kill -9
[gpadmin@hdp3 ~]$ wget http://gpfdist_hostname:port/filename
(2)创建gpfdist外部表
为了创建一个gpfdist外部表,需要指定输入文件的格式和外部数据源的位置。使用以下协议之一访问外部表数据源。一条CREATE EXTERNAL TABLE语句中使用的协议必须唯一,不能混用多个协议。[gpadmin@hdp4 ~]$ gpfdist -p 8081 -d /home/gpadmin/staging -l /home/gpadmin/log &
使用gpfdist协议创建只读外部表example1,文件以管道符(|)作为列分隔符。
db1=# create external table example1
db1=# ( name text, date date, amount float4, category text, desc1 text )
db1=# location ('gpfdist://hdp4:8081/*')
db1=# format 'text' (delimiter '|');
CREATE EXTERNAL TABLE
准备文本文件数据。
[gpadmin@hdp4 unload_data1]$ cd /home/gpadmin/staging
[gpadmin@hdp4 staging]$ more a.txt
aaa|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
[gpadmin@hdp4 staging]$ more b.txt
aaa|2017-03-01|200.1|aaa|aaa
bbb|2017-03-02|200.2|bbb|bbb
查询外部表。
db1=# select * from example1;
name | date | amount | category | desc1
------+------------+--------+----------+-------
aaa | 2017-01-01 | 100.1 | aaa | aaa
bbb | 2017-01-02 | 100.2 | bbb | bbb
aaa | 2017-03-01 | 200.1 | aaa | aaa
bbb | 2017-03-02 | 200.2 | bbb | bbb
(4 rows)
例2:多gpfdist实例外部表
在hdp3和hdp4上分别启动一个gpfdist实例。[gpadmin@hdp3 ~]$ gpfdist -p 8081 -d /home/gpadmin/staging -l /home/gpadmin/log &
[gpadmin@hdp4 ~]$ gpfdist -p 8081 -d /home/gpadmin/staging -l /home/gpadmin/log &
使用gpfdist协议创建只读外部表example2,文件以管道符(|)作为列分隔符,' '作为NULL。
db1=# create external table example2
db1-# ( name text, date date, amount float4, category text, desc1 text )
db1-# location ('gpfdist://hdp3:8081/*.txt', 'gpfdist://hdp4:8081/*.txt')
db1-# format 'text' ( delimiter '|' null ' ') ;
CREATE EXTERNAL TABLE
查询外部表,因为gpfdist://hdp3:8081/*.txt不存在而报错。
db1=# select * from example2;
ERROR: http response code 404 from gpfdist (gpfdist://hdp3:8081/*.txt): HTTP/1.0 404 file not found (url.c:306) (seg0 hdp4:40000 pid=53784) (dispatcher.c:1801)
db1=#
将外部文件复制到hdp3的相关目录下。
[gpadmin@hdp4 staging]$ scp *.txt hdp3://home/gpadmin/staging/
再次查询外部表,可以正确读取全部外部文件数据。
db1=# select * from example2;
name | date | amount | category | desc1
------+------------+--------+----------+-------
aaa | 2017-01-01 | 100.1 | aaa | aaa
bbb | 2017-01-02 | 100.2 | bbb | bbb
aaa | 2017-03-01 | 200.1 | aaa | aaa
bbb | 2017-03-02 | 200.2 | bbb | bbb
aaa | 2017-01-01 | 100.1 | aaa | aaa
bbb | 2017-01-02 | 100.2 | bbb | bbb
aaa | 2017-03-01 | 200.1 | aaa | aaa
bbb | 2017-03-02 | 200.2 | bbb | bbb
(8 rows)
例3:带有错误日志的单gpfdist实例外部表
缺省在访问外部表时只要遇到一行格式错误的数据,就会立即返回错误,并导致查询失败。下面的语句设置了SEGMENT REJECT LIMIT的值,只有当一个segment上的错误数大于等于5时,整个外部表操作才会失败,并且不处理任何行。而当错误数小于5时,会将被拒绝的行写入一个错误表errs,其它数据行还可以正常返回。db1=# create external table example3
db1-# ( name text, date date, amount float4, category text, desc1 text )
db1-# location ('gpfdist://hdp3:8081/*.txt', 'gpfdist://hdp4:8081/*.txt')
db1-# format 'text' ( delimiter '|' null ' ')
db1-# log errors into errs segment reject limit 5;
NOTICE: Error table "errs" does not exist. Auto generating an error table with the same name
CREATE EXTERNAL TABLE
db1=# \d errs
Append-Only Table "public.errs"
Column | Type | Modifiers
----------+--------------------------+-----------
cmdtime | timestamp with time zone |
relname | text |
filename | text |
linenum | integer |
bytenum | integer |
errmsg | text |
rawdata | text |
rawbytes | bytea |
Compression Type: None
Compression Level: 0
Block Size: 32768
Checksum: f
Distributed randomly
db1=#
准备一条格式错误的数据。
[gpadmin@hdp4 staging]$ more a.txt
aaa|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
bbb,2017-01-02,100.2,bbb,bbb
查询外部表,返回8条数据,错误数据进入了errs表。
db1=# select * from example3;
NOTICE: Found 1 data formatting errors (1 or more input rows). Rejected related input data.
name | date | amount | category | desc1
------+------------+--------+----------+-------
aaa | 2017-01-01 | 100.1 | aaa | aaa
bbb | 2017-01-02 | 100.2 | bbb | bbb
aaa | 2017-03-01 | 200.1 | aaa | aaa
bbb | 2017-03-02 | 200.2 | bbb | bbb
aaa | 2017-01-01 | 100.1 | aaa | aaa
bbb | 2017-01-02 | 100.2 | bbb | bbb
aaa | 2017-03-01 | 200.1 | aaa | aaa
bbb | 2017-03-02 | 200.2 | bbb | bbb
(8 rows)
db1=# \x
Expanded display is on.
db1=# select * from errs;
-[ RECORD 1 ]-----------------------------------------------------
cmdtime | 2017-04-05 15:23:19.579421+08
relname | example3
filename | gpfdist://hdp4:8081/*.txt [/home/gpadmin/staging/a.txt]
linenum | 3
bytenum |
errmsg | missing data for column "date"
rawdata | bbb,2017-01-02,100.2,bbb,bbb
rawbytes |
db1=#
准备5条错误数据。
[gpadmin@hdp4 staging]$ more a.txt
aaa|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
b1,2017-01-02,100.2,bbb,bbb
b2,2017-01-02,100.2,bbb,bbb
b3,2017-01-02,100.2,bbb,bbb
b4,2017-01-02,100.2,bbb,bbb
b5,2017-01-02,100.2,bbb,bbb
再次查询外部表,因为达到了错误上限,整条语句失败,没有数据被返回。
db1=# select * from example3;
ERROR: Segment reject limit reached. Aborting operation. Last error was: missing data for column "date" (seg16 hdp3:40000 pid=350431)
DETAIL: External table example3, line 7 of gpfdist://hdp4:8081/*.txt: "b5,2017-01-02,100.2,bbb,bbb"
db1=#
例4:gpfdist可写外部表
建立可写外部表,并插入一条数据。db1=# create writable external table example4 (name text, date date, amount float4, category text, desc1 text)
db1-# location ('gpfdist://hdp4:8081/sales.out', 'gpfdist://hdp3:8081/sales.out')
db1-# format 'text' ( delimiter '|' null ' ')
db1-# distributed by (name);
CREATE EXTERNAL TABLE
db1=# insert into example4 values ('aaa','2017-01-01',100.1,'aaa','aaa');
INSERT 0 1
结果只在hdp4上建立了文件/home/gpadmin/staging/sales.out,而hdp3并没有建立输出文件。
[gpadmin@hdp4 staging]$ more sales.out
aaa|2017-01-01|100.1|aaa|aaa
再次建立可写外部表,将gpfdist位置调换,把hdp3放前面,并插入一条数据。
db1=# drop external table example4;
DROP EXTERNAL TABLE
db1=# create writable external table example4 (name text, date date, amount float4, category text, desc1 text)
db1-# location ('gpfdist://hdp3:8081/sales.out', 'gpfdist://hdp4:8081/sales.out')
db1-# format 'text' ( delimiter '|' null ' ')
db1-# distributed by (name);
CREATE EXTERNAL TABLE
db1=# insert into example4 values ('aaa','2017-01-01',100.1,'aaa','aaa');
INSERT 0 1
这次只在hdp3上建立了文件/home/gpadmin/staging/sales.out。
[gpadmin@hdp3 staging]$ more sales.out
aaa|2017-01-01|100.1|aaa|aaa
在LOCATION子句中指定同一主机上的多个gpfdist实例,结果也是一样的。可见,在可写外部表上执行INSERT操作时,只在第一个gpfdist实例的位置上生成本地文件数据。
(1)基于命令的web外部表
用一个shell命令或脚本的输出定义基于命令的web表数据。在CREATE EXTERNAL WEB TABLE语句的EXECUTE子句指定需要执行的命令。外部表中的数据是命令运行时的数据。EXECUTE子句运行特定master或虚拟段上的shell命令或脚本。脚本必须是gpadmin用户可执行的,并且位于所有master和segment主机的相同位置上,虚拟段并行运行命令。CREATE EXTERNAL WEB TABLE output (output text)
EXECUTE 'PATH=/home/gpadmin/programs; export PATH; myprogram.sh'
ON MASTER
FORMAT 'TEXT';
下面的命令定义一个web表,在五个虚拟段上运行一个名为get_log_data.sh脚本文件。
CREATE EXTERNAL WEB TABLE log_output (linenum int, message text)
EXECUTE '/home/gpadmin/get_log_data.sh' ON 5
FORMAT 'TEXT' (DELIMITER '|');
资源管理器在运行时选取虚拟段。
(2)基于URL的Web外部表
基于URL的web表使用HTTP协议从web服务器访问数据,web表数据是动态的。在LOCATION子句中使用http://指定文件在web服务器上的位置。web数据文件必须在所有segment主机能够访问的web服务器上。URL的数量对应访问该web表时并行的最少虚拟段数量。下面的例子定义了一个从多个URL获取数据的web表。CREATE EXTERNAL WEB TABLE ext_expenses (
name text, date date, amount float4, category text, description text)
LOCATION ('http://hdp1/sales/file.csv',
'http://hdp1/exec/file.csv',
'http://hdp1/finance/file.csv',
'http://hdp1/ops/file.csv',
'http://hdp1/marketing/file.csv',
'http://hdp1/eng/file.csv'
)
FORMAT 'CSV';
(3)基于Web的外部表示例
例5:执行脚本的可读Web外部表db1=# create external web table example5 (linenum int, message text)
db1-# execute '/home/gpadmin/get_log_data.sh' on 5
db1-# format 'text' (delimiter '|');
CREATE EXTERNAL TABLE
HAWQ集群中每台主机的相同位置上都必须有同一个可执行的脚本,否则查询会报错,如hdp1上没有/home/gpadmin/get_log_data.sh文件。
db1=# select * from example5;
ERROR: external table example5 command ended with error. sh: /home/gpadmin/get_log_data.sh: No such file or directory (seg0 hdp1:40000 pid=360600)
DETAIL: Command: execute:/home/gpadmin/get_log_data.sh
对该外部表的查询会返回每个虚拟段输出的并集,例如,get_log_data.sh脚本内容如下:
#!/bin/bash
echo "1|aaa"
echo "2|bbb"
则该表将返回10条(每个虚拟段两条,五个虚拟段)数据:
db1=# select * from example5;
linenum | message
---------+---------
1 | aaa
2 | bbb
1 | aaa
2 | bbb
1 | aaa
2 | bbb
1 | aaa
2 | bbb
1 | aaa
2 | bbb
(10 rows)
执行查询时,资源管理器最少分配5个虚拟段。如果建表时指定的虚拟段数超过了允许的最大值,表仍然可以建立,但查询时会报错。
db1=# drop external web table example5;
DROP EXTERNAL TABLE
db1=# create external web table example5 (linenum int, message text)
db1-# execute '/home/gpadmin/get_log_data.sh' on 100
db1-# format 'text' (delimiter '|');
CREATE EXTERNAL TABLE
db1=# select * from example5;
ERROR: failed to acquire resource from resource manager, minimum expected number of virtual segment 100 is more than maximum possible number 64 in queue pg_default (pquery.c:804)
例6:执行脚本的可写web外部表
db1=# create writable external web table example6
db1-# (name text, date date, amount float4, category text, desc1 text)
db1-# execute 'PATH=/home/gpadmin/programs; export PATH; myprogram1.sh' on 6
db1-# format 'text' (delimiter '|')
db1-# distributed randomly;
CREATE EXTERNAL TABLE
myprogram1.sh的内容如下:
#!/bin/bash
while read line
do
echo "File:${line}" >> /home/gpadmin/programs/a.txt
done
向外部表中插入数据。
db1=# insert into example6 values ('aaa','2017-01-01',100.1,'aaa','aaa');
INSERT 0 1
db1=# insert into example6 values ('bbb','2017-02-01',200.1,'bbb','');
INSERT 0 1
插入的数据通过管道输出给myprogram1.sh并执行,可以看到插入的数据被写入了a.txt文件。与可读表不同,该文件只在一个HAWQ主机上生成,并且每次插入数据只生成一行。
[gpadmin@hdp4 programs]$ more /home/gpadmin/programs/a.txt
File:aaa|2017-01-01|100.1|aaa|aaa
File:bbb|2017-02-01|200.1|bbb|
5. 使用外部表装载数据
CREATE TABLE expenses_travel (LIKE ext_expenses);
INSERT INTO expenses_travel
SELECT * FROM ext_expenses WHERE category='travel';
也可以在创建一个新表的同时装载数据:
CREATE TABLE expenses AS SELECT * FROM ext_expenses;
6. 外部表错误处理
可读外部表通常被用于选择数据装载到普通的HAWQ数据库表中。使用CREATE TABLE AS SELECT或INSERT INTO命令查询外部表数据。缺省时,如果数据包含错误,则整条命令失败,没有数据装载到目标数据库表中。INSERT INTO table_with_pkeys
SELECT DISTINCT * FROM external_table;
(1)使用单行错误隔离定义外部表
下面的例子在HAWQ表中记录错误记录,并设置错误行阈值为10。db1=# create external table ext_expenses ( name text, date date, amount float4, category text, desc1 text )
db1-# location ('gpfdist://hdp3:8081/*', 'gpfdist://hdp4:8081/*')
db1-# format 'text' (delimiter '|')
db1-# log errors into errs segment reject limit 10 rows;
CREATE EXTERNAL TABLE
(2)标识无效的CSV文件数据
如果一个CSV文件包含无效格式,错误日志表的rawdata字段可能包含多行。例如,某字段少了一个闭合的引号,后面所有的换行符都被认为是数据中内嵌的换行符。当这种情况发生时,HAWQ在一行数据达到64K时停止解析,并将此64K数据作为单行写入错误日志表,然后重置引号标记,继续读取数据。如果这种情况在处理装载时发生三次,载入文件被认为是无效的,整个装载失败,错误信息为“rejected N or more rows”。(3)表间迁移数据
可以使用CREATE TABLE AS或INSERT...SELECT语句将外部表或web外部表的数据装载到其它非外部表中,数据将根据外部表或web外部表的定义并行装载。如果一个外部表或web外部表数据源有错误,依赖于使用的错误隔离模式,有以下两种处理方式:(1)确认建立了运行hawq load的环境。
它需要依赖某些HAWQ安装中的文件,如gpfdist和Python,还需要通过网络访问所有HAWQ segment主机。(2)创建控制文件。
hawq load的控制文件是一个YAML(Yet Another Markup Language)格式的文件,在其中指定HAWQ连接信息,gpfdist配置信息,外部表选项、数据格式等。例如下面是一个名为my_load.yml的控制文件内容:
---
VERSION: 1.0.0.1
DATABASE: db1
USER: gpadmin
HOST: hdp3
PORT: 5432
GPLOAD:
INPUT:
- SOURCE:
LOCAL_HOSTNAME:
- hdp4
PORT: 8081
FILE:
- /home/gpadmin/staging/*.txt
- COLUMNS:
- name: text
- date: date
- amount: float4
- category: text
- desc1: text
- FORMAT: text
- DELIMITER: '|'
- ERROR_LIMIT: 25
- ERROR_TABLE: errlog
OUTPUT:
- TABLE: t1
- MODE: INSERT
SQL:
- BEFORE: "INSERT INTO audit VALUES('start', current_timestamp)"
- AFTER: "INSERT INTO audit VALUES('end', current_timestamp)"
注意:
LOCAL_HOSTNAME:
- hdp4-1
- hdp4-2
[gpadmin@hdp4 staging]$ more a.txt
aaa|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
[gpadmin@hdp4 staging]$ more b.txt
aaa|2017-03-01|200.1|aaa|aaa
bbb|2017-03-02|200.2|bbb|bbb
建立目标表和audit表。
db1=# create table t1 ( name text, date date, amount float4, category text, desc1 text );
CREATE TABLE
db1=# create table audit(flag varchar(10),st timestamp);
CREATE TABLE
执行hawq load。
[gpadmin@hdp4 ~]$ hawq load -f my_load.yml
2017-04-05 16:41:44|INFO|gpload session started 2017-04-05 16:41:44
2017-04-05 16:41:44|INFO|setting schema 'public' for table 't1'
2017-04-05 16:41:44|INFO|started gpfdist -p 8081 -P 8082 -f "/home/gpadmin/staging/*.txt" -t 30
2017-04-05 16:41:49|INFO|running time: 5.63 seconds
2017-04-05 16:41:49|INFO|rows Inserted = 4
2017-04-05 16:41:49|INFO|rows Updated = 0
2017-04-05 16:41:49|INFO|data formatting errors = 0
2017-04-05 16:41:49|INFO|gpload succeeded
[gpadmin@hdp4 ~]$
查询目标表和audit表。
db1=# select * from t1;
name | date | amount | category | desc1
------+------------+--------+----------+-------
aaa | 2017-01-01 | 100.1 | aaa | aaa
bbb | 2017-01-02 | 100.2 | bbb | bbb
aaa | 2017-03-01 | 200.1 | aaa | aaa
bbb | 2017-03-02 | 200.2 | bbb | bbb
(4 rows)
db1=# select * from audit;
flag | st
-------+----------------------------
start | 2017-04-05 16:41:44.736296
end | 2017-04-05 16:41:49.60153
(2 rows)
[gpadmin@hdp4 ~]$ psql -h hdp3 -d db1
psql (8.2.15)
Type "help" for help.
db1=# create table t2 (like t1);
NOTICE: Table doesn't have 'distributed by' clause, defaulting to distribution columns from LIKE table
CREATE TABLE
db1=# copy t2 from '/home/gpadmin/staging/a.txt' with delimiter '|';
COPY 2
db1=# select * from t2;
name | date | amount | category | desc1
------+------------+--------+----------+-------
aaa | 2017-01-01 | 100.1 | aaa | aaa
bbb | 2017-01-02 | 100.2 | bbb | bbb
(2 rows)
将表数据卸载到master的本地文件中,如果文件不存在则建立文件,否则会用卸载数据覆盖文件原来的内容。
db1=# copy (select * from t2) to '/home/gpadmin/staging/c.txt' with delimiter '|';
COPY 2
[gpadmin@hdp3 staging]$ more /home/gpadmin/staging/c.txt
bbb|2017-01-02|100.2|bbb|bbb
aaa|2017-01-01|100.1|aaa|aaa
(1)以单行错误隔离模式运行COPY。
缺省时,COPY在遇到第一个错误时就停止运行。如果数据含有错误,操作失败,没有数据被装载。如果以单行错误隔离模式运行COPY,HAWQ跳过含有错误格式的行,装载具有正确格式的行。如果数据违反了NOT NULL或CHECK等约束条件,操作仍然是‘all-or-nothing’输入模式,整个操作失败,没有数据被装载。[gpadmin@hdp3 staging]$ more a.txt
aaa|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
向表拷贝本地文件数据,错误日志表中新增一条数据。
db1=# create table t3 ( name text not null, date date, amount float4, category text, desc1 text );
CREATE TABLE
db1=# copy t3 from '/home/gpadmin/staging/a.txt'
db1-# with delimiter '|' log errors into errtable
db1-# segment reject limit 5 rows;
NOTICE: Error table "errtable" does not exist. Auto generating an error table with the same name
WARNING: The error table was created in the same transaction as this operation. It will get dropped if transaction rolls back even if bad rows are present
HINT: To avoid this create the error table ahead of time using: CREATE TABLE (cmdtime timestamp with time zone, relname text, filename text, linenum integer, bytenum integer, errmsg text, rawdata text, rawbytes bytea)
NOTICE: Dropping the auto-generated unused error table
HINT: Use KEEP in LOG INTO clause to force keeping the error table alive
COPY 2
db1=# select * from t3;
name | date | amount | category | desc1
------+------------+--------+----------+-------
bbb | 2017-01-02 | 100.2 | bbb | bbb
aaa | 2017-01-01 | 100.1 | aaa | aaa
(2 rows)
修改文件,制造一行格式错误的数据。
[gpadmin@hdp3 staging]$ more a.txt
aaa,2017-01-01,100.1,aaa,aaa
bbb|2017-01-02|100.2|bbb|bbb
再次拷贝数据。与卸载不同,装载会向表中追加数据。
db1=# copy t3 from '/home/gpadmin/staging/a.txt'
db1-# with delimiter '|' log errors into errtable
db1-# segment reject limit 5 rows;
NOTICE: Error table "errtable" does not exist. Auto generating an error table with the same name
WARNING: The error table was created in the same transaction as this operation. It will get dropped if transaction rolls back even if bad rows are present
HINT: To avoid this create the error table ahead of time using: CREATE TABLE (cmdtime timestamp with time zone, relname text, filename text, linenum integer, bytenum integer, errmsg text, rawdata text, rawbytes bytea)
NOTICE: Found 1 data formatting errors (1 or more input rows). Errors logged into error table "errtable"
COPY 1
db1=# select * from t3;
name | date | amount | category | desc1
------+------------+--------+----------+-------
bbb | 2017-01-02 | 100.2 | bbb | bbb
bbb | 2017-01-02 | 100.2 | bbb | bbb
aaa | 2017-01-01 | 100.1 | aaa | aaa
(3 rows)
db1=# \x
Expanded display is on.
db1=# select * from errtable;
-[ RECORD 1 ]----------------------------
cmdtime | 2017-04-05 16:56:02.402161+08
relname | t3
filename | /home/gpadmin/staging/a.txt
linenum | 1
bytenum |
errmsg | missing data for column "date"
rawdata | aaa,2017-01-01,100.1,aaa,aaa
rawbytes |
db1=#
再次修改文件,将name字段对应的数据置空,因为该字段定义为NOT NULL,所以违反约束,没有数据被拷贝。
[gpadmin@hdp3 staging]$ more a.txt
|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
db1=# truncate table t3;
TRUNCATE TABLE
db1=# copy t3 from '/home/gpadmin/staging/a.txt'
with delimiter '|' null as '' log errors into errtable
segment reject limit 5 rows;
ERROR: null value in column "name" violates not-null constraint (seg5 hdp1:40000 pid=370883)
CONTEXT: COPY t3, line 1: "|2017-01-01|100.1|aaa|aaa"
db1=# select * from t3;
name | date | amount | category | desc1
------+------+--------+----------+-------
(0 rows)
db1=# create writable external table unload_expenses
db1-# ( like t1 )
db1-# location ('gpfdist://hdp3:8081/expenses1.out',
db1(# 'gpfdist://hdp4:8081/expenses2.out')
db1-# format 'text' (delimiter ',');
NOTICE: Table doesn't have 'distributed by' clause, defaulting to distribution columns from LIKE table
CREATE EXTERNAL TABLE
可写外部表只允许INSERT操作。如果执行卸载的用户不是外部表的属主或超级用户,必须授予对外部表的INSERT权限。例如:
GRANT INSERT ON unload_expenses TO admin;
与例4不同,INSERT INTO 外部表 SELECT ... 语句中,外部表的输出文件只能在一个主机上,否则会报错。
db1=# insert into unload_expenses select * from t1;
ERROR: External table has more URLs then available primary segments that can write into them (seg0 hdp1:40000 pid=387379)
db1=# drop external table unload_expenses;
DROP EXTERNAL TABLE
db1=# create writable external table unload_expenses
db1-# ( like t1 )
db1-# location ('gpfdist://hdp3:8081/expenses1.out')
db1-# format 'text' (delimiter ',');
NOTICE: Table doesn't have 'distributed by' clause, defaulting to distribution columns from LIKE table
CREATE EXTERNAL TABLE
db1=# insert into unload_expenses select * from t1;
INSERT 0 4
查看导出的数据。
[gpadmin@hdp3 staging]$ more expenses1.out
aaa,2017-01-01,100.1,aaa,aaa
bbb,2017-01-02,100.2,bbb,bbb
aaa,2017-03-01,200.1,aaa,aaa
bbb,2017-03-02,200.2,bbb,bbb
[gpadmin@hdp3 staging]$
如上面的例6所示,也可以定义一个可写的外部web表,发送数据行到脚本或应用。脚本文件必须接收输入流,而且必须存在于所有HAWQ segment的主机的相同位置上,并可以被gpadmin用户执行。HAWQ系统中的所有segment都执行脚本,无论segment是否有需要处理的输出行。
gp_external_enable_exec = off
正如前面说明COPY命令时所看到的,COPY TO命令也可以用来卸载数据。它使用HAWQ master主机上的单一进程,从表中数据拷贝到HAWQ master主机上的一个文件(或标准输入)中。COPY TO命令重写整个文件,而不是追加记录。
(1)行格式
HAWQ需要数据行以换行符(LF,Line feed,ASCII值0x0A)、回车符(CR,Carriage return,ASCII值0x0D)或回车换行符(CR+LF,0x0D 0x0A)作为行分隔符。LF是类UNIX操作系统中标准的换行符。而Windows或Mac OS X使用CR或CR+LF。所有这些表示一个新行的特殊符号都被HAWQ作为行分隔符所支持。(2)列格式
文本文件和CSV文件缺省的列分隔符是分别是TAB(ASCII值为0x09)和逗号(ASCII值为0x2C)。在定义数据格式时,可以在CREATE EXTERNAL TABLE或COPY命令的DELIMITER子句,或者hawq load的控制文件中,声明一个单字符作为列分隔符。分隔符必须出现在字段值之间,不要在一行的开头或结尾放置分隔符。例如,下面使用管道符(|)作为列分隔符:data value 1|data value 2|data value 3
下面的建表命令显示以管道符作为列分隔符:
=# CREATE EXTERNAL TABLE ext_table (name text, date date)
LOCATION ('gpfdist://host:port/filename.txt)
FORMAT 'TEXT' (DELIMITER '|');
(3)表示空值
空值(NULL)表示一列中的未知数据。可以指定数据文件中的一个字符串表示空值。文本文件中表示空值的缺省字符串为\N,CSV文件中表示空值的缺省字符串为不带引号的空串(两个连续的逗号)。定义数据格式时,可以在CREATE EXTERNAL TABLE、COPY命令的NULL子句,或者hawq load的控制文件中,声明其它字符串表示空值。例如,如果不想区分空值与空串,就可以指定空串表示NULL。在使用HAWQ装载工具时,任何与声明的代表NULL的字符串相匹配的数据项都被认为是空值。(4)转义
列分隔符与行分隔符在数据文件中具有特殊含义。如果实际数据中也含有这个符号,必须对这些符号进行转义,以使HAWQ将它们作为普通数据而不是列或行的分隔符。文本文件缺省的转义符为一个反斜杠(\),CSV文件缺省的转义符为一个双引号(")。backslash = \\ | vertical bar = \| | exclamation point = !
可以对八进制或十六进制序列应用转义符。在装载进HAWQ时,转义后的值就是八进制或十六进制的ASCII码所表示的字符。例如,取址符(&)可以使用十六进制的(\0x26)或八进制的(\046)表示。
ESCAPE 'OFF'
该设置常用于输入数据中包含很多反斜杠(如web日志数据)的情况。
"Free trip to A,B","5.89","Special rate ""1.79"""
将字段值置于双引号中能保留字符串中头尾的空格。
(5)字符编码
在将一个Windows操作系统上生成的数据文件装载到HAWQ前,先使用dos2unix系统命令去除只有Windows使用的字符,如删除文件中的CR('\x0d')。(6)导入导出固定宽度数据
HAWQ的函数fixedwith_in和fixedwidth_out支持固定宽度的数据格式。这些函数的定义保存在$GPHOME/share/postgresql/cdb_external_extensions.sql文件中。下面的例子声明了一个自定义格式,然后调用fixedwidth_in函数指定为固定宽度的数据格式。db1=# create readable external table students (
db1(# name varchar(5), address varchar(10), age int)
db1-# location ('gpfdist://hdp4:8081/students.txt')
db1-# format 'custom' (formatter=fixedwidth_in, name='5', address='10', age='4');
CREATE EXTERNAL TABLE
db1=# select * from students;
name | address | age
-------+------------+-----
abcde | 1234567890 | 40
(1 row)
students.txt文件内容如下:
[gpadmin@hdp4 unload_data1]$ more students.txt
abcde12345678900040
文件中一行记录的字节必须与建表语句中字段字节数的和一致,如上例中一行必须严格为19字节,否则读取文件时会报错。再看一个含有中文的例子。
[gpadmin@hdp4 unload_data1]$ echo $LANG
zh_CN.UTF-8
[gpadmin@hdp4 unload_data1]$ more students.txt
中文中文中0040
操作系统和数据库的字符集都是UTF8,一个中文占用三个字节,记录一共19字节,满足读取条件。
db1=# select * from students;
name | address | age
------+---------+-----
中 | 中文中 | 40
(1 row)
name字段5字节,address字段10字节,理论上这两个字段都应该含有不完整的字符,但从查询结果看到,HAWQ在这里做了一些处理,name字段读取了一个完整的中文,address字段读取了三个完整的字符。而中间按字节分裂的中文字符被不可见字符所取代。
db1=# select char_length(name),octet_length(name),substr(name,1,1),substr(name,2,1) from students;
char_length | octet_length | substr | substr
-------------+--------------+--------+--------
2 | 5 | 中 |
(1 row)
db1=# select char_length(address),octet_length(address),substr(address,1,1),substr(address,2,1) from students;
char_length | octet_length | substr | substr
-------------+--------------+--------+--------
4 | 10 | | 中
(1 row)
以下选项指定如何读取固定宽度数据文件。
line_delim=E'\n'
line_delim=E'\r'
line_delim=E'\r\n'
line_delim='abc'
(2)pg_statistic系统表与pg_stats视图
pg_statistic系统表保存每个数据库表上最后执行ANALYZE操作的结果。每个表列有一行记录,它具有以下字段: SELECT n.nspname AS schemaname, c.relname AS tablename, a.attname, s.stanullfrac AS null_frac, s.stawidth AS avg_width, s.stadistinct AS n_distinct,
CASE 1
WHEN s.stakind1 THEN s.stavalues1
WHEN s.stakind2 THEN s.stavalues2
WHEN s.stakind3 THEN s.stavalues3
WHEN s.stakind4 THEN s.stavalues4
ELSE NULL::anyarray
END AS most_common_vals,
CASE 1
WHEN s.stakind1 THEN s.stanumbers1
WHEN s.stakind2 THEN s.stanumbers2
WHEN s.stakind3 THEN s.stanumbers3
WHEN s.stakind4 THEN s.stanumbers4
ELSE NULL::real[]
END AS most_common_freqs,
CASE 2
WHEN s.stakind1 THEN s.stavalues1
WHEN s.stakind2 THEN s.stavalues2
WHEN s.stakind3 THEN s.stavalues3
WHEN s.stakind4 THEN s.stavalues4
ELSE NULL::anyarray
END AS histogram_bounds,
CASE 3
WHEN s.stakind1 THEN s.stanumbers1[1]
WHEN s.stakind2 THEN s.stanumbers2[1]
WHEN s.stakind3 THEN s.stanumbers3[1]
WHEN s.stakind4 THEN s.stanumbers4[1]
ELSE NULL::real
END AS correlation
FROM pg_statistic s
JOIN pg_class c ON c.oid = s.starelid
JOIN pg_attribute a ON c.oid = a.attrelid AND a.attnum = s.staattnum
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE has_table_privilege(c.oid, 'select'::text);
新建的表没有统计信息。
(3)采样
在为大表计算统计信息时,HAWQ通过对基表采样数据的方式建立一个小表。如果基表是分区表,从全部分区中采样。样本表中的行数取决于由gp_analyze_relative_error系统配置参数指定的最大可接受错误数。该参数的缺省值是0.25(25%)。通常该值已经足够生成正确的查询计划。如果ANALYZE不能产生好的表列估算,可以通过调低该参数值,增加采样的数据量。注意,降低该值可能导致大量的采样数据,并明显增加分析时间。[gpadmin@hdp3 ~]$ hawq config -s gp_analyze_relative_error
GUC : gp_analyze_relative_error
Value : 0.25
(4)统计更新
不带参数运行ANALYZE会更新当前数据库中所有表的统计信息,这可能需要执行很长时间。所以最好分析单个表,在一个表中的数据大量修改后分析该表。也可以选择分析一个表列的子集,例如只分析连接、where子句、sort子句、group by子句、having子句中用到的列。db1=# analyze t1 (name,category);
ANALYZE
(5)分析分区和AO表
在分区表上运行ANALYZE命令时,它逐个分析每个叶级别的子分区。也可以只在新增或修改的分区文件上运行ANALYZE,避免分析没有变化的分区。[gpadmin@hdp3 ~]$ hawq config -s optimizer
GUC : optimizer
Value : on
[gpadmin@hdp3 ~]$ hawq config -s optimizer_analyze_root_partition
GUC : optimizer_analyze_root_partition
Value : on
每次运行ANALYZE或ANALYZE ROOTPARTITION时,根级别的统计信息被更新。analyzedb应用缺省更新根分区统计。当在父表上使用ANALYZE收集统计信息时,既会收集每个叶子分区的统计信息,又会收集分区表的全局统计信息。生成分区表的查询计划时两个统计信息都需要。如果所有子分区的统计信息都已经更新,ROOTPARTITION选项可用于只收集分区表的全局状态信息,这可以节省分析每个叶子分区所需要的时间。
db1=# analyze rootpartition t1;
WARNING: skipping "t1" --- cannot analyze a non-root partition using ANALYZE ROOTPARTITION
ANALYZE
db1=# analyze rootpartition sales;
ANALYZE
$ source /usr/local/hawq/greenplum_path.sh
$ hawq config -c default_statistics_target -v 50
$ hawq stop cluster -u
db1=# alter table t1 alter column desc1 set statistics 0;
ALTER TABLE
统计目标可以设置为0到1000之间的值,或者设置成-1,此时恢复使用系统缺省的统计目标值。
(2)自动收集统计信息
如果一个表没有统计信息,或者在表上执行的特定操作改变了大量的数据时,HAWQ可以在表上自动运行ANALYZE。对于分区表,自动统计收集仅当直接操作叶表时被触发,它仅分析叶表。[gpadmin@hdp3 ~]$ hawq config -s gp_autostats_mode
GUC : gp_autostats_mode
Value : ON_NO_STATS
on_change模式仅当影响的行数超过gp_autostats_on_change_threshold配置参数设置的阈值时触发ANALYZE,该参数的缺省值为2147483647。
[gpadmin@hdp3 ~]$ hawq config -s gp_autostats_on_change_threshold
GUC : gp_autostats_on_change_threshold
Value : 2147483647
on_change模式可能触发不希望的大的分析操作,严重的会使系统中断,因此不推荐在全局修改该参数,但可以在会话级设置,例如装载数据后自动分析。
$ hawq configure -c gp_autostats_mode -v none
如果想记录自动统计收集操作的日志,可以设置log_autostats系统配置蚕食参数为on。