PostgreSQL学习手册 1-5
来源:互联网 发布:网络信息安全事件 编辑:程序博客网 时间:2024/05/20 06:56
PostgreSQL学习手册(一) 数据表
对于任何一种关系型数据库而言,表都是数据存储的最核心、最基础的对象单元。现在就让我们从这里起步吧。
1. 创建表:CREATE TABLE products (
product_no integer,
name text,
price numeric
);
2. 删除表:
DROP TABLE products;
3. 创建带有缺省值的表:
CREATE TABLE products (
product_no integer,
name text,
price numeric DEFAULT 9.99 --DEFAULT是关键字,其后的数值9.99是字段price的默认值。
);
CREATE TABLE products (
product_no SERIAL, --SERIAL类型的字段表示该字段为自增字段,完全等同于Oracle中的Sequence。
name text,
price numeric DEFAULT 9.99
);
输出为:
NOTICE: CREATE TABLE will create implicit sequence "products_product_no_seq" for serial column "products.product_no"
4. 约束:
检查约束是表中最为常见的约束类型,它允许你声明在某个字段里的数值必须满足一个布尔表达式。不仅如此,我们也可以声明表级别的检查约束。
CREATE TABLE products (
product_no integer,
name text,
--price字段的值必须大于0,否则在插入或修改该字段值是,将引发违规错误。还需要说明的是,该检查约束
--是匿名约束,即在表定义时没有显示命名该约束,这样PostgreSQL将会根据当前的表名、字段名和约束类型,
--为该约束自动命名,如:products_price_check。
price numeric CHECK (price > 0)
);
CREATE TABLE products (
product_no integer,
name text,
--该字段的检查约束被显式命名为positive_price。这样做的好处在于今后维护该约束时,可以根据该名进行直接操作。
price numeric CONSTRAINT positive_price CHECK (price > 0)
);
下面的约束是非空约束,即约束的字段不能插入空值,或者是将已有数据更新为空值。
CREATE TABLE products (
product_no integer NOT NULL,
name text NOT NULL,
price numeric
);
如果一个字段中存在多个约束,在定义时可以不用考虑约束的声明顺序。
CREATE TABLE products (
product_no integer NOT NULL,
name text NOT NULL,
price numeric NOT NULL CHECK (price > 0)
);
唯一性约束,即指定的字段不能插入重复值,或者是将某一记录的值更新为当前表中的已有值。
CREATE TABLE products (
product_no integer UNIQUE,
name text,
price numeric
);
CREATE TABLE products (
product_no integer,
name text,
price numeric,
UNIQUE (product_no)
);
为表中的多个字段定义联合唯一性。
CREATE TABLE example (
a integer,
b integer,
c integer,
UNIQUE (a, c)
);
为唯一性约束命名。
CREATE TABLE products (
product_no integer CONSTRAINT must_be_different UNIQUE,
name text,
price numeric
);
在插入数据时,空值(NULL)之间被视为不相等的数据,因此对于某一唯一性字段,可以多次插入空值。然而需要注意的是,这一规则并不是被所有数据库都遵守,因此在进行数据库移植时可能会造成一定的麻烦。
5. 主键和外键:
从技术上来讲,主键约束只是唯一约束和非空约束的组合。
CREATE TABLE products (
product_no integer PRIMARY KEY, --字段product_no被定义为该表的唯一主键。
name text,
price numeric
);
和唯一性约束一样,主键可以同时作用于多个字段,形成联合主键:
CREATE TABLE example (
a integer,
b integer,
c integer,
PRIMARY KEY (b, c)
);
外键约束声明一个字段(或者一组字段)的数值必须匹配另外一个表中某些行出现的数值。 我们把这个行为称做两个相关表之间的参考完整性。
CREATE TABLE orders (
order_id integer PRIMARY KEY, --该表也可以有自己的主键。
--该表的product_no字段为上面products表主键(product_no)的外键。
product_no integer REFERENCES products(product_no),
quantity integer
);
CREATE TABLE t1 (
a integer PRIMARY KEY,
b integer,
c integer,
--该外键的字段数量和被引用表中主键的数量必须保持一致。
FOREIGN KEY (b, c) REFERENCES example (b, c)
);
当多个表之间存在了主外键的参考性约束关系时,如果想删除被引用表(主键表)中的某行记录,由于该行记录的主键字段值可能正在被其引用表(外键表)中某条记录所关联,所以删除操作将会失败。如果想完成此操作,一个显而易见的方法是先删除引用表中和该记录关联的行,之后再删除被引用表中的该行记录。然而需要说明的是,PostgreSQL为我们提供了更为方便的方式完成此类操作。
CREATE TABLE products (
product_no integer PRIMARY KEY,
name text,
price numeric
);
CREATE TABLE orders (
order_id integer PRIMARY KEY,
shipping_address text
);
CREATE TABLE order_items (
product_no integer REFERENCES products ON DELETE RESTRICT, --限制选项
order_id integer REFERENCES orders ON DELETE CASCADE, --级联删除选项
quantity integer,
PRIMARY KEY (product_no, order_id)
);
限制和级联删除是两种最常见的选项。RESTRICT 禁止删除被引用的行。 NO ACTION 的意思是如果在检查约束的时候,如果还存在任何引用行,则抛出错误; 如果你不声明任何东西,那么它就是缺省的行为。(这两个选择的实际区别是,NO ACTION 允许约束检查推迟到事务的晚些时候,而 RESTRICT 不行。) CASCADE声明在删除一个被引用的行的时候,引用它的行也会被自动删除掉。 在外键字段上的动作还有两个选项: SET NULL 和 SET DEFAULT。 这样会导致在被引用行删除的时候,引用它们的字段分别设置为空或者缺省值。 请注意这些选项并不能让你逃脱被观察和约束的境地。比如,如果一个动作声明 SET DEFAULT,但是缺省值并不能满足外键,那么动作就会失败。类似ON DELETE,还有ON UPDATE 选项,它是在被引用字段修改(更新)的时候调用的。可用的动作是一样的。
二、系统字段:
PostgreSQL的每个数据表中都包含几个隐含定义的系统字段。因此,这些名字不能用于用户定义的字段名。这些系统字段的功能有些类似于Oracle中的rownum和rowid等。
oid: 行的对象标识符(对象ID)。这个字段只有在创建表的时候使用了WITH OIDS,或者是设置了配置参数default_with_oids时出现。这个字段的类型是oid(和字段同名)。
tableoid: 包含本行的表的OID。这个字段对那些从继承层次中选取的查询特别有用,因为如果没有它的话,我们就很难说明一行来自哪个独立的表。tableoid可以和pg_class的oid字段连接起来获取表名字。
xmin: 插入该行版本的事务的标识(事务ID)。
cmin: 在插入事务内部的命令标识(从零开始)。
xmax: 删除事务的标识(事务ID),如果不是被删除的行版本,那么是零。
cmax: 在删除事务内部的命令标识符,或者是零。
ctid: 一个行版本在它所处的表内的物理位置。请注意,尽管ctid可以用于非常快速地定位行版本,但每次VACUUM FULL之后,一个行的ctid都会被更新或者移动。因此ctid是不能作为长期的行标识符的。
OID是32位的量,是在同一个集群内通用的计数器上赋值的。对于一个大型或者长时间使用的数据库,这个计数器是有可能重叠的。因此,假设OID是唯一的是非常错误的,除非你自己采取了措施来保证它们是唯一的。如果你需要标识表中的行,我们强烈建议使用序列号生成器。
三、表的修改:
1. 增加字段:
ALTER TABLE products ADD COLUMN description text;
新增的字段对于表中已经存在的行而言最初将先填充所给出的缺省值(如果你没有声明DEFAULT子句,那么缺省是空值)。
在新增字段时,可以同时给该字段指定约束。
ALTER TABLE products ADD COLUMN description text CHECK(description <> '');
2. 删除字段:
ALTER TABLE products DROP COLUMN description;
如果该表为被引用表,该字段为被引用字段,那么上面的删除操作将会失败。如果要想在删除被引用字段的同时级联的删除其所有引用字段,可以采用下面的语法形式。
ALTER TABLE products DROP COLUMN description CASCADE;
3. 增加约束:
ALTER TABLE products ADD CHECK(name <> ''); --增加一个表级约束
ALTER TABLE products ADD CONSTRAINT same_name UNIQUE(product_no);--增加命名的唯一性约束。
ALTER TABLE products ADD FOREIGN KEY(pdt_grp_id) REFERENCES pdt_grps; --增加外键约束。
ALTER TABLE products ALTER COLUMN product_no SET NOT NULL; --增加一个非空约束。
4. 删除约束:
ALTER TABLE products DROP CONSTRAINT some_name;
对于显示命名的约束,可以根据其名称直接删除,对于隐式自动命名的约束,可以通过psql的\d tablename来获取该约束的名字。和删除字段一样,如果你想删除有着被依赖关系的约束,你需要用CASCADE。一个例子是某个外键约束依赖被引用字段上的唯一约束或者主键约束。如:
MyTest=# \d products
Table "public.products"
Column | Type | Modifiers
------------+---------+-----------
product_no | integer |
name | text |
price | numeric |
Check constraints:
"positive_price" CHECK (price > 0::numeric)
和其他约束不同的是,非空约束没有名字,因此只能通过下面的方式删除:
ALTER TABLE products ALTER COLUMN product_no DROP NOT NULL;
5. 改变字段的缺省值:
在为已有字段添加缺省值时,不会影响任何表中现有的数据行, 它只是为将来INSERT命令改变缺省值。
ALTER TABLE products ALTER COLUMN price SET DEFAULT 7.77;
下面为删除缺省值:
ALTER TABLE products ALTER COLUMN price DROP DEFAULT
6. 修改字段的数据类型:
只有在字段里现有的每个项都可以用一个隐含的类型转换转换成新的类型时才可能成功。比如当前的数据都是整型,而转换的目标类型为numeric或 varchar,这样的转换一般都可以成功。与此同时,PostgreSQL还将试图把字段的缺省值(如果存在)转换成新的类型, 还有涉及该字段的任何约束。但是这些转换可能失败,或者可能生成奇怪的结果。 在修改某字段类型之前,你最好删除那些约束,然后再把自己手工修改过的添加上去。
ALTER TABLE products ALTER COLUMN price TYPE numeric(10,2);
7. 修改字段名:
ALTER TABLE products RENAME COLUMN product_no TO product_number;
8. 修改表名:
ALTER TABLE products RENAME TO items;
四、权限:
只有表的所有者才能修改或者删除表的权限。要赋予一个权限,我们使用GRANT命令,要撤销一个权限,使用REVOKE命令。
需要指出的是,PUBLIC是特殊"用户"可以用于将权限赋予系统中的每一个用户。在声明权限的位置写ALL则将所有的与该对象类型相关的权限都赋予出去。
GRANT UPDATE ON table_name TO user; --将表的更新权限赋予指定的user。
GRANT SELECT ON table_name TO GROUP group; --将表的select权限赋予指定的组。
REVOKE ALL ON table_name FROM PUBLIC; --将表的所有权限从Public撤销。
最初,只有对象所有者(或者超级用户)可以赋予或者撤销对象的权限。但是,我们可以赋予一个"with grant option"权限,这样就给接受权限的人以授予该权限给其它人的权限。如果授予选项后来被撤销,那么所有那些从这个接受者接受了权限的用户(直接或者通过级连的授权)都将失去该权限。
PostgreSQL学习手册(二) 模式(Schema)
一个数据库包含一个或多个命名的模式,模式又包含表。模式还包含其它命名的对象,包括数据类型、函数,以及操作符。同一个对象名可以在不同的模式里使用而不会导致冲突; 比如,schema1和myschema都可以包含叫做mytable的表。和数据库不同,模式不是严格分离的:一个用户可以访问他所连接的数据库中的任意模式中的对象,只要他有权限。我们需要模式有以下几个主要原因:
1). 允许多个用户使用一个数据库而不会干扰其它用户。
2). 把数据库对象组织成逻辑组,让它们更便于管理。
3). 第三方的应用可以放在不同的模式中,这样它们就不会和其它对象的名字冲突。
1. 创建模式:
CREATE SCHEMA myschema;
通过以上命令可以创建名字为myschema的模式,在该模式被创建后,其便可拥有自己的一组逻辑对象,如表、视图和函数等。
2. public模式:
在介绍后面的内容之前,这里我们需要先解释一下public模式。每当我们创建一个新的数据库时,PostgreSQL都会为我们自动创建该模式。当登录到该数据库时,如果没有特殊的指定,我们将以该模式(public)的形式操作各种数据对象,如:
CREATE TABLE products ( ... ) 等同于 CREATE TABLE public.products ( ... )
3. 权限:
缺省时,用户看不到模式中不属于他们所有的对象。为了让他们看得见,模式的所有者需要在模式上赋予USAGE权限。为了让用户使用模式中的对象,我们可能需要赋予额外的权限,只要是适合该对象的。PostgreSQL根据不同的对象提供了不同的权限类型,如:
GRANT ALL ON SCHEMA myschema TO public;
上面的ALL关键字将包含CREATE和USAGE两种权限。如果public模式拥有了myschema模式的CREATE权限,那么登录到该模式的用户将可以在myschema模式中创建任意对象,如:
CREATE TABLE myschema.products (
product_no integer,
name text,
price numeric CHECK (price > 0),
);
在为模式下的所有表赋予权限时,需要将权限拆分为各种不同的表操作,如:
ALTER DEFAULT PRIVILEGES IN SCHEMA myschema
GRANT INSERT, SELECT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON TABLES TO public;
在为模式下的所有Sequence序列对象赋予权限时,需要将权限拆分为各种不同的Sequence操作,如:
ALTER DEFAULT PRIVILEGES IN SCHEMA myschema
GRANT SELECT, UPDATE, USAGE ON SEQUENCES TO public;
在为模式下的所有函数赋予权限时,仅考虑执行权限,如:
ALTER DEFAULT PRIVILEGES IN SCHEMA myschema
GRANT EXECUTE ON FUNCTIONS TO public;
可以看出,通过以上方式在public模式下为myschema模式创建各种对象是极为不方便的。下面我们将要介绍另外一种方式,即通过role对象,直接登录并关联到myschema对象,之后便可以在myschema模式下直接创建各种所需的对象了。
CREATE ROLE myschema LOGIN PASSWORD '123456'; --创建了和该模式关联的角色对象。
CREATE SCHEMA myschema AUTHORIZATION myschema; --将该模式关联到指定的角色,模式名和角色名可以不相等。
在Linux Shell下,以myschema的角色登录到数据库MyTest,在密码输入正确后将成功登录到该数据库。
/> psql -d MyTest -U myschema
Password:
MyTest=> CREATE TABLE test(i integer);
CREATE TABLE
MyTest=> \d --查看该模式下,以及该模式有权限看到的tables信息列表。
List of relations
Schema | Name | Type | Owner
------------+---------+------+----------
myschema | test | table | myschema
(1 rows)
4. 删除模式:
DROP SCHEMA myschema;
如果要删除模式及其所有对象,请使用级联删除:
DROP SCHEMA myschema CASCADE;
5. 模式搜索路径:
我们在使用一个数据库对象时可以使用它的全称来定位对象,然而这样做往往也是非常繁琐的,每次都不得不键入 owner_name.object_name。PostgreSQL中提供了模式搜索路径,这有些类似于Linux中的$PATH环境变量,当我们执行一个Shell命令时,只有该命令位于$PATH的目录列表中,我们才可以通过命令名直接执行,否则就需要输入它的全路径名。PostgreSQL同样也通过查找一个搜索路径来判断一个表究竟是哪个表,这个路径是一个需要查找的模式列表。在搜索路径里找到的第一个表将被当作选定的表。如果在搜索路径中没有匹配表,那么就报告一个错误,即使匹配表的名字在数据库其它的模式中存在也如此。
在搜索路径中的第一个模式叫做当前模式。除了是搜索的第一个模式之外,它还是在CREATE TABLE没有声明模式名的时候,新建表所属于的模式。要显示当前搜索路径,使用下面的命令:
MyTest=> SHOW search_path;
search_path
----------------
"$user",public
(1 row)
可以将新模式加入到搜索路径中,如:
SET search_path TO myschema,public;
为搜索路径设置指定的模式,如:
SET search_path TO myschema; --当前搜索路径中将只是包含myschema一种模式。
PostgreSQL学习手册(三) 表的继承和分区
一、表的继承:
这个概念对于很多已经熟悉其他数据库编程的开发人员而言会多少有些陌生,然而它的实现方式和设计原理却是简单易懂,现在就让我们从一个简单的例子开始吧。
1. 第一个继承表:
CREATE TABLE cities ( --父表
name text,
population float,
altitude int
);
CREATE TABLE capitals ( --子表
state char(2)
) INHERITS (cities);
capitals表继承自cities表的所有属性。在PostgreSQL里,一个表可以从零个或多个其它表中继承属性,而且一个查询既可以引用父表中的所有行,也可以引用父表的所有行加上其所有子表的行,其中后者是缺省行为。
MyTest=# INSERT INTO cities values('Las Vegas', 1.53, 2174); --插入父表
INSERT 0 1
MyTest=# INSERT INTO cities values('Mariposa',3.30,1953); --插入父表
INSERT 0 1
MyTest=# INSERT INTO capitals values('Madison',4.34,845,'WI');--插入子表
INSERT 0 1
MyTest=# SELECT name, altitude FROM cities WHERE altitude > 500; --父表和子表的数据均被取出。
name | altitude
-----------+----------
Las Vegas | 2174
Mariposa | 1953
Madison | 845
(3 rows)
MyTest=# SELECT name, altitude FROM capitals WHERE altitude > 500; --只有子表的数据被取出。
name | altitude
---------+----------
Madison | 845
(1 row)
如果希望只从父表中提取数据,则需要在SQL中加入ONLY关键字,如:
MyTest=# SELECT name,altitude FROM ONLY cities WHERE altitude > 500;
name | altitude
-----------+----------
Las Vegas | 2174
Mariposa | 1953
(2 rows)
上例中cities前面的"ONLY"关键字表示该查询应该只对cities进行查找而不包括继承级别低于cities的表。许多我们已经讨论过的命令--SELECT,UPDATE和DELETE--支持这个"ONLY"符号。
在执行整表数据删除时,如果直接truncate父表,此时父表和其所有子表的数据均被删除,如果只是truncate子表,那么其父表的数据将不会变化,只是子表中的数据被清空。
MyTest=# TRUNCATE TABLE cities; --父表和子表的数据均被删除。
TRUNCATE TABLE
MyTest=# SELECT * FROM capitals;
name | population | altitude | state
------+------------+----------+-------
(0 rows)
2. 确定数据来源:
有时候你可能想知道某条记录来自哪个表。在每个表里我们都有一个系统隐含字段tableoid,它可以告诉你表的来源:
MyTest=# SELECT tableoid, name, altitude FROM cities WHERE altitude > 500;
tableoid | name | altitude
----------+-----------+----------
16532 | Las Vegas | 2174
16532 | Mariposa | 1953
16538 | Madison | 845
(3 rows)
以上的结果只是给出了tableoid,仅仅通过该值,我们还是无法看出实际的表名。要完成此操作,我们就需要和系统表pg_class进行关联,以通过tableoid字段从该表中提取实际的表名,见以下查询:
MyTest=# SELECT p.relname, c.name, c.altitude FROM cities c,pg_class p WHERE c.altitude > 500 and c.tableoid = p.oid;
relname | name | altitude
----------+-----------+----------
cities | Las Vegas | 2174
cities | Mariposa | 1953
capitals | Madison | 845
(3 rows)
3. 数据插入的注意事项:
继承并不自动从INSERT或者COPY中向继承级别中的其它表填充数据。在我们的例子里,下面的INSERT语句不会成功:
INSERT INTO cities (name, population, altitude, state) VALUES ('New York', NULL, NULL, 'NY');
我们可能希望数据被传递到capitals表里面去,但是这是不会发生的:INSERT总是插入明确声明的那个表。
4. 多表继承:
一个表可以从多个父表继承,这种情况下它拥有父表们的字段的总和。子表中任意定义的字段也会加入其中。如果同一个字段名出现在多个父表中,或者同时出现 在父表和子表的定义里,那么这些字段就会被"融合",这样在子表里面就只有一个这样的字段。要想融合,字段必须是相同的数据类型,否则就会抛出一个错误。 融合的字段将会拥有它所继承的字段的所有约束。
CREATE TABLE parent1 (FirstCol integer);
CREATE TABLE parent2 (FirstCol integer, SecondCol varchar(20));
CREATE TABLE parent3 (FirstCol varchar(200));
--子表child1将同时继承自parent1和parent2表,而这两个父表中均包含integer类型的FirstCol字段,因此child1可以创建成功。
CREATE TABLE child1 (MyCol timestamp) INHERITS (parent1,parent2);
--子表child2将不会创建成功,因为其两个父表中均包含FirstCol字段,但是它们的类型不相同。
CREATE TABLE child2 (MyCol timestamp) INHERITS (parent1,parent3);
--子表child3同样不会创建成功,因为它和其父表均包含FirstCol字段,但是它们的类型不相同。
CREATE TABLE child3 (FirstCol varchar(20)) INHERITS(parent1);
5. 继承和权限:
表访问权限并不会自动继承。因此,一个试图访问父表的用户还必须具有访问它的所有子表的权限,或者使用ONLY关键字只从父表中提取数据。在向现有的继承层次添加新的子表的时候,请注意给它赋予所有权限。
继承特性的一个严重的局限性是索引(包括唯一约束)和外键约束只施用于单个表,而不包括它们的继承的子表。这一点不管对引用表还是被引用表都是事实,因此在上面的例子里,如果我们声明cities.name为UNIQUE或者是一个PRIMARY KEY,那么也不会阻止capitals表拥有重复了名字的cities数据行。 并且这些重复的行缺省时在查询cities表的时候会显示出来。实际上,缺省时capitals将完全没有唯一约束,因此可能包含带有同名的多个行。你应该给capitals增加唯一约束,但是这样做也不会避免与cities的重复。类似,如果我们声明cities.name REFERENCES某些其它的表,这个约束不会自动广播到capitals。在这种条件下,你可以通过手工给capitals 增加同样的REFERENCES约束来做到这点。
二、分区表:
1. 概述分区表:
分区的意思是把逻辑上的一个大表分割成物理上的几块儿,分区可以提供若干好处:
1). 某些类型的查询性能可以得到极大提升。
2). 更新的性能也可以得到提升,因为表的每块的索引要比在整个数据集上的索引要小。如果索引不能全部放在内存里,那么在索引上的读和写都会产生更多的磁盘访问。
3). 批量删除可以用简单地删除某个分区来实现。
4). 将很少用的数据可以移动到便宜的、慢一些地存储介质上。
假设当前的数据库并不支持分区表,而我们的应用所需处理的数据量也非常大,对于这种应用场景,我们不得不人为的将该大表按照一定的规则,手工拆分成多个小表,让每个小表包含不同区间的数据。这样一来,我们就必须在数据插入、更新、删除和查询之前,先计算本次的指令需要操作的小表。对于有些查询而言,由于查询区间可能会跨越多个小表,这样我们又不得不将多个小表的查询结果进行union操作,以合并来自多个表的数据,并最终形成一个结果集返回给客户端。可见,如果我们正在使用的数据库不支持分区表,那么在适合其应用的场景下,我们就需要做很多额外的编程工作以弥补这一缺失。然而需要说明的是,尽管功能可以勉强应付,但是性能却和分区表无法相提并论。
目前PostgreSQL支持的分区形式主要为以下两种:
1). 范围分区: 表被一个或者多个键字字段分区成"范围",在这些范围之间没有重叠的数值分布到不同的分区里。比如,我们可以为特定的商业对象根据数据范围分区,或者根据标识符范围分区。
2). 列表分区: 表是通过明确地列出每个分区里应该出现那些键字值实现的。
2. 实现分区:
1). 创建"主表",所有分区都从它继承。
CREATE TABLE measurement ( --主表
city_id int NOT NULL,
logdate date NOT NULL,
peaktemp int,
);
2). 创建几个"子"表,每个都从主表上继承。通常,这些"子"表将不会再增加任何字段。我们将把子表称作分区,尽管它们就是普通的PostgreSQL表。
CREATE TABLE measurement_yy04mm02 ( ) INHERITS (measurement);
CREATE TABLE measurement_yy04mm03 ( ) INHERITS (measurement);
...
CREATE TABLE measurement_yy05mm11 ( ) INHERITS (measurement);
CREATE TABLE measurement_yy05mm12 ( ) INHERITS (measurement);
CREATE TABLE measurement_yy06mm01 ( ) INHERITS (measurement);
上面创建的子表,均以年、月的形式进行范围划分,不同年月的数据将归属到不同的子表内。这样的实现方式对于清空分区数据而言将极为方便和高效,即直接执行DROP TABLE语句删除相应的子表,之后在根据实际的应用考虑是否重建该子表(分区)。相比于直接DROP子表,PostgreSQL还提供了另外一种更为方便的方式来管理子表:
ALTER TABLE measurement_yy06mm01 NO INHERIT measurement;
和直接DROP相比,该方式仅仅是使子表脱离了原有的主表,而存储在子表中的数据仍然可以得到访问,因为此时该表已经被还原成一个普通的数据表了。这样对于数据库的DBA来说,就可以在此时对该表进行必要的维护操作,如数据清理、归档等,在完成诸多例行性的操作之后,就可以考虑是直接删除该表(DROP TABLE),还是先清空该表的数据(TRUNCATE TABLE),之后再让该表重新继承主表,如:
ALTER TABLE measurement_yy06mm01 INHERIT measurement;
3). 给分区表增加约束,定义每个分区允许的健值。同时需要注意的是,定义的约束要确保在不同的分区里不会有相同的键值。因此,我们需要将上面"子"表的定义修改为以下形式:
CREATE TABLE measurement_yy04mm02 (
CHECK ( logdate >= DATE '2004-02-01' AND logdate < DATE '2004-03-01')
) INHERITS (measurement);
CREATE TABLE measurement_yy04mm03 (
CHECK (logdate >= DATE '2004-03-01' AND logdate < DATE '2004-04-01')
) INHERITS (measurement);
...
CREATE TABLE measurement_yy05mm11 (
CHECK (logdate >= DATE '2005-11-01' AND logdate < DATE '2005-12-01')
) INHERITS (measurement);
CREATE TABLE measurement_yy05mm12 (
CHECK (logdate >= DATE '2005-12-01' AND logdate < DATE '2006-01-01')
) INHERITS (measurement);
CREATE TABLE measurement_yy06mm01 (
CHECK (logdate >= DATE '2006-01-01' AND logdate < DATE '2006-02-01')
) INHERITS (measurement);
4). 尽可能基于键值创建索引。如果需要,我们也同样可以为子表中的其它字段创建索引。
CREATE INDEX measurement_yy04mm02_logdate ON measurement_yy04mm02 (logdate);
CREATE INDEX measurement_yy04mm03_logdate ON measurement_yy04mm03 (logdate);
...
CREATE INDEX measurement_yy05mm11_logdate ON measurement_yy05mm11 (logdate);
CREATE INDEX measurement_yy05mm12_logdate ON measurement_yy05mm12 (logdate);
CREATE INDEX measurement_yy06mm01_logdate ON measurement_yy06mm01 (logdate);
5). 定义一个规则或者触发器,把对主表的修改重定向到适当的分区表。
如果数据只进入最新的分区,我们可以设置一个非常简单的规则来插入数据。我们必须每个月都重新定义这个规则,即修改重定向插入的子表名,这样它总是指向当前分区。
CREATE OR REPLACE RULE measurement_current_partition AS
ON INSERT TO measurement
DO INSTEAD
INSERT INTO measurement_yy06mm01 VALUES (NEW.city_id, NEW.logdate, NEW.peaktemp);
其中NEW是关键字,表示新数据字段的集合。这里可以通过点(.)操作符来获取集合中的每一个字段。
我们可能想插入数据并且想让服务器自动定位应该向哪个分区插入数据。我们可以用像下面这样的更复杂的规则集来实现这个目标。
CREATE RULE measurement_insert_yy04mm02 AS
ON INSERT TO measurement WHERE (logdate >= DATE '2004-02-01' AND logdate < DATE '2004-03-01')
DO INSTEAD
INSERT INTO measurement_yy04mm02 VALUES (NEW.city_id, NEW.logdate, NEW.peaktemp);
...
CREATE RULE measurement_insert_yy05mm12 AS
ON INSERT TO measurement WHERE (logdate >= DATE '2005-12-01' AND logdate < DATE '2006-01-01')
DO INSTEAD
INSERT INTO measurement_yy05mm12 VALUES (NEW.city_id, NEW.logdate, NEW.peaktemp);
CREATE RULE measurement_insert_yy06mm01 AS
ON INSERT TO measurement WHERE (logdate >= DATE '2006-01-01' AND logdate < DATE '2006-02-01')
DO INSTEAD
INSERT INTO measurement_yy06mm01 VALUES (NEW.city_id, NEW.logdate, NEW.peaktemp);
请注意每个规则里面的WHERE子句正好匹配其分区的CHECK约束。
可以看出,一个复杂的分区方案可能要求相当多的DDL。在上面的例子里我们需要每个月创建一次新分区,因此写一个脚本自动生成需要的DDL是明智的。除此之外,我们还不难推断出,分区表对于新数据的批量插入操作有一定的抑制,这一点在Oracle中也同样如此。
除了上面介绍的通过Rule的方式重定向主表的数据到各个子表,我们还可以通过触发器的方式来完成此操作,相比于基于Rule的重定向方法,基于触发器的方式可能会带来更好的插入效率,特别是针对非批量插入的情况。然而对于批量插入而言,由于Rule的额外开销是基于表的,而不是基于行的,因此效果会好于触发器方式。另一个需要注意的是,copy操作将会忽略Rules,如果我们想要通过COPY方法来插入数据,你只能将数据直接copy到正确的子表,而不是主表。这种限制对于触发器来说是不会造成任何问题的。基于Rule的重定向方式还存在另外一个问题,就是当插入的数据不在任何子表的约束中时,PostgreSQL也不会报错,而是将数据直接保留在主表中。
6). 添加新分区:
这里将介绍两种添加新分区的方式,第一种方法简单且直观,我们只是创建新的子表,同时为其定义新的检查约束,如:
CREATE TABLE measurement_y2008m02 (
CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' )
) INHERITS (measurement);
第二种方法的创建步骤相对繁琐,但更为灵活和实用。见以下四步:
/* 创建一个独立的数据表(measurement_y2008m02),该表在创建时以将来的主表(measurement)为模板,包含模板表的缺省值(DEFAULTS)和一致性约束(CONSTRAINTS)。*/
CREATE TABLE measurement_y2008m02
(LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
/* 为该表创建未来作为子表时需要使用的检查约束。*/
ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
CHECK (logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01');
/* 导入数据到该表。下面只是给出一种导入数据的方式作为例子。在导入数据之后,如有可能,还可以做进一步的数据处理,如数据转换、过滤等。*/
\copy measurement_y2008m02 from 'measurement_y2008m02'
/* 在适当的时候,或者说在需要的时候,让该表继承主表。*/
ALTER TABLE measurement_y2008m02 INHERIT measurement;
7). 确保postgresql.conf里的配置参数constraint_exclusion是打开的。没有这个参数,查询不会按照需要进行优化。这里我们需要做的是确保该选项在配置文件中没有被注释掉。
/> pwd
/opt/PostgreSQL/9.1/data
/> cat postgresql.conf | grep "constraint_exclusion"
constraint_exclusion = partition # on, off, or partition
3. 分区和约束排除:
约束排除(Constraint exclusion)是一种查询优化技巧,它改进了用上面方法定义的表分区的性能。比如:
SET constraint_exclusion = on;
SELECT count(*) FROM measurement WHERE logdate >= DATE '2006-01-01';
如果没有约束排除,上面的查询会扫描measurement表中的每一个分区。打开了约束排除之后,规划器将检查每个分区的约束然后再试图证明该分区不需要被扫描,因为它不能包含任何符合WHERE子句条件的数据行。如果规划器可以证明这个,它就把该分区从查询规划里排除出去。
你可以使用EXPLAIN命令显示一个规划在constraint_exclusion打开和关闭情况下的不同。用上面方法设置的表的典型的缺省规划是:
SET constraint_exclusion = off;
EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2006-01-01';
QUERY PLAN
-----------------------------------------------------------------------------------------------
Aggregate (cost=158.66..158.68 rows=1 width=0)
-> Append (cost=0.00..151.88 rows=2715 width=0)
-> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
Filter: (logdate >= '2006-01-01'::date)
-> Seq Scan on measurement_yy04mm02 measurement (cost=0.00..30.38 rows=543 width=0)
Filter: (logdate >= '2006-01-01'::date)
-> Seq Scan on measurement_yy04mm03 measurement (cost=0.00..30.38 rows=543 width=0)
Filter: (logdate >= '2006-01-01'::date)
...
-> Seq Scan on measurement_yy05mm12 measurement (cost=0.00..30.38 rows=543 width=0)
Filter: (logdate >= '2006-01-01'::date)
-> Seq Scan on measurement_yy06mm01 measurement (cost=0.00..30.38 rows=543 width=0)
Filter: (logdate >= '2006-01-01'::date)
从上面的查询计划中可以看出,PostgreSQL扫描了所有分区。下面我们再看一下打开约束排除之后的查询计划:
SET constraint_exclusion = on;
EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2006-01-01';
QUERY PLAN
-----------------------------------------------------------------------------------------------
Aggregate (cost=63.47..63.48 rows=1 width=0)
-> Append (cost=0.00..60.75 rows=1086 width=0)
-> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
Filter: (logdate >= '2006-01-01'::date)
-> Seq Scan on measurement_yy06mm01 measurement (cost=0.00..30.38 rows=543 width=0)
Filter: (logdate >= '2006-01-01'::date)
请注意,约束排除只由CHECK约束驱动,而不会由索引驱动。
目前版本的PostgreSQL中该配置的缺省值是partition,该值是介于on和off之间的一种行为方式,即规划器只会将约束排除应用于基于分区表的查询,而on设置则会为所有查询都进行约束排除,那么对于普通数据表而言,也将不得不承担由该机制而产生的额外开销。
约束排除在使用时有以下几点注意事项:
1). 约束排除只是在查询的WHERE子句包含约束的时候才生效。一个参数化的查询不会被优化,因为在运行时规划器不知道该参数会选择哪个分区。因此像CURRENT_DATE这样的函数必须避免。把分区键值和另外一个表的字段连接起来也不会得到优化。
2). 在CHECK约束里面要避免跨数据类型的比较,因为目前规划器会无法证明这样的条件为假。比如,下面的约束会在x是整数字段的时候可用,但是在x是一个bigint的时候不能用:
CHECK (x = 1)
对于bigint字段,我们必须使用类似下面这样的约束:
CHECK (x = 1::bigint)
这个问题并不仅仅局限于bigint数据类型,它可能会发生在任何约束的缺省数据类型与其比较的字段的数据类型不匹配的场合。在提交的查询里的跨数据类型的比较通常是OK的,只是不能在CHECK条件里。
3). 在主表上的UPDATE和DELETE命令并不执行约束排除。
4). 在规划器进行约束排除时,主表上的所有分区的所有约束都将会被检查,因此,大量的分区会显著增加查询规划的时间。
5). 在执行ANALYZE语句时,要为每一个分区都执行该命令,而不是仅仅对主表执行该命令。
PostgreSQL学习手册(四) 常用数据类型
一、数值类型:
下面是PostgreSQL所支持的数值类型的列表和简单说明:
1. 整数类型:
类型smallint、integer和bigint存储各种范围的全部是数字的数,也就是没有小数部分的数字。试图存储超出范围以外的数值将导致一个错误。常用的类型是integer,因为它提供了在范围、存储空间和性能之间的最佳平衡。一般只有在磁盘空间紧张的时候才使用smallint。而只有在 integer的范围不够的时候才使用bigint,因为前者(integer)绝对快得多。
2. 任意精度数值:
类型numeric可以存储最多1000位精度的数字并且准确地进行计算。因此非常适合用于货币金额和其它要求计算准确的数量。不过,numeric类型上的算术运算比整数类型或者浮点数类型要慢很多。
numeric字段的最大精度和最大比例都是可以配置的。要声明一个类型为numeric的字段,你可以用下面的语法:
NUMERIC(precision,scale)
比如数字23.5141的精度为6,而刻度为4。
在目前的PostgreSQL版本中,decimal和numeric是等效的。
3. 浮点数类型:
数据类型real和double是不准确的、牺牲精度的数字类型。不准确意味着一些数值不能准确地转换成内部格式并且是以近似的形式存储的,因此存储后再把数据打印出来可能显示一些缺失。
4. Serial(序号)类型:
serial和bigserial类型不是真正的类型,只是为在表中设置唯一标识做的概念上的便利。
CREATE TABLE tablename (
colname SERIAL
);
等价于
CREATE SEQUENCE tablename_colname_seq;
CREATE TABLE tablename(
colname integer DEFAULT nextval('tablename_colname_seq') NOT NULL
);
这样,我们就创建了一个整数字段并且把它的缺省数值安排为从一个序列发生器取值。应用了一个NOT NULL约束以确保空值不会被插入。在大多数情况下你可能还希望附加一个UNIQUE或者PRIMARY KEY约束避免意外地插入重复的数值,但这个不是自动发生的。因此,如果你希望一个序列字段有一个唯一约束或者一个主键,那么你现在必须声明,就像其它数据类型一样。
还需要另外说明的是,一个serial类型创建的序列在其所属字段被删除时,该序列也将被自动删除,但是其它情况下是不会被删除的。因此,如果你想用同一个序列发生器同时给几个字段提供数据,那么就应该以独立对象的方式创建该序列发生器。
二、字符类型:
下面是PostgreSQL所支持的字符类型的列表和简单说明:
SQL 定义了两种基本的字符类型,varchar(n)和char(n),这里的n是一个正整数。两种类型都可以存储最多n个字符长的字串,试图存储更长的字串 到这些类型的字段里会产生一个错误,除非超出长度的字符都是空白,这种情况下该字串将被截断为最大长度。如果没有长度声明,char等于char(1), 而varchar则可以接受任何长度的字串。
MyTest=> CREATE TABLE testtable(first_col varchar(2));
CREATE TABLE
MyTest=> INSERT INTO testtable VALUES('333'); --插入字符串的长度,超过其字段定义的长度,因此报错。
ERROR: value too long for type character varying(2)
--插入字符串中,超出字段定义长度的部分是空格,因此可以插入,但是空白符被截断。
MyTest=> INSERT INTO testtable VALUES('33 ');
INSERT 0 1
MyTest=> SELECT * FROM testtable;
first_col
-----------
33
(1 row)
这里需要注意的是,如果是将数值转换成char(n)或者varchar(n),那么超长的数值将被截断成n个字符,而不会抛出错误。
MyTest=> select 1234::varchar(2);
varchar
---------
12
(1 row)
最后需要提示的是,这三种类型之间没有性能差别,只不过是在使用char类型时增加了存储尺寸。虽然在某些其它的数据库系统里,char(n)有一定的性能优势,但在PostgreSQL里没有。在大多数情况下,应该使用text或者varchar。
三、日期/时间类型:
下面是PostgreSQL所支持的日期/时间类型的列表和简单说明:
1. 日期/时间输入:
任何日期或者时间的文本输入均需要由单引号包围,就象一个文本字符串一样。
1). 日期:
以下为合法的日期格式列表:
2). 时间:
以下为合法的时间格式列表:
3). 时间戳:
时间戳类型的有效输入由一个日期和时间的联接组成,后面跟着一个可选的时区。因此,1999-01-08 04:05:06和1999-01-08 04:05:06 -8:00都是有效的数值。
2. 示例:
1). 在插入数据之前先查看datestyle系统变量的值:
MyTest=> show datestyle;
DateStyle
-----------
ISO, YMD
(1 row)
2). 创建包含日期、时间和时间戳类型的示例表:
MyTest=> CREATE TABLE testtable (id integer, date_col date, time_col time, timestamp_col timestamp);
CREATE TABLE
3). 插入数据:
MyTest=> INSERT INTO testtable(id,date_col) VALUES(1, DATE'01/02/03'); --datestyle为YMD
INSERT 0 1
MyTest=> SELECT id, date_col FROM testtable;
id | date_col
----+------------
1 | 2001-02-03
(1 row)
MyTest=> set datestyle = MDY;
SET
MyTest=> INSERT INTO testtable(id,date_col) VALUES(2, DATE'01/02/03'); --datestyle为MDY
INSERT 0 1
MyTest=> SELECT id,date_col FROM testtable;
id | date_col
----+------------
1 | 2001-02-03
2 | 2003-01-02
MyTest=> INSERT INTO testtable(id,time_col) VALUES(3, TIME'10:20:00'); --插入时间。
INSERT 0 1
MyTest=> SELECT id,time_col FROM testtable WHERE time_col IS NOT NULL;
id | time_col
----+----------
3 | 10:20:00
(1 row)
MyTest=> INSERT INTO testtable(id,timestamp_col) VALUES(4, DATE'01/02/03');
INSERT 0 1
MyTest=> INSERT INTO testtable(id,timestamp_col) VALUES(5, TIMESTAMP'01/02/03 10:20:00');
INSERT 0 1
MyTest=> SELECT id,timestamp_col FROM testtable WHERE timestamp_col IS NOT NULL;
id | timestamp_col
----+---------------------
4 | 2003-01-02 00:00:00
5 | 2003-01-02 10:20:00
(2 rows)
四、布尔类型:
PostgreSQL支持标准的SQL boolean数据类型。boolean只能有两个状态之一:真(True)或 假(False)。该类型占用1个字节。
"真"值的有效文本值是:
TRUE
't'
'true'
'y'
'yes'
'1'
而对于"假"而言,你可以使用下面这些:
FALSE
'f'
'false'
'n'
'no'
'0'
见如下使用方式:
MyTest=> CREATE TABLE testtable (a boolean, b text);
CREATE TABLE
MyTest=> INSERT INTO testtable VALUES(TRUE, 'sic est');
INSERT 0 1
MyTest=> INSERT INTO testtable VALUES(FALSE, 'non est');
INSERT 0 1
MyTest=> SELECT * FROM testtable;
a | b
---+---------
t | sic est
f | non est
(2 rows)
MyTest=> SELECT * FROM testtable WHERE a;
a | b
---+---------
t | sic est
(1 row)
MyTest=> SELECT * FROM testtable WHERE a = true;
a | b
---+---------
t | sic est
(1 row)
五、位串类型:
位串就是一串1和0的字串。它们可以用于存储和视觉化位掩码。我们有两种类型的SQL位类型:bit(n)和bit varying(n); 这里的n是一个正整数。bit类型的数据必须准确匹配长度n; 试图存储短些或者长一些的数据都是错误的。类型bit varying数据是最长n的变长类型;更长的串会被拒绝。写一个没有长度的bit等效于bit(1),没有长度的bit varying相当于没有长度限制。
针对该类型,最后需要提醒的是,如果我们明确地把一个位串值转换成bit(n),那么它的右边将被截断或者在右边补齐零,直到刚好n位,而不会抛出任何错误。类似地,如果我们明确地把一个位串数值转换成bit varying(n),如果它超过n位,那么它的右边将被截断。 见如下具体使用方式:
MyTest=> CREATE TABLE testtable (a bit(3), b bit varying(5));
CREATE TABLE
MyTest=> INSERT INTO testtable VALUES (B'101', B'00');
INSERT 0 1
MyTest=> INSERT INTO testtable VALUES (B'10', B'101');
ERROR: bit string length 2 does not match type bit(3)
MyTest=> INSERT INTO testtable VALUES (B'10'::bit(3), B'101');
INSERT 0 1
MyTest=> SELECT * FROM testtable;
a | b
-----+-----
101 | 00
100 | 101
(2 rows)
MyTest=> SELECT B'11'::bit(3);
bit
-----
110
(1 row)
六、数组:
1. 数组类型声明:
1). 创建字段含有数组类型的表。
CREATE TABLE sal_emp (
name text,
pay_by_quarter integer[] --还可以定义为integer[4]或integer ARRAY[4]
);
2). 插入数组数据:
MyTest=# INSERT INTO sal_emp VALUES ('Bill', '{11000, 12000, 13000, 14000}');
INSERT 0 1
MyTest=# INSERT INTO sal_emp VALUES ('Carol', ARRAY[21000, 22000, 23000, 24000]);
INSERT 0 1
MyTest=# SELECT * FROM sal_emp;
name | pay_by_quarter
--------+---------------------------
Bill | {11000,12000,13000,14000}
Carol | {21000,22000,23000,24000}
(2 rows)
2. 访问数组:
和其他语言一样,PostgreSQL中数组也是通过下标数字(写在方括弧内)的方式进行访问,只是PostgreSQL中数组元素的下标是从1开始n结束。
MyTest=# SELECT pay_by_quarter[3] FROM sal_emp;
pay_by_quarter
----------------
13000
23000
(2 rows)
MyTest=# SELECT name FROM sal_emp WHERE pay_by_quarter[1] <> pay_by_quarter[2];
name
------
Bill
Carol
(2 rows)
PostgreSQL中还提供了访问数组范围的功能,即ARRAY[脚标下界:脚标上界]。
MyTest=# SELECT name,pay_by_quarter[1:3] FROM sal_emp;
name | pay_by_quarter
--------+---------------------
Bill | {11000,12000,13000}
Carol | {21000,22000,23000}
(2 rows)
3. 修改数组:
1). 代替全部数组值:
--UPDATE sal_emp SET pay_by_quarter = ARRAY[25000,25000,27000,27000] WHERE name = 'Carol'; 也可以。
MyTest=# UPDATE sal_emp SET pay_by_quarter = '{31000,32000,33000,34000}' WHERE name = 'Carol';
UPDATE 1
MyTest=# SELECT * FROM sal_emp;
name | pay_by_quarter
--------+---------------------------
Bill | {11000,12000,13000,14000}
Carol | {31000,32000,33000,34000}
(2 rows)
2). 更新数组中某一元素:
MyTest=# UPDATE sal_emp SET pay_by_quarter[4] = 15000 WHERE name = 'Bill';
UPDATE 1
MyTest=# SELECT * FROM sal_emp;
name | pay_by_quarter
--------+---------------------------
Carol | {31000,32000,33000,34000}
Bill | {11000,12000,13000,15000}
(2 rows)
3). 更新数组某一范围的元素:
MyTest=# UPDATE sal_emp SET pay_by_quarter[1:2] = '{37000,37000}' WHERE name = 'Carol';
UPDATE 1
MyTest=# SELECT * FROM sal_emp;
name | pay_by_quarter
--------+---------------------------
Bill | {11000,12000,13000,15000}
Carol | {37000,37000,33000,34000}
(2 rows)
4). 直接赋值扩大数组:
MyTest=# UPDATE sal_emp SET pay_by_quarter[5] = 45000 WHERE name = 'Bill';
UPDATE 1
MyTest=# SELECT * FROM sal_emp;
name | pay_by_quarter
--------+---------------------------------
Carol | {37000,37000,33000,34000}
Bill | {11000,12000,13000,15000,45000}
(2 rows)
4. 在数组中检索:
1). 最简单直接的方法:
SELECT * FROM sal_emp WHERE pay_by_quarter[1] = 10000 OR
pay_by_quarter[2] = 10000 OR
pay_by_quarter[3] = 10000 OR
pay_by_quarter[4] = 10000;
2). 更加有效的方法:
SELECT * FROM sal_emp WHERE 10000 = ANY (pay_by_quarter); --数组元素中有任何一个等于10000,where条件将成立。
SELECT * FROM sal_emp WHERE 10000 = ALL (pay_by_quarter); --只有当数组中所有的元素都等于10000时,where条件才成立。
七、复合类型:
PostgreSQL 中复合类型有些类似于C语言中的结构体,也可以被视为Oracle中的记录类型,但是还是感觉复合类型这个命名比较贴切。它实际上只是一个字段名和它们的数据类型的列表。PostgreSQL允许像简单数据类型那样使用复合类型。比如,表字段可以声明为一个复合类型。
1. 声明复合类型:
下面是两个简单的声明示例:
CREATE TYPE complex AS (
r double,
i double
);
CREATE TYPE inventory_item AS (
name text,
supplier_id integer,
price numeric
);
和声明一个数据表相比,声明类型时需要加AS关键字,同时在声明TYPE时不能定义任何约束。下面我们看一下如何在表中指定复合类型的字段,如:
CREATE TABLE on_hand (
item inventory_item,
count integer
);
最后需要指出的是,在创建表的时候,PostgreSQL也会自动创建一个与该表对应的复合类型,名字与表字相同,即表示该表的复合类型。
2. 复合类型值输入:
我们可以使用文本常量的方式表示复合类型值,即在圆括号里包围字段值并且用逗号分隔它们。你也可以将任何字段值用双引号括起,如果值本身包含逗号或者圆括号,那么就用双引号括起,对于上面的inventory_item复合类型的输入如下:
'("fuzzy dice",42,1.99)'
如果希望类型中的某个字段为NULL,只需在其对应的位置不予输入即可,如下面的输入中price字段的值为NULL,
'("fuzzy dice",42,)'
如果只是需要一个空字串,而非NULL,写一对双引号,如:
'("",42,)'
在更多的场合中PostgreSQL推荐使用ROW表达式来构建复合类型值,使用该种方式相对简单,无需考虑更多标识字符问题,如:
ROW('fuzzy dice', 42, 1.99)
ROW('', 42, NULL)
注:对于ROW表达式,如果里面的字段数量超过1个,那么关键字ROW就可以省略,因此以上形式可以简化为:
('fuzzy dice', 42, 1.99)
('', 42, NULL)
3. 访问复合类型:
访问复合类型中的字段和访问数据表中的字段在形式上极为相似,只是为了对二者加以区分,PostgreSQL设定在访问复合类型中的字段时,类型部分需要用圆括号括起,以避免混淆,如:
SELECT (item).name FROM on_hand WHERE (item).price > 9.99;
如果在查询中也需要用到表名,那么表名和类型名都需要被圆括号括起,如:
SELECT (on_hand.item).name FROM on_hand WHERE (on_hand.item).price > 9.99;
4. 修改复合类型:
见如下几个示例:
--直接插入复合类型的数据,这里是通过ROW表达式来完成的。
INSERT INTO on_hand(item) VALUES(ROW("fuzzy dice",42,1.99));
--在更新操作中,也是可以通过ROW表达式来完成。
UPDATE on_hand SET item = ROW("fuzzy dice",42,1.99) WHERE count = 0;
--在更新复合类型中的一个字段时,我们不能在SET后面出现的字段名周围加圆括号,
--但是在等号右边的表达式里引用同一个字段时却需要圆括号。
UPDATE on_hand SET item.price = (item).price + 1 WHERE count = 0;
--可以在插入中,直接插入复合类型中字段。
INSERT INTO on_hand (item.supplier_id, item.price) VALUES(100, 2.2);
PostgreSQL学习手册(五) 函数和操作符
一、逻辑操作符:
常用的逻辑操作符有:AND、OR和NOT。其语义与其它编程语言中的逻辑操作符完全相同。
二、比较操作符:
下面是PostgreSQL中提供的比较操作符列表:
比较操作符可以用于所有可以比较的数据类型。所有比较操作符都是双目操作符,且返回boolean类型。除了比较操作符以外,我们还可以使用BETWEEN语句,如:
a BETWEEN x AND y 等效于 a >= x AND a <= y
a NOT BETWEEN x AND y 等效于 a < x OR a > y
三、 数学函数和操作符:
下面是PostgreSQL中提供的数学操作符列表:
按位操作符只能用于整数类型,而其它的操作符可以用于全部数值数据类型。按位操作符还可以用于位串类型bit和bit varying,
下面是PostgreSQL中提供的数学函数列表,需要说明的是,这些函数中有许多都存在多种形式,区别只是参数类型不同。除非特别指明,任何特定形式的函数都返回和它的参数相同的数据类型。
函数返回类型描述例子
结果abs(x) 绝对值abs(-17.4)17.4cbrt(double) 立方根cbrt(27.0)3ceil(double/numeric) 不小于参数的最小的整数ceil(-42.8)-42degrees(double)
把弧度转为角度degrees(0.5)28.6478897565412exp(double/numeric) 自然指数exp(1.0)2.71828182845905floor(double/numeric) 不大于参数的最大整数floor(-42.8)-43ln(double/numeric) 自然对数ln(2.0)0.693147180559945log(double/numeric) 10为底的对数log(100.0)2log(b numeric,x numeric) numeric指定底数的对数log(2.0, 64.0)6.0000000000mod(y, x) 取余数mod(9,4)1pi()
double"π"常量pi()
3.14159265358979power(a double, b double)double求a的b次幂power(9.0, 3.0)729power(a numeric, b numeric)numeric求a的b次幂power(9.0, 3.0)729radians(double)double把角度转为弧度radians(45.0)0.785398163397448random()double0.0到1.0之间的随机数值random() round(double/numeric) 圆整为最接近的整数round(42.4)42round(v numeric, s int)numeric圆整为s位小数数字round(42.438,2)42.44sign(double/numeric) 参数的符号(-1,0,+1)
sign(-8.4)-1sqrt(double/numeric) 平方根sqrt(2.0)1.4142135623731trunc(double/numeric) 截断(向零靠近)trunc(42.8)42trunc(v numeric, s int)numeric截断为s小数位置的数字trunc(42.438,2)42.43
三角函数列表:
函数描述acos(x)反余弦asin(x)反正弦atan(x)反正切atan2(x, y)正切 y/x 的反函数cos(x)余弦cot(x)余切sin(x)正弦tan(x)正切
四、字符串函数和操作符:
下面是PostgreSQL中提供的字符串操作符列表:
五、位串函数和操作符:
对于类型bit和bit varying,除了常用的比较操作符之外,还可以使用以下列表中由PostgreSQL提供的位串函数和操作符,其中&、|和#的位串操作数必须等长。在移位的时候,保留原始的位串的的长度。
除了以上列表中提及的操作符之外,位串还可以使用字符串函数:length, bit_length, octet_length, position, substring。此外,我们还可以在整数和bit之间来回转换,如:
MyTest=# SELECT 44::bit(10);
bit
------------
0000101100
(1 row)
MyTest=# SELECT 44::bit(3);
bit
-----
100
(1 row)
MyTest=# SELECT cast(-44 as bit(12));
bit
--------------
111111010100
(1 row)
MyTest=# SELECT '1110'::bit(4)::integer;
int4
------
14
(1 row)
注意:如果只是转换为"bit",意思是转换成bit(1),因此只会转换成整数的最低位。
六、模式匹配:
PostgreSQL中提供了三种实现模式匹配的方法:SQL LIKE操作符,更近一些的SIMILAR TO操作符,和POSIX-风格正则表达式。
1. LIKE:
string LIKE pattern [ ESCAPE escape-character ]
string NOT LIKE pattern [ ESCAPE escape-character ]
每个pattern定义一个字串的集合。如果该string包含在pattern代表的字串集合里,那么LIKE表达式返回真。和我们想象的一样,如果 LIKE返回真,那么NOT LIKE表达式返回假,反之亦然。在pattern里的下划线(_)代表匹配任何单个字符,而一个百分号(%)匹配任何零或更多字符,如:
'abc' LIKE 'abc' true
'abc' LIKE 'a%' true
'abc' LIKE '_b_' true
'abc' LIKE 'c' false
要匹配文本的下划线或者百分号,而不是匹配其它字符,在pattern里相应的字符必须前导转义字符。缺省的转义字符是反斜杠,但是你可以用ESCAPE子句指定一个。要匹配转义字符本身,写两个转义字符。我们也可以通过写成ESCAPE ''的方式有效地关闭转义机制,此时,我们就不能关闭下划线和百分号的特殊含义了。
关键字ILIKE可以用于替换LIKE,令该匹配就当前的区域设置是大小写无关的。这个特性不是SQL标准,是PostgreSQL的扩展。操作符~~等效于LIKE, 而~~*对应ILIKE。还有!~~和!~~*操作符分别代表NOT LIKE和NOT ILIKE。所有这些操作符都是PostgreSQL特有的。
2. SIMILAR TO正则表达式:
SIMILAR TO根据模式是否匹配给定的字符串而返回真或者假。
string SIMILAR TO pattern [ESCAPE escape-character]
string NOT SIMILAR TO pattern [ESCAPE escape-character]
它和LIKE非常类似,支持LIKE的通配符('_'和'%')且保持其原意。除此之外,SIMILAR TO还支持一些自己独有的元字符,如:
1). | 标识选择(两个候选之一)。
2). * 表示重复前面的项零次或更多次。
3). + 表示重复前面的项一次或更多次。
4). 可以使用圆括弧()把项组合成一个逻辑项。
5). 一个方括弧表达式[...]声明一个字符表,就像POSIX正则表达式一样。
见如下示例:
'abc' SIMILAR TO 'abc' true
'abc' SIMILAR TO 'a' false
'abc' SIMILAR TO '%(b|d)%' true
'abc' SIMILAR TO '(b|c)%' false
带三个参数的substring,substring(string from pattern for escape-character),提供了一个从字串中抽取一个匹配SQL正则表达式模式的子字串的函数。和SIMILAR TO一样,声明的模式必须匹配整个数据串,否则函数失效并返回NULL。为了标识在成功的时候应该返回的模式部分,模式必须出现后跟双引号(")的两个转 义字符。匹配这两个标记之间的模式的字串将被返回,如:
MyTest=# SELECT substring('foobar' from '%#"o_b#"%' FOR '#'); --这里#是转义符,双引号内的模式是返回部分。
substring
-----------
oob
(1 row)
MyTest=# SELECT substring('foobar' from '#"o_b#"%' FOR '#'); --foobar不能完全匹配后面的模式,因此返回NULL。
substring
-----------
(1 row)
七、数据类型格式化函数:
PostgreSQL格式化函数提供一套有效的工具用于把各种数据类型(日期/时间、integer、floating point和numeric)转换成格式化的字符串以及反过来从格式化的字符串转换成指定的数据类型。下面列出了这些函数,它们都遵循一个公共的调用习 惯:第一个参数是待格式化的值,而第二个是定义输出或输出格式的模板。
1. 用于日期/时间格式化的模式:
模式描述HH一天的小时数(01-12)HH12一天的小时数(01-12)HH24一天的小时数(00-23)MI分钟(00-59)SS秒(00-59)MS毫秒(000-999)US微秒(000000-999999)AM正午标识(大写)Y,YYY带逗号的年(4和更多位)YYYY年(4和更多位)YYY年的后三位YY年的后两位Y年的最后一位MONTH全长大写月份名(空白填充为9字符)Month全长混合大小写月份名(空白填充为9字符)month全长小写月份名(空白填充为9字符)MON大写缩写月份名(3字符)Mon缩写混合大小写月份名(3字符)mon小写缩写月份名(3字符)MM月份号(01-12)DAY全长大写日期名(空白填充为9字符)Day全长混合大小写日期名(空白填充为9字符)day全长小写日期名(空白填充为9字符)DY缩写大写日期名(3字符)Dy缩写混合大小写日期名(3字符)dy缩写小写日期名(3字符)DDD一年里的日子(001-366)DD一个月里的日子(01-31)D一周里的日子(1-7;周日是1)W一个月里的周数(1-5)(第一周从该月第一天开始)WW一年里的周数(1-53)(第一周从该年的第一天开始)2. 用于数值格式化的模板模式:
模式描述9带有指定数值位数的值0带前导零的值.(句点)小数点,(逗号)分组(千)分隔符PR尖括号内负值S带符号的数值L货币符号D小数点G分组分隔符MI在指明的位置的负号(如果数字 < 0)PL在指明的位置的正号(如果数字 > 0)SG在指明的位置的正/负号
八、时间/日期函数和操作符:
1. 下面是PostgreSQL中支持的时间/日期操作符的列表:
2. 日期/时间函数:
函数返回类型描述例子结果age(timestamp, timestamp)interval减去参数,生成一个使用年、月的"符号化"的结果age('2001-04-10', timestamp '1957-06-13')43 years 9 mons 27 daysage(timestamp)interval从current_date减去得到的数值age(timestamp '1957-06-13')43 years 8 mons 3 dayscurrent_datedate今天的日期 current_timetime现在的时间 current_timestamptimestamp日期和时间 date_part(text, timestamp)double获取子域(等效于extract)date_part('hour', timestamp '2001-02-16 20:38:40')20date_part(text, interval)double获取子域(等效于extract)date_part('month', interval '2 years 3 months')3date_trunc(text, timestamp)timestamp截断成指定的精度date_trunc('hour', timestamp '2001-02-16 20:38:40')2001-02-16 20:00:00+00extract(field from timestamp)double获取子域extract(hour from timestamp '2001-02-16 20:38:40')20extract(field from interval)double获取子域extract(month from interval '2 years 3 months')3localtimetime今日的时间 localtimestamptimestamp日期和时间 now()timestamp当前的日期和时间(等效于 current_timestamp) timeofday()text当前日期和时间3. EXTRACT,date_part函数支持的field:
域描述例子结果CENTURY世纪EXTRACT(CENTURY FROM TIMESTAMP '2000-12-16 12:21:13');20DAY(月分)里的日期域(1-31)EXTRACT(DAY from TIMESTAMP '2001-02-16 20:38:40');16DECADE年份域除以10EXTRACT(DECADE from TIMESTAMP '2001-02-16 20:38:40');200DOW每周的星期号(0-6;星期天是0) (仅用于timestamp)EXTRACT(DOW FROM TIMESTAMP '2001-02-16 20:38:40');5DOY一年的第几天(1 -365/366) (仅用于 timestamp)EXTRACT(DOY from TIMESTAMP '2001-02-16 20:38:40');47HOUR小时域(0-23)EXTRACT(HOUR from TIMESTAMP '2001-02-16 20:38:40');20MICROSECONDS秒域,包括小数部分,乘以 1,000,000。EXTRACT(MICROSECONDS from TIME '17:12:28.5');28500000MILLENNIUM千年EXTRACT(MILLENNIUM from TIMESTAMP '2001-02-16 20:38:40');3MILLISECONDS秒域,包括小数部分,乘以 1000。EXTRACT(MILLISECONDS from TIME '17:12:28.5');28500MINUTE分钟域(0-59)EXTRACT(MINUTE from TIMESTAMP '2001-02-16 20:38:40');38MONTH对于timestamp数值,它是一年里的月份数(1-12);对于interval数值,它是月的数目,然后对12取模(0-11)EXTRACT(MONTH from TIMESTAMP '2001-02-16 20:38:40');2QUARTER该天所在的该年的季度(1-4)(仅用于 timestamp)EXTRACT(QUARTER from TIMESTAMP '2001-02-16 20:38:40');1SECOND秒域,包括小数部分(0-59[1])EXTRACT(SECOND from TIMESTAMP '2001-02-16 20:38:40');40WEEK该天在所在的年份里是第几周。EXTRACT(WEEK from TIMESTAMP '2001-02-16 20:38:40');7YEAR年份域EXTRACT(YEAR from TIMESTAMP '2001-02-16 20:38:40');2001 4. 当前日期/时间:
我们可以使用下面的函数获取当前的日期和/或时间∶
CURRENT_DATE
CURRENT_TIME
CURRENT_TIMESTAMP
CURRENT_TIME (precision)
CURRENT_TIMESTAMP (precision)
LOCALTIME
LOCALTIMESTAMP
LOCALTIME (precision)
LOCALTIMESTAMP (precision)
九、序列操作函数:
序列对象(也叫序列生成器)都是用CREATE SEQUENCE创建的特殊的单行表。一个序列对象通常用于为行或者表生成唯一的标识符。下面序列函数,为我们从序列对象中获取最新的序列值提供了简单和并发读取安全的方法。
对于regclass参数,仅需用单引号括住序列名即可,因此它看上去就像文本常量。为了达到和处理普通SQL对象一样的兼容性,这个字串将被转换成小写,除非该序列名是用双引号括起,如:
nextval('foo') --操作序列号foo
nextval('FOO') --操作序列号foo
nextval('"Foo"') --操作序列号Foo
SELECT setval('foo', 42); --下次nextval将返回43
SELECT setval('foo', 42, true);
SELECT setval('foo', 42, false); --下次nextval将返回42
十、条件表达式:
1. CASE:
SQL CASE表达式是一种通用的条件表达式,类似于其它语言中的if/else语句。
CASE WHEN condition THEN result
[WHEN ...]
[ELSE result]
END
condition是一个返回boolean的表达式。如果为真,那么CASE表达式的结果就是符合条件的result。如果结果为假,那么以相同方式 搜寻随后的WHEN子句。如果没有WHEN condition为真,那么case表达式的结果就是在ELSE子句里的值。如果省略了ELSE子句而且没有匹配的条件,结果为NULL,如:
MyTest=> SELECT * FROM testtable;
i
---
1
2
3
(3 rows)
MyTest=> SELECT i, CASE WHEN i=1 THEN 'one'
MyTest-> WHEN i=2 THEN 'two'
MyTest-> ELSE 'other'
MyTest-> END
MyTest-> FROM testtable;
i | case
---+-------
1 | one
2 | two
3 | other
(3 rows)
注:CASE表达式并不计算任何对于判断结果并不需要的子表达式。
2. COALESCE:
COALESCE返回它的第一个非NULL的参数的值。它常用于在为显示目的检索数据时用缺省值替换NULL值。
COALESCE(value[, ...])
和CASE表达式一样,COALESCE将不会计算不需要用来判断结果的参数。也就是说,在第一个非空参数右边的参数不会被计算。
3. NULLIF:
当且仅当value1和value2相等时,NULLIF才返回NULL。否则它返回value1。
NULLIF(value1, value2)
MyTest=> SELECT NULLIF('abc','abc');
nullif
--------
(1 row)
MyTest=> SELECT NULLIF('abcd','abc');
nullif
--------
abcd
(1 row)
4. GREATEST和LEAST:
GREATEST和LEAST函数从一个任意的数字表达式列表里选取最大或者最小的数值。列表中的NULL数值将被忽略。只有所有表达式的结果都是NULL的时候,结果才会是NULL。
GREATEST(value [, ...])
LEAST(value [, ...])
MyTest=> SELECT GREATEST(1,3,5);
greatest
----------
5
(1 row)
MyTest=> SELECT LEAST(1,3,5,NULL);
least
-------
1
(1 row)
十一、数组函数和操作符:
1. PostgreSQL中提供的用于数组的操作符列表:
2. PostgreSQL中提供的用于数组的函数列表:
十二、系统信息函数:
1. PostgreSQL中提供的和数据库相关的函数列表:
2. 允许用户在程序里查询对象访问权限的函数:
注:以上函数均返回boolean类型。要评估一个用户是否在权限上持有赋权选项,给权限键字附加 WITH GRANT OPTION;比如 'UPDATE WITH GRANT OPTION'。
3. 模式可视性查询函数:
那些判断一个对象是否在当前模式搜索路径中可见的函数。 如果一个表所在的模式在搜索路径中,并且没有同名的表出现在搜索路径的更早的地方,那么就说这个表视可见的。 它等效于表可以不带明确模式修饰进行引用。
注:以上函数均返回boolean类型,所有这些函数都需要对象 OID 标识作为检查的对象。
postgres=# SELECT pg_table_is_visible('testtable'::regclass);
pg_table_is_visible
---------------------
t
(1 row)
4. 系统表信息函数:
这些函数大多数都有两个变种,其中一个可以选择对结果的"漂亮的打印"。 漂亮打印的格式更容易读,但是缺省的格式更有可能被将来的PostgreSQL版本用同样的方法解释;如果是用于转储,那么尽可能避免使用漂亮打印。 给漂亮打印参数传递false生成的结果和那个没有这个参数的变种生成的结果完全一样。
十三、系统管理函数:
1. 查询以及修改运行时配置参数的函数:
current_setting用于以查询形式获取setting_name设置的当前数值。它和SQL命令SHOW是等效的。 比如:
MyTest=# SELECT current_setting('datestyle');
current_setting
-----------------
ISO, YMD
(1 row)
set_config将参数setting_name设置为new_value。如果is_local设置为true,那么新数值将只应用于当前事务。如果你希望新的数值应用于当前会话,那么应该使用false。它等效于SQL命令SET。比如:
MyTest=# SELECT set_config('log_statement_stats','off', false);
set_config
------------
off
(1 row)
2. 数据库对象尺寸函数:
3. 数据库对象位置函数:
mydatabase=# select pg_relation_filenode('testtable');
pg_relation_filenode
----------------------
17877
(1 row)
mydatabase=# select pg_relation_filepath('testtable');
pg_relation_filepath
----------------------------------------------
pg_tblspc/17633/PG_9.1_201105231/17636/17877
(1 row)
- PostgreSQL学习手册 1-5
- PostgreSQL学习手册6-10
- PostgreSQL学习手册(目录)
- PostgreSQL学习手册(目录)
- PostgreSQL数据库学习手册
- PostgreSQL 学习手册2
- PostgreSQL学习手册(数据表)
- PostgreSQL学习手册
- PostgreSQL学习手册(数据表)
- PostgreSQL学习手册(函数)
- PostgreSQL学习手册(索引)
- PostgreSQL学习手册(数据表)
- PostgreSQL学习手册(目录)
- PostgreSQL学习手册(数据表)
- PostgreSQL学习手册
- PostgreSQL学习手册(数据表)
- PostgreSQL学习手册(数据表)
- PostgreSQL学习手册(索引)
- 放置键盘记录,密码输入框的设计
- Objective-C Runtime 运行时之四:Method Swizzling
- 第十一周 项目6:(5)多文件组织程序
- Java关键字介绍之this与super
- 如何成为优秀的程序员
- PostgreSQL学习手册 1-5
- 第十一周项目六(2)回文数
- 关于二进制补码的若干注记
- Maximum Depth of Binary Tree
- (高质量代码专栏)第一章
- Objective-C Runtime 运行时之五:协议与分类
- MAC下Android环境搭建
- 设计模式一之策略模式
- Objective-C Runtime 运行时之六:拾遗