2016-10-31 7 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; 

당신은 항상 player1로 사용자 데이터를 반환하는 노력의 일환으로, 위의 사용자 정의 SQL 기능에서 볼 수 있듯이, given1, score1 내가 있도록 (다수의 CASE 문을 사용 가져온 다 필요할 때 olumns)를 교환 할 수 있습니다

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

내 질문은 : 느린 PL/pgSQL의)로 전환하지 않고 (위의 SELECT 쿼리를 최적화 할 수 있다면?

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

조건, 당신은이 조건을 풀 수 있고 선택, 예 이 모든 정말로 성능 문제의 경우 @joop 및 @jarlh 이미, 첫 번째 테스트에 의문을 제기으로 열

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, 
     ... 

을 바 꾸었습니다.

+1

참고 : 이전 게시물에서 OP는 비슷한 (다소 부피가 큰) 솔루션으로 UNION http://stackoverflow.com/q/40304011/2235885 – joop

+0

예, 나는 :-) 전에 스왑 된 필드가있는 UNION SELECT를 가졌지 만 가장 자주 호출되는이 문이므로이 문을 최적화하려고합니다. –

+1

그러면 'select union'과 'select case'쿼리 모두에 대해'explain (analyze) '을보고 더 많은 통찰력을 얻을 수 있습니다. –