在单个 SELECT 查询中查找最大值的总和

Find sum of max values in a single SELECT query

我已经准备好 an SQL Fiddle 我的问题了。

在文字游戏中,我 运行 a custom function 查找用户在她上一回合玩过的所有单词:

无效单词的 scoreNULL(如果需要,可以更改为 -1)。

有效词有正数score,如果有多个匹配词,那么我只需要最高分(并丢弃其他分数)。

例如,如果玩家玩水平单词 "ab" 得分 8 和垂直单词 "ab" 得分 2,那么她在该回合只能获得 8 分。

这是我的测试table:

CREATE TABLE words(word varchar, score integer);

这里我用测试数据填充它:

INSERT INTO words (word, score) VALUES
('ab', 8),  -- word with higher score should be taken
('ab', 2),  -- same word with lower score should be discarded
('xy', 2),
('zz', NULL); -- invalid word marked by NULL (or -1)

我可以看看

是否打了一个无效的单词
IF EXISTS (SELECT 1 FROM words WHERE score IS NULL) THEN
    RAISE EXCEPTION 'Invalid word played';
END IF;

我可以用 GROUP BY:

删除重复的单词
SELECT word, max(score) as score FROM words GROUP BY word;

不过我的问题是:

如何将以上两个语句合并为一个 SELECT 语句,以便我:

  1. 知道是否播放了无效单词
  2. 总得分总和(以便我可以更新玩家的得分)

我正在寻找一个单一的声明,这样 the custom function 就不会 运行 多次并且最好没有临时的 table.

结果应该如下所示(我将从另一个自定义 PL/pgSQL 函数调用它):

DECLARE
    total_user_score    integer;
    invalid_words_found boolean;

SELECT
    .....,              -- how to calculate this value please?
    .....               -- how to calculate this value please?
INTO STRICT
    total_user_score,
    invalid_words_found
FROM words_check_words(....);  -- avoid calling this function twice

IF invalid_words_found THEN
    RAISE EXCEPTION "Invalid words found";
ELSE
    UPDATE games SET user_score = user_score + total_user_score;
END IF;

(编辑为 return invalid_words_found 的布尔值)
(编辑为使用 bool_or 聚合函数)

如果我没理解错的话:

with cte as (
    select max(score) as score,
           bool_or(score is null) as has_invalid
      from words_check_words(....)
     group by word
)
select coalesce(sum(score), 0) as total_user_score,
       bool_or(has_invalid) as invalid_words_found
  from cte

您的自定义函数只会被调用一次。

编辑: 集成到您的程序中,它看起来像这样:

DECLARE
    total_user_score    integer;
    invalid_words_found boolean;

with cte as (
    select max(score) as score,
           bool_or(score is null) as has_invalid
      from words_check_words(....)
     group by word
)
select coalesce(sum(score), 0),
       bool_or(has_invalid)
INTO STRICT
    total_user_score,
    invalid_words_found
FROM cte;

IF invalid_words_found THEN
    RAISE EXCEPTION "Invalid words found";
ELSE
    UPDATE games SET user_score = user_score + total_user_score;
END IF;

你可以这样做:

select   coalesce(sum(max_score), 0) as total_user_score,
         count(*) - count(max_score) as invalid_words_found
from     (select   max(score) as max_score
          from     words
          group by word) x

参见:Sql Fiddle

这假设如果一个词无效,它也不能以非空分数出现(在另一条记录中)。

IF 语句需要如下所示:

IF invalid_words_found > 0 THEN

...您可以在错误消息中显示无效单词的数量:

    RAISE EXCEPTION '% Invalid words found!', invalid_words_found;
select 
  word,
  max(
    case 
      when score is null then raise_error('Invalid word: ' || word)::int 
      else score 
    end)
from words
group by word;

其中 raise_error 函数声明为

create function raise_error(text) returns text language plpgsql volatile as
$body$
begin
  raise exception '%', ;
end $body$;

为了使查询更优雅,可以创建另一个通用函数:

create function assert(
  p_cond boolean,
  p_message text,
  p_result anyelement) returns anyelement language plpgsql volatile as
$body$
begin
  if p_cond then
    return p_result;
  else
    raise exception '%', p_message;
  end if;
end $body$;

并且查询变得更加紧凑:

select 
  word,
  max(assert(score is not null, 'Invalid word: ' || word, score))
from words
group by word;