Sql 如何使用多个CASE语句优化SELECT查询?

Sql 如何使用多个CASE语句优化SELECT查询?,sql,postgresql,select,case,postgresql-9.5,Sql,Postgresql,Select,Case,Postgresql 9.5,在双人游戏中,最常调用的语句是返回用户正在玩的游戏列表的SELECT查询: (请原谅截图中的非拉丁字母) 正如您在上面的自定义SQL函数中所看到的,为了始终以player1,given1,score1的形式返回用户数据,我使用了大量CASE语句(以便在需要时可以交换获取的列): 我的问题是:是否可以优化上面的SELECT查询(而不切换到较慢的PL/pgSQL) 更新: Geoff at在加入时已经为用例提供了一个很好的建议: SELECT g.gid, EXT

在双人游戏中,最常调用的语句是返回用户正在玩的游戏列表的SELECT查询:

(请原谅截图中的非拉丁字母)

正如您在上面的自定义SQL函数中所看到的,为了始终以
player1
given1
score1
的形式返回用户数据,我使用了大量CASE语句(以便在需要时可以交换获取的列):

我的问题是:是否可以优化上面的SELECT查询(而不切换到较慢的PL/pgSQL)

更新:

Geoff at在加入时已经为用例提供了一个很好的建议:

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');

由于您担心许多案例陈述,并且总是相同的条件,您可以将此条件拉出并进行两次选择,例如

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已经提出的问题,如果这真的是一个性能问题,那么首先进行测试。

lateral
distinct 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')

你怎么知道它是次优的?在外部查询已经检索到所需数据(一个用于游戏,另两个用于玩家)之后,可能(或可能不)需要在外部查询中交换两组值。是的,我不确定。实际上,我想优化两件事:性能和可读性。我不担心大小写表达式,因为它们在选择列表中。我同意这很难看;-]另一种方法是保留{player1,player2}符号,或在棋盘游戏{player1,player2}或棒球游戏{team_home,team_visitors}中修改类似的内容,和/或将交换留给表示层。为了避免出现多种情况,可以使用:
CASE WHEN g.player1=in_uid THEN(g.player1,g.score1,…):tp_game_data-ELSE(g.player2,g.score2,…):tp_game_data END
注意:在前一篇文章中,OP有一个类似的(相当庞大的)解决方案,使用UNION是的,我之前使用了带有交换字段的UNION SELECT:-),但我正在尝试优化此语句,因为它是最常调用的语句。然后可能会查看
解释(分析)
对于这两个查询
选择union
选择case
提供了更多细节。
select ...
       g.player2, g.player1,
       extract(epoch from g.played2)::int, extract(epoch from g.played1)::int,
       ...
       g.score2, g.score1,
       ...
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')