2016-10-31 96 views
-1

在兩個球員,PostgreSQL based game最頻繁調用語句是SELECT查詢返回的遊戲列表,用戶正在玩其中:如何使用多個CASE語句優化SELECT查詢?

(請原諒在截圖非拉丁字母)

game screenshot

CREATE OR REPLACE FUNCTION words_get_games(in_uid integer) 
     RETURNS TABLE (
       out_gid integer, 
       out_created integer, 
       out_finished integer, 
       out_letters varchar[15][15], 
       out_values integer[15][15], 
       out_bid integer, 
       out_last_tiles jsonb, 
       out_last_score integer, 
       out_player1 integer, 
       out_player2 integer, 
       out_played1 integer, 
       out_played2 integer, 
       out_hand1 text, 
       out_hand2 text, 
       out_score1 integer, 
       out_score2 integer, 
       out_female1 integer, 
       out_female2 integer, 
       out_given1 varchar, 
       out_given2 varchar, 
       out_photo1 varchar, 
       out_photo2 varchar, 
       out_place1 varchar, 
       out_place2 varchar 
     ) AS 
$func$ 
    SELECT 
     g.gid, 
     EXTRACT(EPOCH FROM g.created)::int, 
     EXTRACT(EPOCH FROM g.finished)::int, 
     g.letters, 
     g.values, 
     g.bid, 
     m.tiles, 
     m.score, 
     /* HOW TO OPTIMIZE THE FOLLOWING CASE STATEMENTS? */ 
     CASE WHEN g.player1 = in_uid THEN g.player1 ELSE g.player2 END, 
     CASE WHEN g.player1 = in_uid THEN g.player2 ELSE g.player1 END, 
     CASE WHEN g.player1 = in_uid THEN g.score1 ELSE g.score2 END, 
     CASE WHEN g.player1 = in_uid THEN g.score2 ELSE g.score1 END, 
     CASE WHEN g.player1 = in_uid THEN s1.female ELSE s2.female END, 
     CASE WHEN g.player1 = in_uid THEN s2.female ELSE s1.female END, 
     CASE WHEN g.player1 = in_uid THEN s1.given ELSE s2.given END, 
     CASE WHEN g.player1 = in_uid THEN s2.given ELSE s1.given END, 
     CASE WHEN g.player1 = in_uid THEN s1.photo ELSE s2.photo END, 
     CASE WHEN g.player1 = in_uid THEN s2.photo ELSE s1.photo END, 
     CASE WHEN g.player1 = in_uid THEN s1.place ELSE s2.place END, 
     CASE WHEN g.player1 = in_uid THEN s2.place ELSE s1.place END, 
     EXTRACT(EPOCH FROM CASE WHEN g.player1 = in_uid THEN g.played1 ELSE g.played2 END)::int, 
     EXTRACT(EPOCH FROM CASE WHEN g.player1 = in_uid THEN g.played2 ELSE g.played1 END)::int, 
     ARRAY_TO_STRING(CASE WHEN g.player1 = in_uid THEN g.hand1 ELSE g.hand2 END, ''), 
     REGEXP_REPLACE(ARRAY_TO_STRING(CASE WHEN g.player1 = in_uid THEN g.hand2 ELSE g.hand1 END, ''), '.', '?', 'g'), 
    FROM words_games g 
     LEFT JOIN words_moves m ON m.gid = g.gid 
     -- find move record with the most recent timestamp 
     AND NOT EXISTS (SELECT 1 
      FROM words_moves m2 
      WHERE m2.gid = m.gid 
      AND m2.played > m.played) 
    LEFT JOIN words_social s1 ON s1.uid = g.player1 
     -- find social record with the most recent timestamp 
     AND NOT EXISTS (SELECT 1 
      FROM words_social s 
      WHERE s1.uid = s.uid 
      AND s.stamp > s1.stamp) 
    LEFT JOIN words_social s2 ON s2.uid = g.player2 
     -- find social record with the most recent timestamp 
     AND NOT EXISTS (SELECT 1 
      FROM words_social s 
      WHERE s2.uid = s.uid 
      AND s.stamp > s2.stamp) 
    WHERE in_uid IN (g.player1, g.player2) 
    AND (g.finished IS NULL OR g.finished > CURRENT_TIMESTAMP - INTERVAL '1 day'); 

$func$ LANGUAGE sql; 

正如你可以在自定義的SQL函數見上面,在努力總是返回用戶數據player1given1score1我使用大量的CASE語句(以便獲取ç需要時olumns可以被交換):

CASE WHEN g.player1 = in_uid THEN g.score1 ELSE g.score2 END, 

我的問題是:如果它能夠優化上述SELECT查詢(無需切換到較慢的PL/pgSQL的)?

UPDATE:

傑夫在the mailing list在接合時提供了一個很好的建議,已經在使用CASE:

SELECT 
     g.gid, 
     EXTRACT(EPOCH FROM g.created)::int, 
     EXTRACT(EPOCH FROM g.finished)::int, 
     g.letters, 
     g.values, 
     g.bid, 
     m.tiles, 
     m.score, 
     CASE WHEN g.player1 = in_uid THEN g.player1 ELSE g.player2 END, 
     CASE WHEN g.player1 = in_uid THEN g.player2 ELSE g.player1 END, 
     CASE WHEN g.player1 = in_uid THEN g.score1 ELSE g.score2 END, 
     CASE WHEN g.player1 = in_uid THEN g.score2 ELSE g.score1 END, 
     s1.female, 
     s2.female, 
     s1.given, 
     s2.given, 
     s1.photo, 
     s2.photo, 
     s1.place, 
     s2.place, 
     EXTRACT(EPOCH FROM CASE WHEN g.player1 = in_uid THEN g.played1 ELSE g.played2 END)::int, 
     EXTRACT(EPOCH FROM CASE WHEN g.player1 = in_uid THEN g.played2 ELSE g.played1 END)::int, 
     ARRAY_TO_STRING(CASE WHEN g.player1 = in_uid THEN g.hand1 ELSE g.hand2 END, ''), 
     REGEXP_REPLACE(ARRAY_TO_STRING(CASE WHEN g.player1 = in_uid THEN g.hand2 ELSE g.hand1 END, ''), '.', '?', 'g') 
FROM words_games g 
LEFT JOIN words_moves m ON m.gid = g.gid 
     -- find move record with the most recent timestamp 
     AND NOT EXISTS (SELECT 1 
       FROM words_moves m2 
       WHERE m2.gid = m.gid 
       AND m2.played > m.played) 
LEFT JOIN words_social s1 ON s1.uid = in_uid 
     -- find social record with the most recent timestamp 
     AND NOT EXISTS (SELECT 1 
       FROM words_social s 
       WHERE s1.uid = s.uid 
       AND s.stamp > s1.stamp) 
LEFT JOIN words_social s2 ON s2.uid = (CASE WHEN g.player1 = in_uid THEN g.player2 ELSE g.player1 END) 
     -- find social record with the most recent timestamp 
     AND NOT EXISTS (SELECT 1 
       FROM words_social s 
       WHERE s2.uid = s.uid 
       AND s.stamp > s2.stamp) 
WHERE in_uid IN (g.player1, g.player2) 
AND (g.finished IS NULL OR g.finished > CURRENT_TIMESTAMP - INTERVAL '1 day'); 
+0

你怎麼知道它是次優的?在查詢已經檢索到所需數據(一個用於遊戲,另外兩個用於玩家)之後,只有兩組值可能(或可能不需要)在外部查詢中交換。 – joop

+0

是的,我我不確定。實際上,我想優化兩件事:性能和可讀性。 –

+1

我不擔心大小寫表達式,因爲它們在選擇列表中。 – jarlh

回答

1

lateraldistinct on(IMO)有助於可讀性。 distinct on也會對性能產生影響,儘管我無法猜測是正面還是負面。

select 
    g.gid, 
    extract(epoch from g.created)::int created, 
    extract(epoch from g.finished)::int finished, 
    g.letters, 
    g.values, 
    g.bid, 
    m.tiles, 
    m.score, 
    r.* 
from 
    words_games g 
    left join (
     select distinct on (gid, played) * 
     from words_moves 
     order by gid, played desc 
    ) words_moves m on m.gid = g.gid 
    left join (
     select distinct on (uid, stamp) * 
     from words_social 
     order by uid, stamp desc 
    ) words_social s1 on s1.uid = g.player1 
    left join (
     select distinct on (uid, stamp) * 
     from words_social 
     order by uid, stamp desc 
    ) words_social s2 on s2.uid = g.player2 
    cross join lateral (
     select 
      g.player1, g.player2, 
      extract(epoch from g.player1)::int, extract(epoch from g.player2)::int, 
      array_to_string(g.hand1, ''), 
      regexp_replace(array_to_string(g.hand2, ''), '.', '?', 'g'), 
      g.score1, g.score2, 
      s1.female, s2.female, 
      s1.given, s2.given, 
      s1.photo, s2.photo, 
      s1.place, s2.place 
     where g.player1 = in_uid 
     union all 
     select 
      g.player2, g.player1, 
      extract(epoch from g.player2)::int, extract(epoch from g.player1)::int, 
      array_to_string(g.hand2, ''), 
      regexp_replace(array_to_string(g.hand1, ''), '.', '?', 'g'), 
      g.score2, g.score1, 
      s2.female, s1.female, 
      s2.given, s1.given, 
      s2.photo, s1.photo, 
      s2.place, s1.place 
     where g.player1 != in_uid 
    ) r 
where 
    in_uid in (g.player1, g.player2) 
    and (g.finished is null or g.finished > current_timestamp - interval '1 day') 
1

既然你擔心的許多case語句,它始終是相同的條件,你可以拉出這個條件,並有兩個選擇,例如

select ... 
     g.player1, g.player2, 
     extract(epoch from g.played1)::int, extract(epoch from g.played2)::int, 
     ... 
     g.score1, g.score2, 
     ... 

和其他(相同的)選擇與列交換

select ... 
     g.player2, g.player1, 
     extract(epoch from g.played2)::int, extract(epoch from g.played1)::int, 
     ... 
     g.score2, g.score1, 
     ... 

雖然,作爲@joop和@jarlh已經質疑,第一次測試,如果這是一個真正的性能問題都沒有。

+1

注意:在之前的文章中,OP有一個類似的(相當龐大的)解決方案,使用UNION http://stackoverflow.com/q/40304011/2235885 – joop

+0

是的,我在UNION SELECT之前交換了字段:-)但我試圖優化這個語句,因爲它是最頻繁調用的語句。 –

+1

然後,可能在查看'explain(analyze)'查詢'select union'和'select case'就可以得到更多的見解。 –