PostgreSQL的許多小技巧
1.使用空間索引進行快速間隔資料型別的搜尋.
間隔搜尋有時候很慢,大部分原因是索引優化器不使用索引,並且在開始列和結束列比較獨立。一個解決方案是使用空間索引,它可以把兩個獨立的值當做一個值來使用。
postgres=# EXPLAIN ANALYZE SELECT * FROM testip WHERE 19999999 BETWEEN startip AND endip;
QUERY PLAN ---------------------------------------------------------------- Seq Scan on testip (cost=0.00..19902.00 rows=200814 width=12) (actual time=3.457..434.218 rows=1 loops=1) Filter: ((19999999 >= startip) AND (19999999 <= endip)) Total runtime: 434.299 ms (3 rows) Time: 435,865 ms
結論:根據以上的執行計劃,可以知道上邊的查詢使用的是序列掃描,花費的時間是:435,865 ms
postgres=# CREATE INDEX ggg ON testip USING gist ((box(point(startip,startip),point(endip,endip))) box_ops);
使用如下的查詢:
EXPLAIN ANALYZE SELECT * FROM testip WHERE box(point(startip,startip),point(endip,endip)) @> box(point (19999999,19999999), point(19999999,19999999));
結論:執行計劃使用的是Bitmap Index Scan on ggg,花費的時間是:2,805 ms。可見相比以前的查詢,使用空間索引的查詢效率大大的提高了。
2.16進位制到10進位制的轉換
我們已經有了系統函式將10進位制轉換成16進位制:to_hex(11) result: b 下邊的函式實現將16進位制的數轉換成10進位制。非常的簡單:
--測試create or replace function to_dec(text) returns integer as $$ declare r int; begin execute E'select x''||$1|| E''::integer' into r; return r; end $$ language plpgsql;
select to_dec('ff');
--結果
255
3.ALTER TABLE ALTER COLUMN USING 語法
在PostgreSQl裡邊,我們不能將varchar型別直接轉換到bool,但是我們可以使用Using語法加判斷後進行轉換。
CREATE TABLE foo(a varchar);
INSERT INTO foo VALUES ('ano');
--更改資料型別,會報錯誤資訊
ALTER TABLE foo ALTER COLUMN a TYPE boolean;
ERROR: column "a" cannot be cast to type "pg_catalog.bool"
--使用Using語法更改資料型別
ALTER TABLE foo
ALTER COLUMN a TYPE boolean
USING CASE a
WHEN 'ano' THEN true
ELSE false END;
--更改成功
SELECT * FROM foo;
4.Quote_ident 的使用
使用雙引號是一種防止SQL注入的方法,quote_ident 可以檢查引數,如果引數中包含任何非法的字元,它會在引數兩邊加上""
非常簡單和有效,但是問題是schema.name,因為中間有點分割。問題如下:
select quote_ident('public.foo');
他不能在schema和name兩邊加上雙引號。
我們可以通過使用函式來按點分割上邊的物件名稱,在每個單獨的物件上使用quote_ident來完成我們的目的:
--對陣列進行錶轉換,針對每一列來使用quote_ident
CREATE OR REPLACE FUNCTION quote_array(text[])
RETURNS text AS $$
SELECT array_to_string(array(SELECT quote_ident($1[i])
FROM generate_series(1, array_upper($1,1)) g(i)),
'.')
$$ LANGUAGE SQL IMMUTABLE;
--建立函式按點進行拆分字串
CREATE OR REPLACE FUNCTION quote_schema_ident(text)
RETURNS text AS $$
SELECT quote_array(string_to_array($1,'.'))
$$ LANGUAGE SQL IMMUTABLE;
--測試
select quote_schema_ident('public.foo tab');
5.我們已經習慣使用PostgreSQL的exception來捕捉錯誤,但是錯誤資訊一直不知道如何取得,SQLERRM變數可以給我們詳細的資訊
以下是一個具體的示例:
CREATE OR REPLACE FUNCTION fn_log_error(p_function varchar, p_location int, p_error varchar)
RETURNS void AS $$
DECLARE
v_sql varchar;
v_return varchar;
v_error varchar;
BEGIN
--連線資料庫
PERFORM dblink_connect('connection_name', 'dbname=...');
--拼湊插入的字串
v_sql:= 'INSERT INTO error_log (function_name, location, error_message, error_time) '
|| 'VALUES (''' || p_function_name || ''', '
|| p_location || ', ''' || p_error || ''', clock_timestamp())';
--遠端執行
SELECT INTO v_return *
FROM dblink_exec('connection_name', v_sql, false);
--獲取遠端的錯誤資訊
SELECT INTO v_error *
FROM dblink_error_message('connection_name');
--如果出現錯誤則丟擲異常
IF position('ERROR' in v_error) > 0 OR position('WARNING' in v_error) > 0 THEN
RAISE EXCEPTION '%', v_error;
END IF;
PERFORM dblink_disconnect('connection_name');
EXCEPTION
WHEN others THEN
--使用SQLERRM 來顯示錯誤資訊
PERFORM dblink_disconnect('connection_name');
RAISE EXCEPTION '(%)', SQLERRM;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
SQLERRM是一個非常有用的變數,可以詳細記錄錯誤的具體資訊,幫助我們分析執行中發現的錯誤。
6.迴圈優化技巧
plpgsql對於非SQL操作效率不是特別高。Plpgsql 不喜歡字元或者字元陣列的累計操作,當我們也不能用Perl,因此我們只能用SQL
--使用迴圈,結果會比較慢的函式
CREATE OR REPLACE FUNCTION SlowList(int) -- slow function, usable for N <= 100
RETURNS varchar AS $$
DECLARE s varchar = '';
BEGIN
FOR i IN 1..$1 LOOP
s:= '<item>' || i || '</item>'; -- slow is s:= s || ..
END LOOP;
RETURN s;
END; $$ LANGUAGE plpgsql IMMUTABLE;
--使用SQL,結果會比較快的函式
CREATE OR REPLACE FUNCTON FastList(int) -- fast function
RETURNS varchar AS $$
BEGIN
RETURN array_to_string(ARRAY(SELECT '<item>' || i || '</item>'
FROM generate_series(1, $1) g(i)),
'');
END; $$ LANGUAGE plpgsql IMMUTABLE;
--結果:在迴圈100以下的時候差別並不是很大,當迴圈更多的時候,差距就非常明顯,都來試試吧!
7.查詢一組之中的頭n條記錄
我們一般的做法是使用子查詢如下:
SELECT * FROM people WHERE id IN (
SELECT id FROM people s
WHERE people.category = s.category
ORDER BY age LIMIT 2)
ORDER BY category, age;
使用連線我們也可以達到同樣的效果如下:
SELECT s1.*
FROM people s1
LEFT JOIN
people s2
ON s1.category = s2.category AND s1.age < s2.age
GROUP BY s1.id, s1.category
HAVING COUNT(s2.id) <= 1
ORDER BY s1.category, COUNT(s2.id);
說明:這個SQL語句的含義是找到同一類比自己的age大的記錄,最後判斷比自己大的記錄的個數,如果是0,那麼應該排名第一,
如果是1,那麼排名第二(HAVING COUNT(s2.id) <= 1)