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';



 

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