sqlpostgresqlaggregate-functions

Complex point aggregation with modifiers


I have the following tables in a Postgres 16.5 database, tracking player completions for levels, with some modifiers:

CREATE TABLE levels (
  id int PRIMARY KEY
, points int NOT NULL
);

CREATE TABLE completions(
  user_id int
, level_id int REFERENCES levels
, full_completion boolean
, alternate_strategy boolean
, is_fastest boolean
);

INSERT INTO levels VALUES
  (1, 100)
, (2,  50)
, (3,  10)
;

INSERT INTO completions VALUES
  (1, 1, true , false, false)
, (1, 1, false, true , false)  --> 500
, (1, 2, true , true , false)
, (1, 2, true , false, false)  --> 300
, (1, 3, false, true , true )
, (1, 3, true , false, true )
, (1, 3, false, false, false)  -->  70
;

Every row in completions is unique, so users can have multiple completions in the same level, but with different modifiers.

Levels can be worth as little as one point, but it's always a natural number (so strictly greater than zero).

The points for the modifiers are assigned as follows:

Here are some examples of the completions table, assuming a level 1 worth 100 points:

1, 1, true, false, false
1, 1, false, true, false

Here, the first row would give 100*3 points, and the second 100*2, for a total of 500 points

1, 1, true, true, false
1, 1, true, false, false

Here, the first row would give us 100*3*2 points, while the second row would give us 0 points because we have already used the first modifier in the first completion. The total is 600 points.

1, 1, false, true, true
1, 1, true, false, true
1, 1, false, false, false

Here, the second row would give us 100*3+20 points, while the first only 100*2 but not the +20, because it was already used once. The third row nets no points. The total is 520.

I need a query that outputs player ID, level ID, and points gained for that level, accounting for all completions of the player in that level - ideally in a single query to calculate a leaderboard.

As for the completion modifier priority rules, I'll describe them some more:


Solution

  • Simpler query for your updated rules:

    SELECT c.user_id, c.level_id, l.points * mult + add AS total_score
    FROM  (
       SELECT user_id, level_id
            , CASE WHEN bool_or(full_completion AND alternate_strategy) THEN 6
                   ELSE GREATEST (CASE WHEN bool_or(full_completion)    THEN 3 ELSE 0 END
                                + CASE WHEN bool_or(alternate_strategy) THEN 2 ELSE 0 END, 1)
                   END AS mult
            , CASE WHEN bool_or(is_fastest) THEN 20 ELSE 0 END AS add
       FROM   completions
       GROUP  BY 1, 2
       ) c
    JOIN   levels l ON l.id = c.level_id
    ORDER  BY 1, 2;
    

    fiddle

    Produces your desired result in a single query.

    In subquery c, compute multiplier mult and addition add with the aggregate function bool_or(). In the outer SELECT do the math.