2012-03-17 34 views
2

我在尋找將數據庫訪問權抽象爲postgres的方法。在我的示例中,我將在nodejs中使用假想的twitter克隆,但最終還是有關postgres如何處理預準備語句的問題,因此語言和庫並不重要:Postgres準備了具有不同字段的語句

假設我希望能夠訪問通過用戶名來自用戶所有的tweet的列表:

name: "tweets by username" 
text: "SELECT (SELECT * FROM tweets WHERE tweets.user_id = users.user_id) FROM users WHERE users.username = $1" 
values: [username] 

這工作正常,但似乎效率不高,無論是在實用性方面和代碼質量方面必須做出另一個函數來處理通過電子郵件獲得的鳴叫,而不是用戶名:

name: "tweets by email" 
text: "SELECT (SELECT * FROM tweets WHERE tweets.user_id = users.user_id) FROM users WHERE users.email = $1" 
values: [email] 

是否有可能在準備好的語句中包含一個字段作爲參數?

name: "tweets by user" 
text: "SELECT (SELECT * FROM tweets WHERE tweets.user_id = users.user_id) FROM users WHERE users.$1 = $2" 
values: [field, value] 

雖然這是真的,這可能是由USER_ID訪問微博的角落情況下少了幾分效率,這是一個行業,我願意作出改善代碼質量,並通過減少有望整體提高效率查詢模板的數量爲1而不是3+。

回答

0
SELECT t.* 
FROM tweets t 
inner join users u on t.user_id = u.user_id 
WHERE case $2 
    when 'username' then u.username = $1 
    when 'email' then u.email = $1 
    else u.user_id = $1 
    end 
+0

這回答了OP的問題很好,但我認爲這是有可能產生一個可怕的執行計劃作爲其中的情況下會抑制計劃者選擇的用戶一個很好的指標的能力。 – dbenhur 2012-03-18 00:29:54

+0

@dbenhur是的,我想過,但選項是使用動態查詢,我不知道他是否想聽到。他似乎更關心代碼簡潔性。如果他沒有使用9.1格式化函數將做一個乾淨的動態查詢,它將是必要的連接字符串產生雜亂的代碼,我的印象正是他想避免的。 – 2012-03-18 00:59:00

+0

我很好奇,並建立了一個實驗。事實上,這個案例確實嚴重傷害了這個計劃。查看我的備選答案進行分析,並查詢表單,該表單也可以快速生成,而無需執行動態語句生成。 – dbenhur 2012-03-18 01:01:33

3

@Clodoaldo的回答是正確的,因爲它允許你想要的能力,並應該返回正確的結果。不幸的是,它的執行速度很慢。

我建立了包含推文和用戶的實驗數據庫。每個用戶擁有100個推文(1M條推文記錄)。我將PK索引爲u.id,t.id,FK t.user_id和謂詞字段u.username,u.email。

create table t(id serial PRIMARY KEY, data integer, user_id bignit); 
create index t1 t(user_id); 
create table u(id serial PRIMARY KEY, name text, email text); 
create index u1 on u(name); 
create index u2 on u(email); 
insert into u(name,email) select i::text, i::text from generate_series(1,10000) i; 
insert into t(data,user_id) select i, (i/100)::bigint from generate_series(1,1000000) i; 
analyze table t; 
analyze table u; 

利用一個場作爲謂詞的簡單查詢是非常快:

prepare qn as select t.* from t join u on t.user_id = u.id where u.name = $1; 

explain analyze execute qn('1111'); 
Nested Loop (cost=0.00..19.81 rows=1 width=16) (actual time=0.030..0.057 rows=100 loops=1) 
    -> Index Scan using u1 on u (cost=0.00..8.46 rows=1 width=4) (actual time=0.020..0.020 rows=1 loops=1) 
     Index Cond: (name = $1) 
    -> Index Scan using t1 on t (cost=0.00..10.10 rows=100 width=16) (actual time=0.007..0.023 rows=100 loops=1) 
     Index Cond: (t.user_id = u.id) 
Total runtime: 0.093 ms 

中提出的,其中作爲@Clodoaldo使用情況下的查詢需要近30秒時:

prepare qen as select t.* from t join u on t.user_id = u.id 
    where case $2 when 'e' then u.email = $1 when 'n' then u.name = $1 end; 

explain analyze execute qen('1111','n'); 
Merge Join (cost=25.61..38402.69 rows=500000 width=16) (actual time=27.771..26345.439 rows=100 loops=1) 
    Merge Cond: (t.user_id = u.id) 
    -> Index Scan using t1 on t (cost=0.00..30457.35 rows=1000000 width=16) (actual time=0.023..17.741 rows=111200 loops=1) 
    -> Index Scan using u_pkey on u (cost=0.00..42257.36 rows=500000 width=4) (actual time=0.325..26317.384 rows=1 loops=1) 
     Filter: CASE $2 WHEN 'e'::text THEN (u.email = $1) WHEN 'n'::text THEN (u.name = $1) ELSE NULL::boolean END 
Total runtime: 26345.535 ms 

觀察那個計劃,我認爲使用union subselect然後過濾其結果以獲得適合於參數化謂詞選擇的id將允許計劃人員爲每個參數使用特定的索引謂語。事實證明我是對的:

prepare qen2 as 
select t.* 
from t 
join (
SELECT id from 
    (
    SELECT 'n' as fld, id from u where u.name = $1 
    UNION ALL 
    SELECT 'e' as fld, id from u where u.email = $1 
) poly 
where poly.fld = $2 
) uu 
on t.user_id = uu.id; 

explain analyze execute qen2('1111','n'); 
Nested Loop (cost=0.00..28.31 rows=100 width=16) (actual time=0.058..0.120 rows=100 loops=1) 
    -> Subquery Scan poly (cost=0.00..16.96 rows=1 width=4) (actual time=0.041..0.073 rows=1 loops=1) 
     Filter: (poly.fld = $2) 
     -> Append (cost=0.00..16.94 rows=2 width=4) (actual time=0.038..0.070 rows=2 loops=1) 
       -> Subquery Scan "*SELECT* 1" (cost=0.00..8.47 rows=1 width=4) (actual time=0.038..0.038 rows=1 loops=1) 
        -> Index Scan using u1 on u (cost=0.00..8.46 rows=1 width=4) (actual time=0.038..0.038 rows=1 loops=1) 
          Index Cond: (name = $1) 
       -> Subquery Scan "*SELECT* 2" (cost=0.00..8.47 rows=1 width=4) (actual time=0.031..0.032 rows=1 loops=1) 
        -> Index Scan using u2 on u (cost=0.00..8.46 rows=1 width=4) (actual time=0.030..0.031 rows=1 loops=1) 
          Index Cond: (email = $1) 
    -> Index Scan using t1 on t (cost=0.00..10.10 rows=100 width=16) (actual time=0.015..0.028 rows=100 loops=1) 
     Index Cond: (t.user_id = poly.id) 
Total runtime: 0.170 ms 
+0

哇,非常徹底,謝謝。這是一個天才的解決方案,雖然不像@ Clodoaldo那麼優雅。我永遠不會想到這樣做。然而,這是否像從一組預定義字段中生成多個查詢一樣高效(通過預定義它們,我不必擔心驗證字段名稱)? – adrusi 2012-03-18 16:16:40

+0

@adrusi它沒有時間效率,查看第一個簡單查詢的運行時間與多態字段的運行時間匹配:0.093 vs 0.170ms。它們都非常快,但是您可以以〜11K查詢/秒的速度連續執行簡單查詢,而後者只能達到約6K的每秒。 (仍然有少數應用程序,6K qps是一個真正的限制。) – dbenhur 2012-03-18 17:39:47