前幾天 Postgres 群裡有人問如何在 Postgres 裡實現動態列的行轉列,於是我整理了PostgreSQL 實現動態行轉列的方法匯總
然後被問到如何直接通過一個函數返回結果,而不是返回一個待執行的 SQL 語句,於是我參考下面的教程,實現了一個函數返回 json,先把代碼附上,
-- PL/pgSQL code to create pivot tables with automatic column names
-- prerequisite: install the tablefunc module
create extension if not exists tablefunc;
-- tablename: name of source table you want to pivot
-- rowc: the name of the column in source table you want to be the rows
-- colc: the name of the column in source table you want to be the columns
-- cellc: an aggregate expression determining how the cell values will be created
-- celldatatype: desired data type for the cells
create or replace function pivot (tablename varchar, rowc varchar, colc varchar, cellc varchar, celldatatype varchar)
returns json language plpgsql as $$
declare
dynsql1 varchar;
dynsql2 varchar;
columnlist varchar;
stmt TEXT;
result json;
begin
-- 1. retrieve list of column names.
dynsql1 = 'select string_agg(distinct ''_''||'||colc||'||'' '||celldatatype||''','','' order by ''_''||'||colc||'||'' '||celldatatype||''') from '||tablename||';';
execute dynsql1 into columnlist;
-- 2. set up the crosstab query
dynsql2 = 'select * from crosstab (
''select '||rowc||','||colc||','||cellc||' from '||tablename||' group by 1,2 order by 1,2'',
''select distinct '||colc||' from '||tablename||' order by 1''
)
as newtable (
'||rowc||' varchar,'||columnlist||'
)';
stmt = E' select array_to_json(array_agg(row_to_json(t))) from ('|| dynsql2 ||') t ';
execute stmt into result;
return result;
end
$$這裡就是把 pivotcode 生成的 SQL 執行,並把結果存到 json 裡返回
stmt = E' select array_to_json(array_agg(row_to_json(t))) from ('|| dynsql2 ||') t ';
execute stmt into result;
正文在本 PostgreSQL 教程中,我們將仔細研究動態 SQL 的概念,以及如何通過允許高效地查詢數據來簡化資料庫程式設計師的工作。
在查詢中,動態 SQL 用於減少重複性任務。例如,可以使用動態 SQL 每天為特定表創建表分區,在所有外鍵上添加缺失的索引,或在不產生重大編碼影響的情況下為特定表添加數據審核功能。動態 SQL 的另一個重要用途是克服 PL/pgSQL 緩存的副作用,因為不緩存使用 EXECUTE 語句執行的查詢。
動態 SQL 是通過 EXECUTE 語句實現的。該 EXECUTE 語句接受一個字符串,並簡單地計算它。執行語句的摘要如下:
EXECUTE command-string [ INTO [STRICT] target ] [ USING expression [, ...]];
在動態 SQL 中執行 DDL 語句在某些情況下,需要在資料庫對象級別執行操作,例如表,索引,列,角色等。例如,資料庫開發人員希望清理並分析特定的架構對象,這是部署後的常見任務,目的是更新統計信息。例如,要分析 schema public 下的表 ,可以編寫以下腳本:
DO $$
DECLARE
table_name text;
BEGIN
FOR table_name IN SELECT tablename FROM pg_tables WHERE schemaname ='public' LOOP
RAISE NOTICE 'Analyzing %', table_name;
EXECUTE 'ANALYZE ' || table_name;
END LOOP;
END;
$$;
在動態 SQL 中執行 DML 語句某些應用程式可能以交互方式與數據交互。例如,一個人可能每月生成一次計費數據。此外,某些應用程式會根據用戶定義的不同標準過濾數據。在這種情況下,動態 SQL 非常方便。例如,在汽車門戶應用程式中,需要使用搜索功能才能使用動態謂詞獲取帳戶,如下所示:
CREATE OR REPLACE FUNCTION get_account (predicate TEXT)
RETURNS SETOF account AS$$
BEGIN
RETURN QUERY EXECUTE 'SELECT * FROM account WHERE ' || predicate;
END;
$$ LANGUAGE plpgsql;要測試以前的功能:
car_portal=> SELECT * FROM get_account ('true') limit 1;
account_id | first_name | last_name | email | password
--+--+-+--+----
1 | James | Butt | jbutt@gmail.com |1b9ef408e82e38346e6ebebf2dcc5ece
(1 row)
car_portal=> SELECT * FROM get_account(E'first_name='James'');
account_id | first_name | last_name | email | password
--+--+-+--+----
1 | James | Butt | jbutt@gmail.com |1b9ef408e82e38346e6ebebf2dcc5ece
(1 row)
動態 SQL 及其緩存效果如前所述,PL / pgSQL 緩存執行計劃。如果預期生成的計劃是靜態的,則這非常好。例如,由於選擇性,下面的語句應使用索引掃描。在這種情況下,緩存計劃可以節省一些時間,從而 提高性能:
SELECT * FROM account WHERE account_id =<INT>但是,在其他情況下,情況並非如此。例如,假設我們在 ads_date 列上有一個索引, 並且希望獲取自某個日期以來的廣告數量 ,如下所示:
SELECT count (*) FROM advertisement WHERE advertisement_date >= <certain_date>;在前面的查詢中,可以通過使用索引掃描或使用基於選擇性的順序掃描(取決於所提供的 sure_date 值)從硬碟中 獲取廣告表中 的條目。緩存這樣的查詢的執行計劃 會導致嚴重的問題。因此,編寫以下函數不是一個好主意:
CREATE OR REPLACE FUNCTION get_advertisement_count
(some_date timestamptz ) RETURNS BIGINT AS $$
BEGIN
RETURN (SELECT count (*) FROM advertisement WHERE advertisement_date >=some_date)::bigint;
END;
$$ LANGUAGE plpgsql;要解決緩存問題,可以使用 SQL 語言函數或使用 PL / pgSQL execute 命令重寫以前的函數,如下所示:
CREATE OR REPLACE FUNCTION get_advertisement_count
(some_date timestamptz ) RETURNS BIGINT AS $$
DECLARE
count BIGINT;
BEGIN
EXECUTE 'SELECT count (*) FROM advertisement WHERE
advertisement_date >= $1' USING some_date INTO count;
RETURN count;
END;
$$ LANGUAGE plpgsql;
動態 SQL 使用的推薦做法如果處理不當,動態 SQL 可能會導致安全性問題。動態 SQL 容易受到 SQL 注入技術的攻擊。SQL 注入用於執行揭示安全信息的 SQL 語句,甚至破壞資料庫中的數據。易受 SQL 注入攻擊的 PL / pgSQL 函數的一個非常簡單的示例如下:
CREATE OR REPLACE FUNCTION can_login (email text, pass text) RETURNS BOOLEAN AS $$
DECLARE
stmt TEXT;
result bool;
BEGIN
stmt = E'SELECT COALESCE (count(*)=1, false) FROM account WHERE email = ''|| $1 || E'' and password = ''||$2||E''';
RAISE NOTICE '%' , stmt;
EXECUTE stmt INTO result;
RETURN result;
END;
$$ LANGUAGE plpgsql;如果電子郵件和密碼匹配,則前面的函數返回 true。為了測試此功能,讓我們插入一行並嘗試注入一些代碼,如下所示:
car_portal=> SELECT can_login('jbutt@gmail.com',md5('jbutt@gmail.com'));
NOTICE: SELECT COALESCE (count(*)=1, false) FROM account WHERE email ='jbutt@gmail.com' and password = '1b9ef408e82e38346e6ebebf2dcc5ece'
Can_login
-
t
(1 row)
car_portal=> SELECT can_login('jbutt@gmail.com',md5('jbutt@yahoo.com'));
NOTICE: SELECT COALESCE (count(*)=1, false) FROM account WHERE email ='jbutt@gmail.com' and password = '37eb43e4d439589d274b6f921b1e4a0d'
can_login
-
f
(1 row)
car_portal=> SELECT can_login(E'jbutt@gmail.com'--', 'Do not know password');
NOTICE: SELECT COALESCE (count(*)=1, false) FROM account WHERE email ='jbutt@gmail.com'--' and password = 'Do not know password'
can_login
-
t
(1 row)請注意,即使密碼與表中存儲的密碼不匹配,該函數也會返回 true。這僅僅是因為謂詞已被注釋,如引發通知所示:
SELECT COALESCE (count(*)=1, false) FROM account WHERE email ='jbutt@gmail.com'--' and password = 'Do not know password'為了保護代碼不受這種技術的影響,可以遵循以下做法:
對於參數化的動態 SQL 語句,請使用 USING 子句。使用格式函數和適當的插值來構造查詢。請注意,%I 將參數轉義為標識符,而將%L 轉義為文字。使用quote_ident(),quote_literal()和quote_nullable()正確格式化標識符和文字的格式。編寫上述功能的一種方法如下:
CREATE OR REPLACE FUNCTION can_login (email text, pass text) RETURNS BOOLEAN AS $$
DECLARE
stmt TEXT;
result bool;
BEGIN
stmt = format('SELECT COALESCE (count(*)=1, false) FROM account WHERE email = %Land password = %L', $1,$2);
RAISE NOTICE '%' , stmt;
EXECUTE stmt INTO result;
RETURN result;
END;
$$ LANGUAGE plpgsql;我們看到了如何動態地動態使用 SQL 來構建和執行查詢。與靜態 SQL 語句不同,動態 SQL 語句的全文是未知的,並且可以在連續執行之間進行更改。這些查詢可以是 DDL,DCL 和(或)DML 語句。
原文:How to implement Dynamic SQL in PostgreSQL 10[1]
作者: Amey Varangaonkar
參考資料[1]How to implement Dynamic SQL in PostgreSQL 10: https://hub.packtpub.com/how-to-implement-dynamic-sql-in-postgresql-10/