1. 程式人生 > >PostgreSQL的許多小技巧

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)