本文针对目前最新版9.5.1,若非说明,文中所说文档即指官方文档。本人刚接触PostgreSQL不久,文中不免错漏,请大家指正;随着了解深入,本文[可能]会不定期更新补足。
JSON
PostgreSQL支持Json格式数据,有两种类型:json和jsonb。两者在效率上有所区别,而这是因为jsonb存储的是格式化后的二进制数据,所以在写入时,json类型比较快,而在检索时(注意这里说的检索不是简单的读取整个数据,而是比如检索json数据中某个键的值的场景),jsonb效率较高。一般情况下,使用jsonb就可以了。json数据是为了弥补关系型数据在伸缩性扩展性上的不足,但是文档也说了,不能啥都往里放,要考虑数据原子性和数据大小。
json类型可以作包含判断和是否存在的判断(containment or existence),表示符号分别为@>和?(以及其它一些变种)。对于这两种牵涉到多个键和元素的判断场景,json类型比下面要讲的arrays更适合,因为其对查询有内在的优化机制,而array只是单纯的线性查找。
若json列需要经常检索,那么可以在其上建立GIN索引,jsonb支持两种特有的GIN索引jsonb_ops和jsonb_path_ops。创建的语法如下:
CREATE INDEX idxgin ON api USING GIN (jdoc);
CREATE INDEX idxginp ON api USING GIN (jdoc jsonb_path_ops); -- 只是比前一行多了jsonb_path_ops标记
The jsonb_path_ops supports indexing the @> operator only. 关于这两者使用和技术实现上的区别可参看:PgSQL 9.4 新特性jsonb类型解析,PostgreSQL 9.4 中使用 jsonb
我们可以对json数据中的某一属性建GIN索引(可称之为属性索引),如:CREATE INDEX idxgintags ON api USING GIN ((jdoc -> ’tags’)); 这能提升检索键值对的效率,比如如下场景:
SELECT jdoc->’guid’, jdoc->’name’ FROM api WHERE jdoc -> ’tags’ ? ’qui’;
当然我们也可以不使用属性索引,而是换一种查询方式:
SELECT jdoc->’guid’, jdoc->’name’ FROM api WHERE jdoc @> ’{"tags": ["qui"]}’;
jsonb also supports btree and hash indexes. These are usually useful only if it’s important to check equality of complete JSON documents.
Array
PostgreSQL支持Array类型,其字段声明有如下几种方式:
CREATE TABLE emptable (
arraycol1 integer[],
arraycol2 text[][],
arraycol3 text[],
arraycol4 integer ARRAY,
arraycol5 integer ARRAY[]
);
在列声明时我们可以指定数组中的元素类型、维度和长度,后两者然并卵,当前版本的PostgreSQL会忽略这两者的设置,它们更多是以一种备注的意义存在。
插入格式如下:
INSERT INTO emptable VALUES (
’{10000, 10000, 10000, 10000}’,
’{{"meeting", "lunch"}, {"training", "presentation"}}’,
ARRAY[10000, 10000, 10000, 10000],
ARRAY[[’meeting’, ’lunch’], [’training’, ’presentation’]]
);
注意字符串的写法,第3行单引号内部是以双引号包含,第5行ARRAY构造函数方式则是以单引号包含。多维数组中每个元素的长度要一致,否则会报错,比如不能
INSERT INTO emptable VALUES (
’{10000, 10000, 10000, 10000}’,
’{{"meeting", "lunch"}, {"training"}}’ -- error
);
访问,arraycol[n],PostgreSQL的数组默认下标是1基的,这点需要注意,即默认情况下我们访问数组第项应使用arraycol[1],而非惯常的arraycol[0],当然我们可以 SET arraycol[-2:7] = '{XXOO,...}'的方式设置数组的上下界(这个例子就变成了-2基);多维数组访问,以二维数组为例,arraycol[n][m];若下标超出数组长度则返回null,并不会抛出异常。若访问数组某部分毗邻元素,则需要用到slice形式,形如arraycol[1:3][2:5],表示要访问1到3项,并且取这三项中的2到5项——仍以数组形式——返回,第一个中括号表示第1维,第二个表示第2维,以此类推。需要注意的是arraycol[1:3][2],并不是表示取1到3项中的第2项,PostgreSQL认为只要有一个维度是slice形式,则所有你要访问的维度都是slice形式,若只有1位数,则前面附加1:,即arraycol[1:3][2] == arraycol[1:3][1:2]。如果slice的下标超出数组长度,又会怎样呢?有两种情况:若起始下标就超出了,那么返回空数组(文档中说是因为历史原因);若只是结束下标超出,则返回从起始下标到数组末尾这段数据。
一些函数:array_dims,以文本形式返回数组的所有维度;array_length,指定维度的数组长度;array_upper,返回指定维度上界;array_lower,返回指定维度下界;cardinality,所有维度的元素个数总和(不知能否用于子数组或子维度)。
对于一维数组,set arraycol[m] = xxoo,若m大于当前长度,那么arraycol将自动扩充到m上界,而原上界到新上界之间位置的项将置为null,重复一遍,目前只有一维数组有这个特性。
array_prepend、array_append、array_cat用于元素的头尾插入或数组的连接,前两者只能用于一维数组,一般我们可以使用连接符 || 来提供这三者的功能。
数组检索相关:any、all、generate_subscripts、array_position、array_positions、&&(左操作数是否包含右操作数)。关于数组检索,官方文档有这么段提示:数组不是集合,搜索数组中的特定元素通常表明你的数据库设计有问题。 数组字段通常是可以分裂成独立的表(with a row for each item of the array)。 很明显表要容易搜索得多,并且在元素数目非常庞大的时候也可以更好地伸展。这似乎表示数组是设计用来进行直接展示的,若业务查询需要关联数组中的特定值,则需要考虑重新设计或使用其它类型。
插:在使用MySql的时候,我们通常会被告知,使用有最大长度的char或者varchar会在性能方面有好处,而在PostgreSQL中,却不一定是这样。在PostgreSQL中,这三种类型的字符串数据并没有明显的性能差别,而且character(n)类型的数据一般是最慢的,因为固定长度导致更多的存储空间。所以,一般来说,text或者character varying就行了。
Functions
PostgreSQL没有存储过程的概念(博主也不明白为何其它数据库要划分存储过程和函数)。函数会返回最后一条语句的结果[的第一行数据];若要返回结果集,需要显示声明要返回某类型的结果集或Table。 Unless the function is declared to return void, the last statement must be a SELECT, or an INSERT, UPDATE, or DELETE that has a RETURNING clause. You cannot use transaction control commands, e.g. COMMIT, SAVEPOINT, and some utility commands, e.g. VACUUM, in SQL functions. 函数体以双"$"符号或单引号包裹,若用单引号包裹则需要注意特殊字符转义。可以在函数体内以参数名(9.2及以后版本支持)或"$n"的方式引用参数。举个例子:
CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS integer AS $$
UPDATE bank
SET balance = balance - debit
WHERE accountno = tf1.accountno -- 由于参数名和列同名,前面需要加函数名作为前缀
-- ;SELECT balance FROM bank WHERE accountno = tf1.accountno;
RETURNING balance;
$$ LANGUAGE SQL;
在INSERT INTO或者UPDATE的时候在最后面加上RETURNING colname,PostgreSQL会在插入或者更新数据之后会返回你指定的字段。
函数可以接收、返回多个字段,将这多个字段看作一个整体,称为复合类型。比如数据表中的一行,或者使用ROW构造函数构造的一行数据,或者以逗号分隔的多个字段。我们可以显式定义自己的复合类型,如:
CREATE TYPE inventory_item AS (
name text,
supplier_id integer,
price numeric
);
然后就可以将inventory_item用于很多地方了,甚至将一个表字段类型设置为inventory_item,如下:
CREATE TABLE on_hand (
item inventory_item,
count integer
); INSERT INTO on_hand VALUES (ROW('fuzzy dice', 42, 1.99), 1000);
在你创建表的时候,也会自动创建一个复合类型,名字与表名字相同,表示该表的复合类型。需要注意的是,表定义的各项约束(如不可为空)对自动创建的同名复合类型无效。
关于复合类型值写法,上面的ROW方式比较常用,如果是多个字段,那么ROW可以省略,即('fuzzy dice', 42, 1.99);还可以一般格式——'("fuzzy dice",42,1.99)'——外层以单引号包裹。我们可以操作复合类型的整体,也可以针对其某几个字段操作,具体请参考文档。
回到函数的介绍,比如下面两段代码表示的是同一个意思:
--
CREATE FUNCTION new_emp() RETURNS emp AS $$
SELECT
text ’None’ AS name, -- 注意类型转换
1000.0 AS salary,
25 AS age,
point ’(2,2)’ AS cubicle;
$$ LANGUAGE SQL; -- 字段顺序和类型要和返回类型(此处是emp)保持一致 --
CREATE FUNCTION new_emp() RETURNS emp AS $$
SELECT ROW(’None’, 1000.0, 25, ’(2,2)’)::emp;
$$ LANGUAGE SQL;
双冒号:: 表示类型转换。
前面说到,函数能返回集合和表,返回表是最近出版的SQL标准之一,所以可能比返回集合更好一点;但是对于返回表来说,It is not allowed to use explicit OUT or INOUT parameters with the RETURNS TABLE notation — you must put all the output columns in the TABLE list.
Polymorphic SQL Functions:运行select array_to_string('{"meeting", "lunch"}',',');报错:could not determine polymorphic type because input has type "unknown"。原因:SQL functions can be declared to accept and return the polymorphic types anyelement, anyarray, anynonarray, anyenum, and anyrange.This is required if the argument is just a string literal, since otherwise it would be treated as type unknown。而通过文档发现array_to_string(anyarray, text [, text]),array_to_string就是Polymorphic SQL Function。so,要么换成Array形式,要么显式类型转换。
稳定性级别:
PostgreSQL中的函数在定义时有三种稳定性级别:VOLATILE(不稳定)、STABLE(稳定)和IMMUTABLE(非常稳定)。默认情况下,CREATE FUNCTION创建函数的稳定性为VOLATILE。稳定性级别使得优化器可以判断不同函数的行为。
VOLATILE函数可以做任何事情,包括修改数据库。在调用中,输入同样的参数会返回不同的结果,优化器并不对这一类函数的行为做任何假设。在一个Query中,对于每一行都会重新计算该函数。
STABLE函数不能修改数据库,单个Query中所有行给定同样的参数确保返回相同的结果。这种稳定级别允许优化器将多次函数调用转换为一次。在索引扫描的条件中使用这种函数是可行的,因为索引扫描只计算一次比较值(comparison value),而不是每行都计算一次。
IMMUTABLE函数不能修改数据库,在任何情况下,只要输入参数相同,返回结果就相同。这种级别的函数,优化器可以提前进行计算,在查询过程中作为常量参数。比如:SELECT...WHERE x=2+2 可以简化为SELECT...WHERE x=4。
为了得到最佳的优化结果,在创建函数时我们应该指定严格的稳定性级别。
任何有副作用的函数都应该被标记为VOLATILE;另外,有些没有副作用但在一次query中值会发生改变的函数也应该标记为VOLATILE,比如random(),currval(),timeofday()。current_timestamp类型的函数应该被标记为STABLE,因为它们的值在同一事务中不会发生改变。
PostgreSQL还支持函数重载。。。,所以pg的函数定位还和所传参数的个数有关。同时还支持可变参数和参数默认值,某种程度上提高了编写函数的灵活性,但是还是有不方便的地方,比如参数默认值,假如一个函数有多个参数具有默认值,调用的时候,我想保留前面的参数默认值不变,只改变后面若干个参数值,那么也需要传递前面不变的和后面变了的参数值,而不像C#一样,可以指定参数名传递值。所以有些时候还是得写很多个重载,或者在函数内部作判断。
对于返回setof的函数来说,通过select func方式返回的是一列(已逗号分隔的字符串),select * from func返回的才是表格格式。
动态SQL
有时候我们会拼接字符串后,再执行该条语句,PostgreSQL也对这种情况作了支持(注意此时PL/pgSQL's normal attempts to cache plans for commands will not work in such scenarios.执行计划是每次执行动态语句时临时做的)。以PL/Pgsql为例,语法如下:
EXECUTE command-string [ INTO [STRICT] target ] [ USING expression [, ... ] ];
上式中的可选项target表示a record variable, a row variable, or a comma-separated list of simple variables and record/row fields。如果要返回结果集,那么需要用到RETURN QUERY的一个变形:RETURN QUERY EXECUTE command-string [ USING expression [, ... ] ]; 参数表达式可以通过USING插入到计算查询字符串中,以EXECUTE命令的同样方式。
PostgreSQL也提供了一些字符串处理函数,可以更方便地拼接字符串。
quote_ident:Return the given string suitably quoted to be used as an identifier in an SQL statement string。在字符串是表名列名等标识数据库对象时候有用。
quote_literal:Return the given string suitably quoted to be used as a string literal in an SQL statement string.它会对一些特殊字符进行转义。
quote_nullable:当传入参数可能为null时,可使用quote_nullable,而不是quote_literal。前者返回字符串格式的'Null',后者返回的就是Null。当然了pg中所有东西与null比较返回的都是null,这点需要注意。
format:EXECUTE format('UPDATE tbl SET %I = %L WHERE key = %L', colname, newvalue, keyvalue); or EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname) USING newvalue, keyvalue; 后者更有效率,because the parameters newvalue and keyvalue are not converted to text.注意format的格式化类型字符s, I, L. 分别表示字符串, identified, 和literal(注意s、L不要搞反了)。示例:
CREATE OR REPLACE FUNCTION func_get_merchandises(
keyword text,
isinland boolean,
startindex integer DEFAULT 0,
takecount integer DEFAULT 20,
sortfield text DEFAULT 'MerchandiseName'::text,
sortorder text DEFAULT 'asc'::text)
RETURNS SETOF "Merchandises" AS $BODY$
begin
return query EXECUTE
format('select m.* from "Merchandises" m
where m.tsv @@ plainto_tsquery($1) and m."IsInland"=$2
order by %I %s limit $3 offset $4',sortfield,sortorder) using keyword,isinland,takecount,startindex;
end
$BODY$
LANGUAGE plpgsql VOLATILE
需要注意的是第15行sortfield和sortorder不能作为execute参数放在using后面,否则并不会替换,因为前者是标识变量(如表名列名),后者为何并不非常清楚。using参数适用场景,在文档中稍有提及。
PostgreSQL安装与配置
不得不说,自从入门了Linux之后,技术接触面广了很多,更乐意尝试.NET“标配”之外的东西。要在CentOS7.0上安装PostgreSQL,先到PostgreSQL RPM Building Project - Repository Packages找到对应的RPM包,并用yum安装:
yum install https://download.postgresql.org/pub/repos/yum/9.5/redhat/rhel-7-x86_64/pgdg-centos95-9.5-2.noarch.rpm
上步只是install了RPM包,then,安装postgresql-server(还有其它一些packages,不过我们暂时安装postgresql-server即可)
yum install postgresql95-server
如果你安装的是9.4的版本,只要把上面的数字95改成94即可。
Due to policies for Red Hat family distributions, the PostgreSQL installation will not be enabled for automatic start or have the database initialized automatically. To make your database installation complete, you need to perform these two steps:
/usr/pgsql-9.5/bin/postgresql95-setup initdb #初始化库
systemctl start postgresql-9.5.service #启动
为了其它主机能连接到服务器,需要进行一些配置。关于系统参数配置,PostgreSQL提供了多种方式,适用场景稍有不同。这里选择编辑postgresql.conf文件的方式,另外还有个postgresql.auto.conf文件,保存的是系统参数默认值,是不允许直接编辑的,可以使用ALTER SYSTEM 命令进行配置值设定。postgresql.conf存储在PostgreSQL的data目录下,data目录可以在initdb时指定,如下:
initdb -D /usr/local/pgsql/data
#或者如下
pg_ctl -D /usr/local/pgsql/data initdb
This may be more intuitive if you are using pg_ctl for starting and stopping the server, so that pg_ctl would be the sole command you use for managing the database server instance.
不指定data目录的话,会默认给你一个,博主这用find命令看到是/var/lib/pgsql/9.5/data/。因为PostgreSQL实例是依赖于data目录的,所以可以在一台机子上开多个实例,每个实例都有自己的data目录,配置自然也不同;要pg_ctl启动、停止、重启等操作时需要带上data目录,或者指定PGDATA环境变量,否则不知道针对哪个实例进行操作。
find / -name postgresql.conf
#输出 /var/lib/pgsql/9.5/data/postgresql.conf
找到之后,就可以进行设置了,顺便熟悉下vi的操作。
vi postgresql.conf #打开,此时为一般模式
/address #定位到listen_addresses
#或者home键,移动光标到该行最前面
X #删除最前面的井号,即取消该行注释
a #A、i、I等皆可,进入编辑模式,将listen_addresses设为'localhost,开发机IP'
<Esc> #返回一般模式
<Enter> #向下移动4行,定位到#port=,同样删除前面的井号
:wq #保存并退出vi
开放端口centos7之后使用firewall:
firewall-cmd --permanent --zone=public --add-port=/tcp
另外还要修改pg_hba.conf文件,允许开发机连接(窃以为这里和postgresql.conf的listen_addresses稍有重复了),这里就不细说了,注意使用md5方式,表示客户端需要使用用户名和密码(加密)连接服务端。重启PostgresQL。
pg_ctl -D /var/lib/pgsql/9.5/data restart
最后修改默认用户postgres的密码。
# sudo -u postgres psql
postgres=# ALTER USER postgres WITH PASSWORD 'postgres';
EF CodeFirst with PostgreSQL(暂缓)
目前EF操作PostgreSQL使用的是EntityFramework6.Npgsql,版本为3.0.5,大部分的数据类型都支持使用linq操作,然而json和array并不在此列(虽然它所基于的Npgsql是支持json和array类型的)。
Npgsql does support TransactionScope,有两种方法:Include Enlist=true in your connection string, or Call NpgsqlConnection.EnlistTransaction
后记:为什么要选择PostgreSQL?关系数据库,博主接触最多的是SQLSERVER和MYSQL,目前基本上已经告别SQLSERVER,你懂的;MYSQL号称最流行,这点毋庸置疑,但如此流行的原因未必是因为最好的,或者最适用的。在MYSQL里面做递归(递归不是SQL标准),基本上多少都是个坑,似乎也不太跟得上时代的脚步,对NoSQL的支持薄弱,如果你说它只要做好关系型数据库的本分,那么某些SQL标准尚不支持,比如LATERAL;全文检索方面功能不足也是一大软肋 。而PostgreSQL号称是全球/宇宙最先进的数据库,虽有夸大其词之嫌,确实功能比较全面,而且开源,开源协议是MIT,比MYSQL的GPL来得更*。
Postgres中执行:UPDATE "TTest" SET "CTest"= floor(random()*16+1); 会发现每条记录的CTest并不一致,可知并非先生成随机数再统一赋值,而是逐一生成不同的随机数。不知道其它数据库是怎样。
postgres游标:
do $$
declare
tn text;
curs1 CURSOR for select tablename from pg_tables where schemaname='public';
begin OPEN curs1;
loop
fetch curs1 into tn;
if not found then
exit;
end if;
--RAISE INFO 'VARIABLE: %', tn;
EXECUTE 'ALTER TABLE "' || tn || '" OWNER TO masondever';
end loop;
close curs1; end; $$language plpgsql;
有用的函数:coalesce、string_agg
视图分为普通视图和物化视图,物化视图是物理存在的,可以认为是数据库层的缓存或临时存储,当基础表数据更新时,需要手动刷新。由于物化视图是真实存在的,可在其上建索引提高查询效率。物化视图创建——CREATE
MATERIALIZED
VIEW ...
关于PL/Pgsql的语法可参看Chapter 40. PL/pgSQL - SQL Procedural Language。文档中所说的SQL语法相对PL/Pgsql来说,your client application must send each query to the database server, wait for it to be processed, receive and process the results, do some computation, then send further queries to the server. 这应该指的是标准SQL语法没有变量和控制结构等,导致中间过程处理只能移到外部(比如应用层)。
发现了一个比较坑的地方(使用的是9.4版):select r.ShopId from (select p."ShopId" from "IndexProductInfoes" p) r 是不行的,因为pg对大小写敏感,列名若有大写字母,则必须以双引号括起来,否则会报错“column r.shopid does not exist”,可以看到,虽然代码里我们写的是r.ShopId,但提示信息是shopid,pg自动给转为小写了。以下几种写法都是正确的:
1、select r."ShopId" from (select p."ShopId" from "IndexProductInfoes" p) r; --结果列名ShopId
2、select r.ShopId from (select p."ShopId" ShopId from "IndexProductInfoes" p) r; --结果列名shopid
3、select r.shopid from (select p."ShopId" ShopId from "IndexProductInfoes" p) r; --结果列名shopid
参考资料:
转载请注明本文出处:http://www.cnblogs.com/newton/p/5203957.html