Postgres SQL 实用版 都学会的已是大神! 记得收藏



-------------------------------
/*
 * 日期时间
 */
-- 当前日期
select current_date;
-- 日期差
select age(timestamp '2018-07-20');
select age('2018-02-12', '2000-01-01');
select age(timestamp '2017-01-26', timestamp '1951-08-15');

-- 当前时间
select current_time(2);
-- 当前日期时间
select current_timestamp(2);
-- 本地时间戳
select localtimestamp(0);
-- 返回timestamp [(p)] with time zone
select now(), now()::timestamp(0);
select now()::date;
select now()::time, now()::time with time zone;
select now(), now() + interval '1 day';

-- extract() :抽取函数
select extract(year from now()), extract(month from now()), extract(day from now()), -- 年月日
			 extract(hour from now()), extract(minute from now()), extract(second from now()), -- 时分秒
			 extract(week from now()), -- 取当前日期所在年份的第几周
			 extract(doy from now()); -- 当天属于当年的第几天

-- 时间相差的小时数
select round(date_part('epoch', timestamp '2013-11-11 10:01:00' - timestamp '2013-11-08 12:00:00')::NUMERIC / 3600, 2)

-- 截取年月日时分秒、毫秒
select date_part('year', '2018-09-19 23:34:34.926123456789'::timestamp) as year,
date_part('years', '2018-09-19 23:34:34.926123456789'::timestamp) as years,
date_part('month', '2018-09-19 23:34:34.926123456789'::timestamp) as month,
date_part('day', '2018-09-19 23:34:34.926123456789'::timestamp) as day,
date_part('hour', '2018-09-19 23:34:34.926123456789'::timestamp) as hour,
date_part('minute', '2018-09-19 23:34:34.926123456789'::timestamp) as minute,
date_part('second', '2018-09-19 23:34:34.926123456789'::timestamp) as second,
date_part('millisecond', '2018-09-19 23:34:34.926123456789'::timestamp) as millisecond

-- 获取每月一号、每个季度一号
select  trunc(12.12), trunc(12.12, 0), trunc(12.12, 1), trunc(12.12, -1);
select date_trunc('quarter', now()) quarter, 
date_trunc('month', now()) mon, 
date_trunc('week', now()) week,
date_trunc('day', now()) d, 
date_trunc('h', now()) h, 
date_trunc('m', now()) minu, 
date_trunc('s', now()) s;

/*
 * 数学函数
 */
/*
 * width_bucket(op numeric, b1 numeric, b2 numeric, count in)
 */
/*
 * 在1000-5000之间均匀划分4个等级,低于1000为0等,高于5000为5等。
 * 即1000=<sal<=1999为1等,2000 <= sal <= 2999为2等,依次类推。
 */
select ename, sal, width_bucket(sal, 1000, 5000, 4) from emp;
-- 如下语句,注意count(1)必须写在后面。按等级分组统计个数
select width_bucket(sal, 1000, 5000, 4) grade, count(1) 
from emp group by 1 order by grade;
-- 等级占比
select t.grade, t.count, round(100 * (count/sum(count) over()), 2) || '%' percent 
from (select width_bucket(sal, 1000, 5000, 4) grade, count(1) from emp group by 1) t
order by t.grade;
-- 数组分组统计(数组方式统计支持非均匀分布)
select width_bucket(sal, array[1000.0,1500.0,3000.0,4000.0,5000.0]) grade, count(1) 
from emp group by 1 order by grade;

/*
 * 此外还可以使用kmeans插件、MADlib插件分类统计,MADlib插件是PostgreSQL的开源机器学习库。
 */

-- 取0和1之间的随机数
select random();
-- 取介于两数之间的随机数
select random() * (100 - 10) + 10;
-- 取介于两数之间的随机整数
select floor(random() * (100 - 10) + 10);

-- 取整
select trunc(23.4567);
-- 截取保留2位小数
select trunc(23.4567, 2);
-- 截取保留2位小数
select trunc('23.4567', 2);

-- 求平方根,立方根
select sqrt(16), cbrt(27);

-- 取参数的符号(-1,0,1)
select sign(-23);
select sign(0);
select sign(23);

-- 为以后被调用的 random()函数设置种子
select setseed(0.54823), random();

-- 求2的3次方
select power(2, 3);
-- "π" 常量
select pi();

-- r:把角度转化为弧度,d:把弧度转化为角度
select radians(360) r, degrees(6.28318530717959) d;

-- 取模
select mod(7, 50);

-- 默认底数为10,即10^2=100,指定底数的对数,即5^3=125
select log(100), log(5, 125);

-- 自然对数的底e约等于2.718281828459…,即e^2.19722457733622=9
select ln(1), ln(9);

-- 自然指数e^2.19722457733622=9
select exp(2.19722457733622);

-- 不大于参数的最大整数
select floor(13.5), floor(13.4), floor(-13.4), floor(-13.5);
-- 不小于参数的最小整数
select ceiling(13.5), ceiling(13.4), ceiling(-13.4), ceiling(-13.5);

-- 取绝对值
select abs(12.2), abs(0), abs(-12.2);

-- 长度
select bit_length('1234'), char_length('1234'), octet_length('1234'), length('1234'), length('中国');

-- 替换子串
select overlay('Hello World' placing '#@$#' from 2 for 4);

-- 查找子串在字符串中第一次出现的位置
select position('o' in 'Hello World');

/* 转义的两种方式:1.字符串前面加上E,然后使用反斜杠,2.需要转义的单引号前面再加一个单引号
 * 注意:不想要每次都写E或者加上单引号就直接使用自己习惯的反斜杠,可以设置postgresql的一个参数:
 * set standard_conforming_strings = on;
 * substring() 截取子串
 */
select substring(E'THE WORLD\'S MOST ADVANCED OPEN SOURCE RELATIONAL DATABASE', 1),
			 substring('THE WORLD''S MOST ADVANCED OPEN SOURCE RELATIONAL DATABASE', 1),
			 substring('THE WORLD''S MOST ADVANCED OPEN SOURCE RELATIONAL DATABASE', 1, 5),
			 substring('THE WORLD''S MOST ADVANCED OPEN SOURCE RELATIONAL DATABASE' from 1 for 5),
			 substring('hello' from '..$');

/*
 * 从字符串string的开始、末尾或者开始和末尾删除只包含指定的字符串characters 中的字符的最长的字符串。
 * 如果没有指定参数characters,则它的值默认是空格。
 * leading表示只删除字符串头部匹配的子串。
 * trailing 表示只删除字符串尾部匹配的子串。
 * both表示同时删除字符串头部和尾部匹配的子串。
 */
select trim(both 'o' from 'hello'), trim(both 'gxf' from 'xgTxTomxxf'), trim(both  from '  xgTxTomxxf  '),
			 trim(leading 'xf' from 'xTomxxf'), trim(trailing 'xf' from 'xTomxxf'), trim('  we  '), trim('12341', '1');
			 
-- btrim()
select btrim('xyxtrimyyx', 'xy'), btrim('xgTomxxf','gxf');

-- ltrim(), rtrim()
select ltrim('zzzytrim', 'xyz'), rtrim('trimxxxx', 'x');

-- 大小写
select lower('WWWqwer'), upper('WWWqwer');

-- ASCII码
select ascii('a'), ascii('z'), ascii('A'), ascii('Z'), ascii('ab'), ascii('中');
-- chr();返回指定编码值的字符
select chr(97), chr(122),chr(65),chr(90), chr(20013);
-- md5()加密
select md5('admin');
-- 左/右补齐
select lpad('where', 10, 'xy'), lpad('where', 4, 'xy'), lpad('where', 10, 'x'), lpad('where', 10),
			 rpad('where', 10, 'xy'), rpad('where', 4, 'xy'), rpad('where', 10, 'x'), rpad('where', 10);
			 
-- convert()字符串编码转换

-- 客户端编码
select pg_client_encoding();

-- 用ISO 8859-1格式表示的字符串text_in_utf8
select convert('text_in_utf8', 'UTF8', 'LATIN1');

-- 用数据库当前的编码类型表示的字符串text_in_utf8
select convert_from('text_in_utf8', 'UTF8');

-- 用UTF8编码的字符串
select convert_to('some text', 'UTF8');

-- base64编码解码
select decode('MTIzAAE=', 'base64'), decode('3132330001','hex'),  decode('abc', 'escape'),
			 encode(E'123//000//001', 'base64'), encode(E'123//000//001', 'hex'), encode('abc', 'escape');
			 
-- 分割成子串,首字母大写,其他字母小写。
select initcap('hi THOMAS'), initcap('hh+jj');

/*
 * quote_ident(string text):返回字符串string作为合法的SQL标识符的表示形式。
 * quote_literal(string text):将字符串string转换成一个合法的SQL语句字符串常量的形式。
 * quote_literal(value anyelement):将 value转换成字符串常量,value必须是一个数值。
 */
select quote_ident('Foo bar'), quote_ident('abc'),quote_ident('abc'''),
			 quote_literal('O/"Reilly'), quote_literal('abc//'), quote_literal(42.5);

/*
 * regexp_matches(string text, pattern text [, flags text])
 * 返回所有匹配指定的POSIX正则表达式的子串
 * regexp_replace(string text, pattern text, replacement text [, flags text])
 * 用字符串 replacement替换所有匹配指定的POSIX正则表达式的子串。
 * regexp_split_to_array(string text, pattern text [, flags text ])
 * 使用POSIX正则表达式作为分割符来分割字符串。
 * regexp_split_to_table(string text, pattern text [, flags text])
 * 使用POSIX正则表达式作为分割符来分割字符串。
 */
select regexp_matches('foobarbequebaz', '(bar)(beque)'),
			 regexp_replace('ThomasMM', '.[mN]a.', 'M'),
			 regexp_replace('北郊-马旗寨24D', '/[1-9]\d*/D.', 'M'),
			 regexp_split_to_array('hello world', E'//s+'),
			 regexp_split_to_table('hello world', E'//s+');

-- 将字符串string重复指定的次数。
select repeat('Pg', 4);

-- replace(string text, from text, to text):将字符串string中的所有子串from用子串to代替。
select replace('abcdefabcdef', 'cd', 'XX');

/*
 * split_part(string text, delimiter text, field int)
 * 将字符串string用分割符delimiter分成多个域后,返回field指定的域(域的编号从1开始)。delimiter可以是一个字符串。
 */
select split_part('abc~@~def~@~ghi', '~@~', 2), split_part('abc$de$f','$',3);

-- 返回子串在字符串中的位置。
select strpos('high', 'ig'), strpos('我是','是');

-- 1.将用 ASCII表示的字符串转换成其它编码类型的字符串(只支持LATIN1, LATIN2, LATIN9和WIN1250)
-- 2.将数字转换成十六进制的表示形式。
select to_ascii('Karel'), to_hex(2147483647);

/*
 * translate(string text, from text, to text)
 * 如果字符串string中的某个字符匹配字符串from的某个字符,
 * 则string中的这个字符将用字符串to中和from中的匹配字符对应的字符代替。
 */
select translate('12345', '14', 'ax'), translate('abcdef','abc','f'), translate('abcdef','abc','fg')

/*
	表
*/
-- 数据库
create database testdb;
drop database testdb;
-- 表
create table public.student (
	id integer not null,
	name character(100),
	subject character(1),
	constraint student_pkey primary key (id)
);
comment on table student is '学生表';
-- 删除表
drop table student;
-- 清空表
truncate table table_name;
-- 批量删除
delete from ww 
using (values ('22'), ('23')) as tmp(id) 
where ww.id = tmp.id;
-- regexp_split_to_table(),用此函数来优化in
select t.id, t.mc from ozt t where t.id = any(values('650104792253937'));
select t.id, t.mc from ozt t where t.id in ('650104792253937','41302219631105404401');
select ozt.id, ozt.mc
from (select regexp_split_to_table('650104792253937,41302219631105404401', ',') as ids) as tmp 
left join ozt on tmp.ids = ozt.id

-- 联合主键
alter table 你的表名 add constraint pk_你的表名  primary key (字段1,字段2)

-- 高效去重(表名ww,去重字段dd)
delete from ww
where ctid = any(array(select ctid 
		from (select row_number() over (partition by dd), ctid from ww) x 
		where x.row_number > 1 
));

-- upsert更新
insert into student (id, name, subject) values (69, 'eee', 'e');
-- 当id冲突时更新name值
insert into student (id, name, subject) values (69, 'eee1', 'e') on conflict(id) do update set name = excluded.name;
-- id,name 作为联合主键,冲突时不做任何操作
insert into student (id, name, subject) values (69, 'eee1', 'e') on conflict(id, name) do nothing;
-- 只插入满足条件的数据行
insert into student (id, name, subject) values (70, 'qqq', 'q'), (69, 'rrr', 'r') on conflict(id) do nothing;

/*
union 去重复行
union all 不去重
必须保持列数,列类型一致
注:
1.列类型不一致时,可用(e.ename)::varchar转(::前应加括号)
2.select 语句中不可用order by、limit等,使用时可将当前语句写成子查询,在外层select上排序。
*/
select e.ename from emp e
union all
select d.dname from dept d

-- 条件表达式
/*
case when condition then result [when ...] [else result] end
*/
select case deptno when 10 then '10号部门' when 20 then '20号部门' else '未知部门' end from dept;

-- 返回它的第一个非NULL的参数的值
select coalesce(null, '', '123');

/*
	nullif(value1, value2)
	当且仅当value1和value2相等时,NULLIF才返回NULL。否则它返回value1。
*/
select nullif('asd', 'asd');
select nullif('asd', 'asdf');

/*
greatest(value [, ...]),least(value [, ...])
函数从一个任意的数字表达式列表里选取最大或者最小的数值。列表中的NULL数值将被忽略。
只有所有表达式的结果都是NULL的时候,结果才会是NULL。
*/
-- 取最大
select greatest(1,3,7);
-- 取最小
select least(4,0,23);

-- 类型转换、cast()用法
select 1/4;

-- 约为接近参数的2位小数的数字
select round(1::numeric/4::numeric, 2); -- 四舍五入
select round( cast ( '1' as numeric )/ cast( '4' as numeric),2);
select substr(cast (1234 as text), 3,1);

/*
	like(区分大小写)、ilike(不区分大小写)
	LIKE和SIMILAR TO都支持模糊查询,另外SIMILAR TO还支持正则表达式查询。
	模糊查询中有两个重要的符号:
		下划线'_'匹配任意单个字符,百分号'%'匹配任意多个字符,可以是0个,
		如果想匹配'_'和'%',必须在'_'和'%'前使用反斜线(\)逃逸。
		
*/
select * from emp where job like '_ALESMAN';
select * from emp where job ilike '_ALESman';
-- 查询字段c中包含下划线'_'的行, regexp_split_to_table()行转列
select t.* from (select regexp_split_to_table('hello_world,www.baidu.com,table_name', ',') c) t where c like '%\_%';
-- 查询字段c中包含百分号'%'的行
select t.* from (select regexp_split_to_table('hello%,%www.baidu.com,table_name', ',') c) t where c like '%\%%';
-- | 表示选择(二选一,如a|b,类似or)
select t.* from (select regexp_split_to_table('abc,ABC,AbC,aBc', ',') c) t where c similar to '%(Ab|aB)c%';
-- * 查询c字段中以'3'结尾,前面有0个或多个'1'的行
-- TODO:如下未查询到结果
select t.* from (select regexp_split_to_table('q124311114,q3213,q222113,q4525213', ',') c) t where c similar to '1*3';
select t.* from (select regexp_split_to_table('q124311114,q3213,q222113,q4525213', ',') c) t where c similar to '1+3';
select t.* from (select regexp_split_to_table('124311114,3213,222113,4525213', ',') c) t where c similar to '[0-9]3';
select t.* from (select regexp_split_to_table('124311114,3213,222113,4525213', ',') c) t where c similar to '[1,4]3';
select * from emp where ename similar to 'T+T';

-- 模式
create schema myschema;
drop schema myschema;

-- 增删改查分组排序分页
insert into student(id, name, subject)  
values
(1, '张三', '数'), 
(2, '王五', '英'), 
(3, '李洋', '语');

-- 简写
update student set name = '小小' where id = 1;
-- 查询更新
update tb1 set score = coalesce(b.scorea, 0) from tb1 a
left join tb2 b on a.ids=b.ida 
where a.ids = tb1.ids;
-- 删除
delete from student where id = 3;
-- 分页(从第一条数据开始查询两条数据)
select id, name, subject from student limit 2 offset 0;
select name, subject from student group by name, subject order by subject desc;
select name, subject from student group by name, subject having subject = '英' order by name desc;

-- 如果两张表的关联字段名相同,也可以使用USING子句。
select e.ename, e.deptno, d.deptno, d.dname from emp e left join dept d using(deptno);

-- having子句
select e.job from emp e group by e.job having count(e.job) < 2;

-- 视图
create or replace view v_student as select id, name, subject from student;
select * from v_student;
drop view v_student;

create or replace view v_emp_dept as 
select e.empno, e.ename, e.job, e.mgr, e.hiredate, e.sal, e.comm, e.deptno, d.dname, d.loc from emp e left join dept d using(deptno)

-- 创建临时视图,session会话范围
create or replace temp view v_emp (empno1, ename1) as select empno, ename from emp;
insert into v_emp (empno1, ename1) values ('12', '12');

-- 创建物化视图
create materialized view v_m_emp as select e.empno, e.ename, e.job, e.mgr, e.hiredate, e.sal, e.comm, e.deptno, d.dname, d.loc from emp e left join dept d using(deptno)

-- 对比效率,物化视图的效率高于视图
explain select * from v_emp_dept;
explain select * from v_m_emp;

-- 物化视图的数据填充
-- 数据填充命令(refresh materialized view)

/*
触发器是一组动作或数据库回调函数.
1.PostgreSQL在以下情况下执行/调用触发器:在尝试操作之前(在检查约束并尝试INSERT,UPDATE或DELETE之前)。
	或者在操作完成后(在检查约束并且INSERT,UPDATE或DELETE完成后)。
	或者不是操作(在视图中INSERT,UPDATE或DELETE的情况下)
2.对于操作修改的每一行,都会调用一个标记为FOR EACH ROWS的触发器。
	另一方面,标记为FOR EACH STATEMENT的触发器只对任何给定的操作执行一次,而不管它修改多少行。
3.您可以为同一事件定义同一类型的多个触发器,但条件是按名称按字母顺序触发。
4.当与它们相关联的表被删除时,触发器被自动删除。
*/
create trigger example_trigger after insert on student
for each row execute procedure func_audit_log();
insert into student (id, name, subject) values ('55', '五五', '中');

select * from audit;

/*
	函数(存储过程)
	1.概述:
	PL/pgSQL(块结构语言)函数在第一次被调用时,其函数内的源代码(文本)将被解析为二进制指令树,
	但是函数内的表达式和SQL命令只有在首次用到它们的时候,PL/pgSQL解释器才会为其创建一个准备好的执行规划,
	随后对该表达式或SQL命令的访问都将使用该规划。如果在一个条件语句中,
	随后对该表达式或SQL命令的访问都将使用该规划。如果在一个条件语句中,
	有部分SQL命令或表达式没有被用到,那么PL/pgSQL解释器在本次调用中将不会为其准备执行规划,
	这样的好处是可以有效地减少为PL/pgSQL函数里的语句生成分析和执行规划的总时间,
	然而缺点是某些表达式或SQL命令中的错误只有在其被执行到的时候才能发现。

	不能将函数的参数用作SQL命令的表名或字段名。如果想绕开该限制,可以考虑使用PL/pgSQL中的EXECUTE语句动态地构造命令,
	由此换来的代价是每次执行时都要构造一个新的命令计划。

	2.PL/pgSQL的结构:
	[ <<label>> ]
	[ DECLARE declarations ]
	BEGIN
			statements
	END [ label ];
	
	3.声明:
	所有在块里使用的变量都必须在块的声明段里先进行声明,唯一的例外是FOR循环里的循环计数变量,
	该变量被自动声明为整型。变量声明的语法如下:
	variable_name [ CONSTANT ] variable_type [ NOT NULL ] [ { DEFAULT | := } expression ];
	1). SQL中的数据类型均可作为PL/pgSQL变量的数据类型,如integer、varchar和char等。
	2).如果给出了DEFAULT子句,该变量在进入BEGIN块时将被初始化为该缺省值,否则被初始化为SQL空值。
	缺省值是在每次进入该块时进行计算的。因此,如果把now()赋予一个类型为timestamp的变量,
	那么该变量的缺省值将为函数实际调用时的时间,而不是函数预编译时的时间。
	3). CONSTANT选项是为了避免该变量在进入BEGIN块后被重新赋值,以保证该变量为常量。
	4). 如果声明了NOT NULL,那么赋予NULL数值给该变量将导致一个运行时错误。
	因此所有声明为NOT NULL的变量也必须在声明时定义一个非空的缺省值。
	3.1 函数参数的别名:
	传递给函数的参数都是用$1、$2这样的标识符来表示的。为了增加可读性,我们可以为其声明别名。
	之后别名和数字标识符均可指向该参数值,见如下示例:
	a.在函数声明的同时给出参数变量名。
	b.在声明段中为参数变量定义别名。
	c.如果PL/pgSQL函数的返回类型为多态类型(anyelement或anyarray),
	那么函数就会创建一个特殊的参数:$0。我们仍然可以为该变量设置别名。
	3.2 拷贝类型:
	声明:variable%TYPE
	示例:user_id users.user_id%type;
	说明:变量user_id的数据类型等同于users表中user_id字段的类型,在函数的参数和返回值中也可使用该方式的类型声明。
	3.3 行类型:
	声明:name table_name%ROWTYPE;(推荐)或者name composite_type_name;
  说明:由此方式声明的变量,可以保存SELECT返回结果中的一行。
	如果要访问变量中的某个域字段,可以使用点表示法,如rowvar.field
	3.4 记录类型:
	声明:name RECORD;(RECORD不是真正的数据类型,只是一个占位符。)
	说明:记录变量类似于行类型变量,但是它们没有预定义的结构,只能通过SELECT或FOR命令来获取实际的行结构,
	因此记录变量在被初始化之前无法访问,否则将引发运行时错误。
	
	4.基本语句:
	4.1 赋值:
	声明:identIFier := expression
	示例:tax := subtotal * 0.06;
	4.2 SELECT INTO:
	为记录变量或行类型变量进行赋值.
	4.3 执行一个没有结果的表达式或者命令:
	声明:perform query
	4.4 执行动态命令:
	声明:EXECUTE command-string [ INTO target ]
	示例:EXECUTE 'UPDATE tbl SET ' || quote_ident(columnname) || ' = ' || quote_literal(newvalue); 
	操作的表或数据类型在每次调用该函数时都可能会发生变化,其中command-string是用一段文本表示的表达式,
	它包含要执行的命令。而target是一个记录变量、行变量或者一组用逗号分隔的简单变量和记录/行域的列表。
	这里需要特别注意的是,该命令字符串将不会发生任何PL/pgSQL变量代换,变量的数值必需在构造命令字符串时插入到该字符串中。
	一个由EXECUTE语句运行的命令在服务器内并不会只prepare和保存一次。相反,该语句在每次运行的时候,命令都会prepare一次。
	因此命令字符串可以在函数里动态的生成以便于对各种不同的表和字段进行操作,从而提高函数的灵活性。
	然而由此换来的却是性能上的折损。
	
	5. 控制结构:
	5.1 函数返回:
	a.声明:return expression
	说明:终止当前的函数,然后再将expression的值返回给调用者。
	b.声明:return next expression
	说明:如果PL/pgSQL函数声明为返回SETOF sometype,其行记录是通过RETURN NEXT命令进行填充的,
	直到执行到不带参数的RETURN时才表示该函数结束。
	5.2 条件:
	a.if-then
	if boolean-expression then
		statements
	end if;
	b.if-then-else
	if boolean-expression then
		statements
	end
		statements
	end if;
	c.if-then-elsif-else
	if boolean-expression then
		statements
	elsif boolean-expression then
		statements
	elsif boolean-expression then
		statements
	else
		statements
	end if;
	5.3 循环
	a.loop
	loop
		statements
	end looop [lable];
	b.exit
	exit [lable] [when expression];
	c.continue
	continue [lable] [when expression];
	d.while
	[<<lable>>]
	while expression loop
		statements
	end loop[lable];
	e.for
	[<<lable>>]
	for name in [reverse] expression .. expression loop
		statements
	end loop [lable];
	5.4 遍历命令结果:
	[<<lable>>]
	for record_or_row in query loop
		statements
	end loop [lable];
	或者
	[<<lable>>]
	for record_or_row in execute text_expression loop
		statements
	end loop [lable];
	5.5 异常捕获
	[<<lable>>]
	[declare
		declarations]
	begin
		statements
	exception
		when condition [or condition ...] then
			handler_statements
		when condition [or condition ...] then
			handler_statements
	end;
	
	6.游标:
	6.1 声明游标变量:
	在PL/pgSQL中对游标的访问都是通过游标变量实现的,其数据类型为refcursor。 
	创建游标变量的方法有以下两种:
	a.和声明其他类型的变量一样,直接声明一个游标类型的变量即可。
	b.使用游标专有的声明语法,如:
    name CURSOR [ ( arguments ) ] FOR query;
	curs1 refcursor;
  curs2 cursor for SELECT * FROM tenk1;(游标已被绑定)
  curs3 cursor (key integer) IS SELECT * FROM tenk1 WHERE unique1 = key;(游标已被绑定)
	6.2 打开游标
	a.open for:
	声明形式:open unbound_cursor for query;
	该形式只能用于未绑定的游标变量,其查询语句必须是SELECT,或其他返回记录行的语句。
	b.open for execute:
	声明形式:open unbound_cursor for execute query-string
	c.打开一个绑定的游标
	声明形式:open bound_cursor [(argument_values)];
	6.3 使用游标
	游标一旦打开,就可以按照以下方式进行读取。然而需要说明的是,
	游标的打开和读取必须在同一个事物内,因为在PostgreSQL中,
	如果事物结束,事物内打开的游标将会被隐含的关闭。
	a.fetch
	声明形式:fetch cursor into target;
	b.close
	声明形式:close cursor;
	
	7.错误和消息
	用raise报告信息和抛出错误。
	声明形式:raise level 'format' [, expression [, ...]];
	这里包含的级别有DEBUG(向服务器日志写信息)、LOG(向服务器日志写信息,优先级更高)、
	INFO、NOTICE和WARNING(把信息写到服务器日志以及转发到客户端应用,
	优先级逐步升高)和EXCEPTION抛出一个错误(通常退出当前事务)。
	某个优先级别的信息是报告给客户端还是写到服务器日志,还是两个均有,
	是由log_min_messages和client_min_messages这两个系统初始化参数控制的。
	
  在format部分中,%表示为占位符,其实际值仅在RAISE命令执行时由后面的变量替换,
	如果要在format中表示%自身,可以使用%%的形式表示,见如下示例:
  RAISE NOTICE 'Calling cs_create_job(%)',v_job_id;  --v_job_id变量的值将替换format中的%。
  RAISE EXCEPTION 'Inexistent ID --> %',user_id;   

	1.函数可以重载,删除函数时必须指定参数列表。
*/
create or replace function func_audit_log()
returns trigger as $example_table$
begin
	insert into audit (id, entry_date) values (new.id, current_timestamp);
	return new;
end;
$example_table$ language plpgsql;

-- 获取学生表记录总数
create or replace function total_records()
returns integer as $total$
declare
	total integer;
begin
	select count(*) into total from student;
	return total;
end;
$total$ language plpgsql;

select total_records();

-- 获取表中id的最大值(存储过程的对象不可以直接用变量,要用 quote_ident(objVar))
create or replace function f_get_new_id(table_name text, field_name text)
returns integer as $$
declare
	my_id integer;
	my_sql text;
begin
	my_sql = 'select max('|| quote_ident(field_name) ||') from ' || quote_ident(table_name);
	execute my_sql into my_id;
	if my_id is null or my_id = 0
		then return 1;
	else return my_id + 1;
  end if;
end;
$$ language plpgsql;

select f_get_new_id('dept', 'deptno');

-- setof 返回多行记录
create type compfoo as (col1 integer, col2 text);

create or replace function get_set(rows integer)
returns setof compfoo
as $$
begin
	return query select i * 2, i || '_text' from generate_series(1, rows, 1) as t(i);
end;
$$ language plpgsql;

select col1, col2 from get_set(3);

-- return table 返回多行多列
create or replace function get_table(rows integer)
returns table(col1 integer, col2 text)
as $$
begin
	return query select i * 2, i || '_text' from generate_series(1, rows, 1) as t(i);
end;
$$ language plpgsql;

select col1, col2 from get_table(3);

-- 多态SQL函数
create or replace function get_array(anyelement, anyelement)
returns anyarray
as $$
select array[$1, $2];
$$ language sql;

select get_array(1, 2), get_array('a'::text, 'b'::text);

/*
 * 游标
 */
create or replace function cursor_test1()
returns refcursor as $$
declare
	unbound_refcursor refcursor;
	v_id int;
	v_step_desc varchar(20);
begin
	open unbound_refcursor for execute 'select empno, ename from emp';
	loop
		fetch unbound_refcursor into v_id, v_step_desc;
		if found then
			raise notice '%-%', v_id, v_step_desc;
		else
			exit;
		end if;
	end loop;
	close unbound_refcursor;
	raise notice 'the end of msg ...';
	return unbound_refcursor;
exception when others then
	raise notice 'error...';
end;
$$ language plpgsql;

begin;
select * from cursor_test1();
commit;

/*
 ·索引:
		类型:B-tree(默认),Hash,GiST,SP-GiST,GIN和BRIN
		目前多列索引最多支持32列
*/
-- 单列
create index student_id_index on student (id);
-- 多列
create index student_mm_index on student (id, name);

/*
排序:
rank()        	发生不持续的编号 例如数据值 1,2,2,3 发生的编号将是1,2,2,4
dense_rank()   	发生持续的编号 例如数据值 1,2,2,3 发生的编号将是1,2,2,3
row_number()   	发生持续的编号(不重复) 例如数据值 1,2,2,3 发生的编号将是1,2,3,4
percent_rank()	限制序号在0~1之间排序,含0
cume_dist()			限制序号在0~1之间排序,不含0
ntile(val1)			限制最大序号排序
lag(ename,1,'')	获取到排序数据的每一项的偏移值(向下偏移),参数含义:(输出的上一条记录的字段,偏移值,无偏移值的默认值)
lead(ename ,1, '') 	获取到排序数据的每一项的偏移值(向上偏移)
first_value(ename) 	获取分类子项排序中的第一条记录的某个字段的值
last_value(ename) 	获取分类子项排序中的最后一条记录的某个字段的值
nth_value(ename,2) 	取得排序字段项目中指定序号记录的某个字段值
*/
select e.deptno, e.ename, e.sal,
rank() over(partition by e.deptno order by e.sal) rank,
dense_rank() over(partition by e.deptno order by e.sal) dense_rank,
row_number() over(partition by e.deptno order by e.sal) row_number,
percent_rank() over(partition by e.deptno order by e.sal) percent_rank,
cume_dist() over(partition by e.deptno order by e.sal),
ntile(3) over(partition by e.deptno order by e.sal),
lag(ename, 1, '') over(partition by e.deptno order by e.sal),
lead(ename ,1, '') over(partition by e.deptno order by e.sal),
first_value(ename) over(partition by e.deptno order by e.sal),
last_value(ename) over(partition by e.deptno order by e.sal range between unbounded preceding and unbounded following),
nth_value(ename,2) over(partition by e.deptno order by e.sal range between unbounded preceding and unbounded following)
from emp e;

select * from emp;

-- 用::类型转换
select oprname, oprleft::regtype, oprright::regtype, oprresult::regtype, oprcode from pg_operator;

/**
 * 窗口函数
 * 允许在range窗口模式中使用off_set preceding/following限定窗口范围
 * 支持groups窗口模式,使用基于组的方式限定窗口范围
 * 支持窗口范围子句的排除选项,用于排除窗口内的某些数据行
 *
 * PostgreSQL 11 支持 SQL:2011 标准中定义的所有窗口范围子句选项
 */
create table tbl_window_function (
	id serial primary key,
	val int4,
	logtime timestamp
);

insert into tbl_window_function (val, logtime)
values (1, '2019-01-15 08:08:17'),
(1, '2019-01-15 08:14:30'),
(3, '2019-01-15 08:36:00'),
(6, '2019-01-15 09:20:56'),
(6, '2019-01-15 10:15:41');

/**
 * sum_rows 列计算按照字段 val 排序后,每一行以及前后各一行的合计值;
 * sum_range 列计算按照字段 val 排序后,每一行以及前后和它的值相差小于等于 1 的那些行的合计值;
 * sum_groups 计算按照字段 val 排序后,每一行所在的组以及前后各一个组中所有行的合计值。
 * sum_range_time 列计算按照字段 logtime 排序后,每一行以及前后和它的值相差小于等于 10 分钟的那些行的合计值;
 *
 * 对于groups窗口模式,计算窗口范围时使用组(由order by决定)为单位,而不是以行rows模式)或者值range模式)为单位。
 *
 * 窗口范围子句的排除选项 frame_exclusion,用于排除窗口内的某些数据
 * exclude current row 排除当前行
 * exclude group 排除当前行所在的组,也就是和当前行排序相同的行
 * exclude ties 排除当前行所在组中的其他行,但是不排除当前行
 * exclude no others 不排除任何行,默认值
 */
select id, val, logtime,
sum(val) over(order by val rows between 1 preceding and 1 following) as sum_rows,
sum(val) over(order by val range between 1 preceding and 1 following) as sum_range,
sum(val) over(order by val groups between 1 preceding and 1 following) as groups,
sum(val) over(order by logtime range between interval '10 minute' preceding and '10 minute' following) as sum_range_time,
sum(val) over(order by val rows between 1 preceding and 1 following exclude no others) as sum_rows,
sum(val) over(order by val rows between 1 preceding and 1 following exclude current row) as sum_rows,
sum(val) over(order by val rows between 1 preceding and 1 following exclude group) as sum_rows,
sum(val) over(order by val rows between 1 preceding and 1 following exclude ties) as sum_rows
from tbl_window_function;

/*
	string_agg();	array_to_string();	array_agg();
*/
-- 列转行
select deptno, string_agg(ename, ',' order by ename desc) from emp group by deptno;
select deptno, array_to_string(array_agg(ename), ',') from emp group by deptno;
select deptno, array_agg(ename) from emp group by deptno;
select array_agg(distinct deptno order by deptno desc) from emp;
-- 行转列
select regexp_split_to_table(array_to_string(array_agg(ename), ','), ',') from emp;
-- 转数组
select regexp_split_to_array(array_to_string(array_agg(ename), ','), ',') from emp;
-- 创建扩展
create extension tablefunc;
-- 交叉表查询(Crosstab Query)方式实现列转行
select * from crosstab(
'select ''变电站''::varchar, t.num, t.num from (
select num from
(select 1::varchar as num) n
union all
(select 2::varchar)
union all
(select 3::varchar)
) t'
) as t ("1" varchar, "2" varchar, "3" varchar, "4" varchar)

-- concat_ws(separator,str1,str2,...), 若分隔符为null,则结果也为null。
select concat_ws(',', e.empno, e.ename, e.deptno) from emp e;

/*
	json:->、->>、#>、#>>、@>、<@、?、?|、?&、||、-、#-
	json_typeof() 
	json_object_keys() 
	json_each()

	json与jsonb区别:
	json是对输入的完整拷贝,使用时再去解析,所以它会保留输入的空格,重复键以及顺序等。
	而jsonb是解析输入后保存的二进制,它在解析时会删除不必要的空格和重复的键,顺序和输入可能也不相同。使用时不用再次解析。
	两者对重复键的处理都是保留最后一个键值对。
	效率的差别:json类型存储快,使用慢,jsonb类型存储稍慢,使用较快。

	注意:键值对的键必须使用双引号

*/
-- 通过位置查找
select '[1,2,3]' :: json -> 2;
select '{"1":"a", "2":"b"}' :: json -> '2';
-- 返回text
select '[1,2,3]' :: json ->> 2;
select '{"1":"a", "2":"b"}' :: json ->> '2';

select '{"a":[1,2,3],"b":[4,5,6]}' :: json #> '{a,2}';
select '{"a":[1,2,3],"b":[4,5,6]}' :: json #>> '{a,2}';

-- 左侧json最上层的值是否包含右边json对象
select '{"a":{"b":2}}'::jsonb @> '{"b":2}'::jsonb; 					// f 	false
select '{"a":1, "b":2}'::jsonb @> '{"b":2}'::jsonb;					// t true
-- 左侧json对象是否包含于右侧json最上层的值内
select '{"b":2}'::jsonb <@ '{"a":1, "b":2}'::jsonb;					// t true
-- text是否作为左侧Json对象最上层的键
select '{"a":1, "b":2}' :: jsonb ? 'b';											// t true
-- text[]中的任一元素是否作为左侧Json对象最上层的键
select '{"a":1, "b":2, "c":3}'::jsonb ?| array['b', 'c']; 	// t true
-- text[]中的所有元素是否作为左侧Json对象最上层的键
select '["a", "b"]'::jsonb ?& array['a', 'b'];							// t true
-- 连接两个json对象,组成一个新的json对象
select '["a", "b"]'::jsonb || '["c", "d"]'::jsonb;
-- 删除左侧json对象中键为text的键值对
select '{"a": "b"}'::jsonb - 'a';
-- 删除数组指定索引处的元素,如果索引值为负数,则从右边计算索引值。如果最上层容器内不是数组,则抛出错误。
select '["a", "b"]'::jsonb - 1;
-- 删除指定路径下的域或元素(如果是json数组,且整数值是负的,则索引值从右边算起)
select '["a", {"b":1}]'::jsonb #- '{1,b}';

-- 返回json或jsonb类型的值。数组和复合被转换(递归)成数组和对象。
-- 另外除数字、布尔、NULL值(直接使用NULL抛出错误)外,其他标量必须有类型转换。
select to_json('3'::int), to_jsonb('3'::int);
-- 以JSON数组返回该数组。PostgreSQL多维数组变成JSON数组中的数组。
select array_to_json('{{1,5},{99,100}}'::int[],true);
-- 以JSON对象返回行。如果pretty_bool 为真,则在级别1元素之间添加换行。
select row_to_json(row(1, 'foo', 2, 'too'), true);
select row_to_json(emp) from emp;
select row_to_json(row(empno, ename, sal, job)) from emp;
select row_to_json(t)
from (select empno, ename, sal, job from emp) t;
-- 结果集转json->列转行->转json数组
select array_to_json(array_agg(row_to_json(t)))
from (select empno, ename, sal, job from emp) t;
-- 建立一个由可变参数列表组成的不同类型的JSON数组
select json_build_array(1,2,'3',4,5);
-- 建立一个由可变参数列表组成的JSON对象。参数列表参数交替转换为键和值。
select json_build_object('foo',1,'bar',2);
select json_build_object('coffee','咖啡','diet','饮食','glasses','眼镜');
-- 根据text[]数组建立一个json对象,如果是一维数组,则必须有偶数个元素,元素交替组成键和值。
-- 如果是二维数组,则每个元素必须有2个元素,可以组成键值对。
select json_object('{a, 1, b, "def", c, 3.5}');
select json_object('{{a, 1},{b, "def"},{c, 3.5}}');
-- 分别从两组text[]中获取键和值,与一维数组类似。
select json_object('{a, b}', '{1, 2}');

-- 返回Json数组最外层元素个数
select json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]');
-- 将最外层Json对象转换为键值对集合
select json_each('{"a":"foo", "b":"bar"}');
-- 将最外层Json对象转换为键值对集合,且value为text类型
select json_each_text('{"a":"foo", "b":"bar"}');
-- 返回path_elems指向的value,同操作符#>
select json_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4');
-- 返回path_elems指向的value,并转为text类型,同操作符#>>
select json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4', 'f6');
-- 返回json对象最外层的key
select json_object_keys('{"f1":"abc","f2":{"f3":"a", "f4":"b"}}');
-- 将json对象的value以base定义的行类型返回,如果行类型字段比json对象键值少,
-- 则多出的键值将被抛弃;如果行类型字段多,则多出的字段自动填充NULL。
select * from json_populate_record(null::emp, '{"ename":"scott","sal":2000}');
-- 将json对象最外层数组以base定义的行类型返回
select * from json_populate_recordset(null::emp, '[{"ename":"scott","sal":2000},{"ename":"king","sal":40000}]');
-- 将json数组转换成json对象value的集合
select json_array_elements('[1,true, [2,false]]');
-- 将json数组转换成text的value集合
select json_array_elements_text('["foo", "bar"]');
-- 返回json最外层value的数据类型,可能的类型有object, array, string, number, boolean, 和null.
select json_typeof('-123.4');
-- 根据json对象创建一个record类型记录,所有的函数都返回record类型,所以必须使用as明确定义record的结构。
select * from json_to_record('{"a":1,"b":[1,2,3],"c":"bar"}') as x(a int, b text, d text);
-- 根据json数组创建一个record类型记录,所有的函数都返回record类型,所以必须使用as明确定义record的结构。
select * from json_to_recordset('[{"a":1,"b":"foo"},{"a":"2","c":"bar"}]') as x(a int, b text);
-- 返回json对象中所有非null的数据,其他的null保留。
select json_strip_nulls('[{"f1":1,"f2":null},2,null,3]');
-- 如果create_missing为true,则将在target的path处追加新的jsonb;如果为false,则替换path处的value。
select jsonb_set('[{"f1":1,"f2":null},2,null,3]', '{0,f1}','[2,3,4]', false);
select jsonb_set('[{"f1":1,"f2":null},2]', '{0,f3}','[2,3,4]');
-- 如果insert_after是true,则在target的path后面插入新的value,否则在path之前插入。
-- TODO 效果未实现
select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"');
select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"', true);
-- 以缩进的格式更容易阅读的方式返回json对象
select jsonb_pretty('[{"f1":1,"f2":null},2,null,3]');
-- 聚合后得到json, 不带key的json聚合
select json_agg(empno) from emp;
-- 聚合后得到json, 带key的json聚合, 注意key不能为null, 否则报错.
select json_object_agg(empno, ename) from emp;
-- 聚合后得到xml
select xmlagg(ename::xml) from emp;

/*
 * 表继承
 */
drop table t_state;
create table t_state (
	state char(2)
) inherits (student);

-- 父表子表都添加两条数据。
insert into t_state values (11, '十一', '十');
insert into t_state values (11, '十一', '十', '是');

select * from student;
select * from t_state;
-- 只查询父表数据
select * from only student;

/*
	数组:每一种数据类型都有其对应的数组类型
*/
create table contacts(
	id serial primary key,
	name varchar(100),
	phones text[]
);

insert into contacts(name, phones) values 
('Jon', array['13100119988','17711223344']),
('Curry', array['18912345566','15500991122']);

select id, name, phones from contacts;
select id, name, phones[1] from contacts;
-- 搜索数组元素
select id, name, phones from contacts where '18912345566' = any(phones);
-- 展开数组
select id, name, unnest(phones) from contacts;
-- 在数组最前面增加元素
select array_prepend(3, array[1, 2]);
-- 在数组末尾增加元素
select array_append(array[1, 2], 3);
-- 连接两个数组
select array_cat(array[1, 2, 3], array[4, 5]);
-- 返回数组维度(int)
select array_ndims(array[[1, 2, 3], [4, 5, 6]]);
-- 返回数组维度(text)
select array_dims(array[[1, 2, 3, 4], [4, 5, 6, 7]]);
-- 使用提供的值和维度初始化一个数组,
-- 其中anyelement是值,第一个int[]是数组的长度,第二个int[]是数组下界,下界默认是1
select array_fill(7, array[3], array[2]);
-- 返回数组指定维度的长度
select array_length(array[[1, 2, 3, 4], [4, 5, 6, 7]], 1);
-- 二维数组按下标获取值
select (array[array['qq', 'ee'], array['aa', 'ss']])[1:1];
-- 使用提供的分隔符和可选的空字符串将字符串拆分为数组元素
select string_to_array('xx~^~yy~^~zz', '~^~', 'yy'), string_to_array('xx~^~yy~^~zz', '~^~');
-- 返回数组维度上限、下限
select array_upper(array[1,8,3,7], 1), array_lower('[0:2]={1,2,3}'::int[], 1);
-- 使用提供的分隔符和可选的空字符串连接数组元素
select array_to_string(array[1, 2, 3, NULL, 5], ',', '*')

/*
	内置函数
*/
/*
generate_series(start, stop, step) 	
参数类型(与返回类型相同):int 或 bigint 	
返回类型:setof int 或 setof bigint(与参数类型相同)	
函数功能:生成一个数值序列,从start 到 stop,步进为step,generate_series(start, stop)步进为1
generate_series(start, stop, step_interval)	
参数类型(与返回类型相同):timestamp or timestamp with time zone 
函数功能:生成一个数值序列,从start 到 stop,步进为step

注:IP也可序列生成。
*/
select generate_series(1,10);
select generate_series(1,10,3);
select generate_series(5,1);
select generate_series(5,1,-1);
select generate_series(now(),now() + '7 day','1 day');
select generate_series(to_date('20120827','yyyymmdd'),to_date('20120828','yyyymmdd'),'3 h');

-- nulls last/nulls first关键字
select e.* from emp e order by comm desc nulls last;
select e.* from emp e order by comm asc nulls first;

/*
	序列
	语法:
	create [ temporary | temp ] sequence name [ increment [ by ] increment ]
    [ minvalue minvalue | no minvalue ] [ maxvalue maxvalue | no maxvalue ]
    [ start [ with ] start ] [ cache cache ] [ [ no ] cycle ]
    [ owned by { table.column | none } ]
*/
create sequence seq_id increment by 1 minvalue 1 no maxvalue start with 1;
select * from seq_id;
-- 1.表字段类型为serial类型
-- 2.先创建序列,然后在新建的表中列属性指定序列,该列需int类型
create table tbl_sequence2 (
	id serial,
	pid int not null default nextval('seq_id'),
	name text
);
insert into tbl_sequence2(id, pid, name) values (nextval('seq_id'), nextval('seq_id'), '小小');
select * from tbl_sequence2;
-- 序列应用
select nextval('seq_id');
select currval('seq_id');
-- 当前会话里最近一次nextval返回的数值。
select lastval();
-- 重置序列
select setval('seq_id', 1);
-- 执行完该方法后,执行currval('seq_id')还是之前的值,再调用nextval('seq_id'),
-- 则序列值还是1,再调用一次才会递增。
select setval('seq_id', 1, false);
-- 修改序列
alter sequence seq_id restart with 1;
-- 删除序列(对于序列是由建表时指定serial 创建的,删除该表的同时,对应的序列也会被删除。)
drop sequence seq_id;

/*
 * 临时表用法:
 * with t1 as (), t2 as ()
 * select *;
 * array_agg() : 将记录集转为数组
 * unnest() : 将数组转为记录集
 * with中可以不仅可以使用select语句,同时还能使用delete,update,insert语句。
 * WITH中的数据修改语句会被执行一次,并且肯定会完全执行,无论主语句是否读取或者是否读取所有其输出。
 * 而WITH中的SELECT语句则只输出主语句中所需要记录数。WITH中使用多个子句时,这些子句和主语句会并行执行,
 * 所以当存在多个修改子语句修改相同的记录时,它们的结果不可预测。所有的子句所能“看”到的数据集是一样的,
 * 所以它们看不到其它语句对目标数据集的影响。这也缓解了多子句执行顺序的不可预测性造成的影响。 
 * 如果在一条SQL语句中,更新同一记录多次,只有其中一条会生效,并且很难预测哪一个会生效。 
 * 如果在一条SQL语句中,同时更新和删除某条记录,则只有更新会生效。 
 * 目前,任何一个被数据修改CTE的表,不允许使用条件规则,和ALSO规则以及INSTEAD规则。
 * cte query
 */
-- cte query
with studentdata as (
  insert into student (id, name, subject) values ('58', '阿米尔', '英')
  returning id
), contactsdata as (
  insert into contacts (id, name, phones)
  select id, '索拉博', '{15712345678}'
  from studentdata
  returning id
)
select s.id sid, c.id cid from studentdata s, contactsdata c;

-- 临时表
with t_dept as (
	select d.deptno, d.dname, d.loc from dept d
)
select d.dname, array_agg(ename), unnest(array_agg(ename))
from emp e left join t_dept d on e.deptno = d.deptno
group by e.deptno, d.dname;

-- insert update or delete in with.
with modify_rows as (
	-- update student set name = '我们宿舍' where id = '122' returning * -- 返回修改后的记录
	delete from student where id = '144' returning *
)
select * from modify_rows;

-- with recursive[递归],查询树结构的ename
with recursive result as (
	select e.empno, e.ename
	from emp e
	where e.empno = '7839'
	union all
	select origin.empno, rs.ename || '>' || origin.ename
	from result rs join emp origin on origin.mgr = rs.empno
)
select empno, ename from result;

-- 求唯一值:
-- 某张表,数据量在亿级别,求某稀疏列的唯一值。
create table sex (sex char(1), otherinfo text);
create index idx_sex_1 on sex(sex);
insert into sex select 'm', generate_series(1,50000);
insert into sex select 'w', generate_series(1,50000);
-- 求select distinct col from table;优化做法如下:
with recursive skip as (
  (select min(t.sex) as sex from sex t where t.sex is not null)
  union all
  (select (select min(t.sex) as sex from sex t where t.sex > s.sex and t.sex is not null)
  from skip s where s.sex is not null)
)
select * from skip where sex is not null;

/*
 * 一张小表A,里面存储了一些ID,大约几百个。另外有一张日志表B,每条记录中的ID是来自前面那张小表。
 * 但不是每个ID都出现,现求今天没有出现的ID。
 */
create table a(id int primary key, info text);
create table b(id int primary key, aid int, crt_time timestamp);
create index b_aid on b(aid);
-- a表插入1000条
insert into a select generate_series(1,1000), md5(random()::text);
-- b表插入500万条,只包含aid的500个id。
insert into b select generate_series(1,5000000), generate_series(1,500), clock_timestamp();
-- 普通做法如下:
select * from a where id not in (select aid from b);
select a.id from a left join b on (a.id=b.aid) where b.* is null;
-- 优化做法如下:
select * from a where id not in
(
with recursive skip as (
  (select min(aid) aid from b where aid is not null)
  union all
  (select (select min(aid) aid from b where b.aid > s.aid and b.aid is not null)
   from skip s where s.aid is not null)
)
select aid from skip where aid is not null
);

/*
 * 求时序数据最新值
 * 有很多传感器,不断的在上报数据,用户需要查询当前最新的,每个传感器上报的值。
 */
create unlogged table sort_test(  
  id serial8 primary key,  -- 主键  
  c2 int,  -- 传感器ID  
  c3 int  -- 传感器值  
);
-- 写入1000万传感器测试数据,10万个传感器。
insert into sort_test (c2,c3) select random()*100000, random()*100 from generate_series(1,10000000);
-- 普通做法
select id,c2,c3 from (select id,c2,c3,row_number() over(partition by c2 order by id desc) rn from sort_test) t where rn=1;
-- 优化做法
create index sort_test_1 on sort_test(c2,id desc);
with recursive skip as (
  (select (c2,c3)::r as r from sort_test where id in (select id from sort_test where c2 is not null order by c2,id desc limit 1))
  union all
  (select (
      select (c2,c3)::r as r from sort_test where id in (select id from sort_test t where t.c2>(s.r).c2 and t.c2 is not null order by c2,id desc limit 1)
    ) from skip s where (s.r).c2 is not null)
)
select (t.r).c2, (t.r).c3 from skip t where t.* is not null;

-- 如果数据需要处理或实时展示,流式返回
begin;
declare cur cursor for with recursive skip as (    
  (select (c2,c3)::r as r from sort_test where id in (select id from sort_test where c2 is not null order by c2,id desc limit 1)   
  )    
  union all    
  (select (select (c2,c3)::r as r from sort_test where id in (select id from sort_test t where t.c2>(s.r).c2 and t.c2 is not null order by c2,id desc limit 1)) from skip s where (s.r).c2 is not null  
  )      
)     
select (t.r).c2, (t.r).c3 from skip t where t.* is not null;   
fetch 100 from cur;

/*
 * with ordinality : 返回的结果集将增加一个整数列,从1开始,按1递增。
 */
select * from generate_series(3, 9) with ordinality as t(sequence, nums);

/*
 * lateral : 子查询中引用父查询的字段
 */
select * from emp e left join lateral(select d.deptno, d.dname, d.loc, e.ename from dept d) t on e.deptno = t.deptno;

/*
 * grouping set:
 * 每个子列表可以指定零个或多个列或表达式,并且与其直接在GROUP BY子句中的解释方式相同。
 * 一个空的分组集合意味着所有的行都被聚合到一个组中(即使没有输入行存在,也是输出)。
 * rollup((a),(b),(c))等价于grouping sets((a,b,c),(a,b),(a),())
 * cube((a),(b),(c))等价于grouping sets((a,b,c),(a,b),(a,c),(a),(b,c),(b),(c),())
 */
select e.deptno, e.job, sum(e.sal) from emp e
group by grouping sets ((e.deptno),(e.job),())
order by e.deptno, e.job;
select e.deptno, e.job, sum(e.sal) from emp e
group by grouping sets ((e.deptno, e.job))
order by e.deptno, e.job;
select e.deptno, e.job, sum(e.sal) from emp e
group by rollup ((e.deptno), (e.job))
order by e.deptno, e.job;
select e.deptno, e.job, sum(e.sal) from emp e
group by cube ((e.deptno), (e.job))
order by e.deptno, e.job;

/*
	事物
	四大特性:原子性、一致性、隔离性、持久性
	隔离级别:
	Read uncommitted(读未提交)[pg不支持]:
	一个事物在执行中可以看到其他事物修改或新增的没有提交的记录。
	Read committed(读已提交)[pg默认隔离级别]
	当一个事务运行使用这个隔离级别时, 一个查询(没有FOR UPDATE/SHARE子句)只能看到
	查询开始之前(1)已经被提交的数据,而无法看到未提交的数据或在查询执行期间其它事务提交的数据(2)。
  实际上,SELECT查询看到的是一个在查询开始运行的瞬间该数据库的一个快照。
  不过SELECT可以看见在它自身事务中之前执行的更新的效果(3),即使它们还没有被提交。
  还要注意的是,即使在同一个事务里两个相邻的SELECT命令可能看到不同的数据(4),   
	因为其它事务可能会在第一个SELECT开始和第二个SELECT开始之间提交。(此为文档原话)。
	注解:
	1. 表明当前select查找的数据是从 当前 查询开始的时候,注意并不是select所处的事务开始的时候。这边容易出现误解。
	2. 表明一旦执行了当前查询,那么在此之后提交的数据不能被读取到。
	3. 表明select可以读取到自身事务中之前的更新数据。
	4. 表明select可能会读取到不同的数据,当select2和select1期间其他事务做了提交,更新了数据。这就是所谓的不可重复读。
	Repeatable read(可重复读):
	可重复读隔离级别只看到在事务开始之前被提交的数据(1);
	它从来看不到未提交的数据或者并行事务在本事务执行期间提交的修改(2)
	(不过,查询能够看见在它的事务中之前执行的更新,即使它们还没有被提交),
	这是标准特别允许的,标准只描述了每种隔离级别必须提供的最小保护。
  这个级别与读已提交不同之处在于,一个可重复读事务中的查询看到 事务中第一个非事务控制语句开始时的一个快照, 
  而不是事务中当前语句开始时的快照。(3) 
  因此,在一个单一事务中的后续SELECT 命令看到的是相同的数据,即它们看不到其他事务在本事务启动后提交的修改。    

  基于1,2,3可以发现,可重复读机制 读取的数据是根据事务开始的时间,而不是 当前语句的时间,注意一下。
  那么也就是在这个事务内,其他事务的修改对它都是不可见的,无论执行多少次select,查询的数据都是此次事务开启时读取的。
	Serializable(序列化):
	可序列化隔离级别提供了最严格的事务隔离。
	这个级别为所有已提交事务模拟序列事务执行;就好像事务被按照序列一个接着另一个被执行,而不是并行地被执行。
	但是,和可重复读级别相似,使用这个级别的应用必须准备好因为序列化失败而重试事务。
	事实上,这个给力级别完全像可重复读一样地工作,除了它会监视一些条件,
	这些条件可能导致一个可序列化事务的并发集合的执行产生的行为与这些事务所有可能的序列化(一次一个)执行不一致。
	这种监控不会引入超出可重复读之外的阻塞,但是监控会产生一些负荷,并且对那些可能导致序列化异常的条件的检测将触发一次序列化失败。    

  序列化 隔离机制 内同一事务的 读和 可重复读是一致的,只能读取从事务开始时的快照,所以不可能出现 重复读的问题。

  但是幻读在 可重复读是 没有解决的,可重复读解决的问题主要可以看成是对 同一条数据保证了一致性,但是没有保证对突然出现的行数据的一致性。

  序列化解决的就是这个问题。
*/
select current_setting('transaction_isolation');

-- 如下测试需要将两个事物分别放置与不同的tab页中,不能在同一个页面测试。
-- 开启事物A查询student表中有n条数据,开启事物B,插入一条数据,
-- 再在事物A中执行查询有n条数据,提交事物B,再次查询事物A中有n+1条数据。
-- 事物A
start transaction;
select * from student;
-- 事物B
start transaction;
insert into student (id, name, subject) values (33, 'haier', '生');
commit;

/*
for update测试
当执行事物A的查询时,可以查询出当前数据,此时执行事物B,发现没有信息通知,此处事物B被阻塞了。
for update期间不允许其他事物对满足条件的数据进行修改,除非事物A被释放。
反之,先执行事物B,但是未提交,for update查询的事物也是不能执行,需等事物B提交后才能查询。
*/
-- 事物A
start transaction;
select * from student for update;
commit;
-- 事物B
start transaction;
update student set name = '可可' where id = 11;
commit;

-- 序列化测试
-- 事物A
start transaction isolation level serializable;
select * from student;
-- 事物B
start transaction isolation level serializable;
insert into student (id, name, subject) values (59, '小卡','外');
commit;

/*
 postgres数据库中占空间最大的表
*/
select relname, relpages from pg_class order by relpages desc;

select count(*) from pg_proc;

/*
 * 断开连接到这个数据库上的所有链接
 * pg_terminate_backend:用来终止与数据库的连接的进程id的函数。
 * pg_stat_activity:是一个系统表,用于存储服务进程的属性和状态。
 * pg_backend_pid():是一个系统函数,获取附加到当前会话的服务器进程的ID。
 */
select pg_terminate_backend(pg_stat_activity.pid)
from pg_stat_activity
where datname='testdb' and pid <> pg_backend_pid();

/*
 * brin、Hot Standby、发布/订阅通知、表继承、拆分表(10以上版本)、区间类型、数组类型、触发器
 */

/*
 * b-tree 索引:
 * 按顺序将KEY分成几个层次(根、分支、叶子)组织起来,在VALUE中存储数据的实际行号。
 * 此索引适合所有的数据类型,在排序、范围查询(大于、小于、大于等于、小于等于)、
 * 精确查询(等于)的场景中非常有效。索引与递归查询结合,还能实现快速的稀疏检索。
 * 不支持几何对象的搜索需求(相交,包含,距离等)。
 * hash 索引:
 * 存储的是被索引字段VALUE的哈希值,只支持等值查询。
 * hash索引特别适用于字段VALUE非常长的场景。
 * 例如很长的字符串,并且用户只需要等值搜索,建议使用hash index。
 *
 * gin 索引:
 * 倒排索引,存储被索引字段的VALUE或VALUE的元素,以及行号的list或tree。
 * 应用场景
 * 1、当需要搜索多值类型内的VALUE时,适合多值类型,例如数组、全文检索、TOKEN。
 * (根据不同的类型,支持相交、包含、大于、在左边、在右边等搜索)
 * 2、当用户的数据比较稀疏时,如果要搜索某个VALUE的值,
 * 可以适应btree_gin支持普通btree支持的类型。(支持btree的操作符)
 * 3、当用户需要按任意列进行搜索时,gin支持多列展开单独建立索引域,
 * 同时支持内部多域索引的bitmapAnd, bitmapOr合并,快速的返回按任意列搜索请求的数据。
 *
 * gist 索引:
 * GiST是一个通用的索引接口,全称Generalized Search Trees。不仅仅适合空间数据类型的检索,同样适合其他数据类型。
 * 在GIS数据的GiST索引实现中,将空间数据按“在另一边(上、下、左、右)”,“相交”,“包含”使用R-Tree结构组织。
 * 同时,GIS索引能够支持在一个索引结构中,存储平面、三维、多维的数据。这是很了不起的功能。
 * 
 * sp-gist 索引:
 * SP-GiST类似GiST,是一个通用的索引接口,但是SP-GIST使用了空间分区的方法,
 * 使得SP-GiST可以更好的支持非平衡数据结构,例如quad-trees, k-d tree, radis tree. 
 * 应用场景
 * 1、几何类型,支持位置搜索(包含、相交、在上下左右等),按距离排序。
 * 2、范围类型,支持位置搜索(包含、相交、在左右等)。
 * 3、IP类型,支持位置搜索(包含、相交、在左右等)。
 *
 * brin index since 9.5,存储了表的连续数据块区间以及对应的数据取值范围。即全表扫描的切片。
 * 使用场景:流式日志数据。
 * 主键和唯一键会自动创建Btree索引,无需另外单独再为主键和唯一键创建索引。
 * 默认brin是存储128个连续的数据块区间,值越小,精度越高
 * rum、bloom、zombodb、bitmap
 */
 
-- brin索引(block range index)
create table t1 (id int, info text);
create table t2 (id int, info text);
insert into t1 select generate_series(1, 2000000), md5(random()::text); -- 8.457s
insert into t2 select id, md5(random()::text) from generate_series(1, 2000000) as t(id) order by random(); -- 17.241s
-- 查询相关性
select correlation from pg_stats where tablename='t1' and attname='id'; -- 1
select correlation from pg_stats where tablename='t2' and attname='id'; -- 0.0043753
-- 创建索引(with (pages_per_range=1)调整brin索引的精度提高查询效率)
create index idx_t1 on t1 using brin(id) -- with (pages_per_range=1); -- 0.362s
create index idx_t2 on t2 using brin(id); -- 0.256s
-- 查看表、索引大小
select pg_size_pretty(pg_relation_size('t1')) table_size, -- 查看表的大小
			 pg_size_pretty(pg_total_relation_size('t1')) table_total_size, -- 表的总大小,包括索引的大小
			 pg_size_pretty(pg_relation_size('idx_t1')) index_size, -- 查看索引大小
			 pg_size_pretty(pg_tablespace_size('pg_default')) tablespace_size, -- 查看表空间的大小
			 pg_size_pretty(pg_database_size('postgres')) database_size; -- 查看数据库的大小
-- 查询
explain analyze select * from t1 where id >= 1000 and id <= 5000; -- 0.604s
explain analyze select * from t2 where id >= 1000 and id <= 5000; -- 2.328s,对于相关性差的,还不如全表扫描。
-- 使用pageinspect来观测brin索引的内容
create extension pageinspect;
select * from brin_page_items(get_raw_page('idx_t1', 2), 'idx_t1'); -- {261121 .. 276480}
select min(id), max(id) from t1 where ctid::text ~E'^\\(2176,'; -- min:261121;max:261240;
-- select brin_page_type(get_raw_page('idx_t1_id', id)) from generate_series(0,10) t(id);
select brin_page_type(get_raw_page('idx_t1', 2));
select * from brin_metapage_info(get_raw_page('idx_t1', 0));
select * from brin_revmap_data(get_raw_page('idx_t1', 1)) limit 5;

truncate table t1;
drop table t1;
drop table t2;


/*
 * system 抽样方式
 * 随机抽取表上数据块上的数据,理论上被抽样表的每个数据块被检索的概率是一样的,
 * 此方式基于数据块级别,后接抽样参数,被选中的块上的所有数据将被检索。
 * bernoulli 抽样方式
 * 随机抽取表的数据行,并返回指定百分比数据,基于数据行级别,理论上被抽样表的每行记录被检索的概率是一样的,
 *
 * 区别:
 * 因此BERNOULLI抽样方式抽取的数据相比SYSTEM抽样方式具有更好的随机性,但性能上相比SYSTEM抽样方式低很多。
 * 条数大约等于2000000 * 0.01%
 * bernoulli抽样方式返回的数据量非常接近抽样数据的百分比,而system抽样方式数据返回以数据块为单位,
 * 被抽样的块上的所有数据都被返回,因此system抽样方式的数据量返回的偏差较大。
 */
-- select count(id) from t1;
select * from t1 tablesample system(0.01);
select * from t1 tablesample bernoulli(0.01);

/**
 * distinct on() 去重
 * 把记录根据[, …]的值进行分组,分组之后仅返回每一组的第一行。
 * 如果你不指定ORDER BY子句,返回的第一条的不确定的。
 * 方法参数必须出现于排序中。
 */
select distinct on(e.job) e.job, e.empno, e.ename, e.sal 
from emp e 
order by e.job, e.sal 

select * from emp;


/**
 * uuid
 */
create extension if not exists "uuid-ossp";
select uuid_generate_v4();
select replace(uuid_generate_v4()::varchar, '-', '');


-- postgis dijkstra最优路径
/*

----1---
CREATE EXTENSION postgis_topology;
CREATE EXTENSION fuzzystrmatch;
CREATE EXTENSION postgis_tiger_geocoder;
CREATE EXTENSION address_standardizer;
--2 imp shp table----
create table t_app_line as(
select * from t_app_ytqline)

---3------

ALTER TABLE t_app_line ADD COLUMN "source" integer;
ALTER TABLE t_app_line ADD COLUMN "target" integer;
ALTER TABLE t_app_line ADD COLUMN length double precision;
--4-----
SELECT pgr_createTopology('t_app_line',0.000001, 'geom', 'gid');
---5---
select * from t_app_line
update t_app_line set length=st_length(geom)

---7---
SELECT seq, id1 AS node, id2 AS edge, cost FROM pgr_dijkstra('
                SELECT  gid AS id,
                         source::integer,
                         target::integer,
                         length::double precision AS cost
                        FROM t_app_line ',
                33, 37, false, FALSE);


 */

-- 查看当前慢SQL,长事务,长2PC事务(执行时间超过5秒的QUERY)
select pid, state, query_start, xact_start, now() - query_start, query 
from pg_stat_activity 
where now() - query_start > '5 s' 
order by query_start;

select * from pg_prepared_xacts;

-- kill query
select pg_cancel_backend('6908');
-- kill session
select pg_terminate_backend('6908');

-- 死锁的处理
-- 1. 检索出死锁进程的ID
select * from pg_stat_activity where datname = 'postgres'
/**
 * wating字段,数据为t的那条,就是死锁的进程。找到对应的procpid列的值。
 */
-- 2. 将进程杀掉
select pg_cancel_backend('死锁那条数据的procpid值');
/**
 * 若pg_stat_activity没有记录,则查询pg_locks是否有这个对象的锁
 */
select oid,relname from pg_class where relname='table name';
select locktype,pid,relation,mode,granted,* from pg_locks where relation= '上面查询出来的oid';
select pg_cancel_backend('进程ID');
-- pg_terminate_backend()函数也可以杀掉进程


/**
 * postgres_fdw实现跨库查询
 * 
 * 主键、外键和唯一键都保留了下来。但是新增时主键自增是从本地表的1开始,
 * 而不是接着远程表的id,造成新增时id冲突;
 * 远程t_resource和本地t_resource的新增都会同步对方;
 */
-- 查询所有扩展
select * from pg_available_extensions();
-- 创建fdw扩展
create extension postgres_fdw;
-- 创建远程server
create server server_xagis
foreign data wrapper postgres_fdw
options(host '192.168.100.40', port '5432', dbname 'xagis');
-- 查看所有远程连接,验证server创建成功。
select * from pg_foreign_server;
-- 创建用户匹配信息(在server_xagis下为角色postgres创建一个用户匹配信息,options里是用户名和密码。)
create user mapping for postgres server server_xagis options(user 'postgres',password '123456');
select * from pg_user_mappings;
drop user mapping if exists for postgres server server_xagis;
-- 在本地创建外部表
create foreign table t_app_jd
(id varchar,
name varchar,
type varchar,
pos_x numeric, 
pos_y numeric
)
server server_xagis
options (table_name 't_app_jd');
drop foreign table t_app_jd;
-- 在本地数据库中运行查询sql
select * from t_app_jd;


/**
 * postgres中使用oracle_fdw插件同步oracle数据表
 */
-- 创建oracle_fdw
create extension oracle_fdw;
-- 语句能查询到oracle_fdw extension
select * from pg_available_extensions;

-- 创建访问oracle的连接
create server ora_xagis_server foreign data wrapper oracle_fdw options(dbserver '192.168.100.44:1521/orcl');
-- 删除连接
drop server ora_xagis_server;
-- 查看所有远程连接,验证server创建成功。
select * from pg_foreign_server;
-- 授予postgres用户访问权限
grant usage on foreign server ora_xagis_server to postgres;
-- 创建到oracle的映射
create user mapping for postgres server ora_xagis_server options(user 'xagis', password 'xagis');
-- 查询已创建的到oracle的连接
select * from pg_user_mappings;
drop user mapping if exists for postgres server ora_xagis_server;
-- 创建需要访问的oracle中对应表的结构
create foreign table t_app_bdz
(id varchar,
	name varchar,
	address varchar,
	pos_x numeric,
	pos_y numeric
) server ora_xagis_server options(schema 'XAGIS',table 'GIS_BDZ'); -- 必须大写
-- 删除创建的对象
drop foreign table t_app_bdz;
-- 在pg中访问oracle的表
select * from t_app_bdz;


/**
 * dblink跨库
 */
select * from pg_extension;
create extension dblink;
select dblink_get_connections();
select dblink_disconnect();
select dblink_disconnect('pg_xagis'); 
select dblink_connect('pg_xagis', 'host=192.168.100.40 dbname=xagis_dev user=postgres password=123456');
select * from dblink('pg_xagis','select id, gljdname from t_app_ywgljd') as t1 (id varchar,gljdname varchar););
select dblink_connect('ora_xagis', 'host=192.168.100.44 dbname=XAGIS port=1521 user=xagis password=xagis');


/**
 * vacuumlo -n cas -v
 * vacuumlo cas -v -l 1000
 */
select lo_creat('1000001');
begin;
select lo_open('1000001', 131072);
select lo_unlink(1000001);
select * from pg_largeobject;
select * from pg_largeobject_metadata;
select lomowner,count(1) from pg_largeobject_metadata group by 1;
select lo_unlink(oid) from pg_largeobject_metadata where lomowner = 10;
select lo_unlink(oid) from pg_largeobject_metadata where lomowner = 10 limit 20000;
select pg_size_pretty(pg_database_size('cas'));


-- 查询语句执行顺序
from、on、join、where、group by、with cube/with rollup、having、select、distinct、order by


-- 查询表所在的物理文件名称
select oid,datname from pg_database where datname='postgres';
select oid,relfilenode from pg_class where relname='emp';



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章