1

I want to get the MAX value of close to 20 fields in my database. My issue is that I also want some other fields with all of these MAX values. I'm using PostgreSQL.

Here is an example:

Match Table definition

CREATE TABLE "matches" (
    "id" int4 NOT NULL DEFAULT nextval('match_players_id_seq'::regclass),
    "kills" int4 NOT NULL,
    "deaths" int4 NOT NULL,
    "assists" int4 NOT NULL,
    "gamemode" int4 NOT NULL,
    PRIMARY KEY ("id")
);

Match table example data

id kills deaths assists gamemode
1 0 0 3 1
2 1 0 2 2
3 1 12 0 3
4 7 2 27 1
5 1 4 27 2
6 2 3 1 3
INSERT INTO "matches" ("id", "kills", "deaths", "assists", "gamemode") VALUES
(1, 0, 0, 3, 1),
(2, 1, 0, 2, 2),
(3, 1, 12, 0, 3),
(4, 7, 2, 27, 1),
(5, 1, 4, 27, 2),
(6, 2, 3, 1, 3);

Result I want (Table or JSON style) from the above data

what amount gamemode id
"kills" 7 1 4
"deaths" 12 3 3
"assists" 27 1 4
{
  "maxKills": {"id": 4, "kills": 7, "gamemode": 1},
  "maxDeaths": {"id": 3, "deaths": 12, "gamemode": 3}
  "maxAssists": {"id": 4, "assists": 27, "gamemode": 1} // Get the first one if 2 are equal
}

This is simplified. Here is a little pastebin to give you an idea of the real query: https://pastebin.com/eT8MNKKe
And another pastebin with the @Erwin answer's implementation: https://pastebin.com/2B8imJTj

I already posted this question two years ago but I used a NoSQL database (Mongo): Get objects containing max values for multiple fields using aggregation in mongodb

Here is my idea, I don't know if it's the best one:
Do 2 queries. The first one to get the MAX value of the 20 columns, and the second one to fetch the rows with a WHERE clause on the 20 aggregated MAX values. I'm just not sure how to do when multiple rows have the same MAX value.

Kalane
  • 57
  • 2
  • 8
  • Please review [ask]. You need to post your table definition (do not need the complete definition, just 3,4 columns you want the max values on) and some test data and the expected results of tat data, all as text - **no images**. But I would take a guess that you could use the [window](https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS) version of the max function. something like `max(kills) over (partition by id)` and repeat for each column name. That would only be 1 query. – Belayer Sep 15 '21 at 20:15
  • First, thanks I will check these links. I did put my table definition ? It's markdown not an image. I updated the results I want from JS to JSON, it's maybe a little more clear. – Kalane Sep 15 '21 at 20:22
  • Mark down is not a table definition, it is a description. I cannot copy/past and run a query against it. Sorry, I guess I should have said post the table ddl. – Belayer Sep 15 '21 at 20:24
  • I see! I have updated my question, I hope it's what you meant. It's the first time that I actually export data like this and I had to remove more than 40 fields so I hope it's not broken. – Kalane Sep 15 '21 at 20:34
  • @ErwinBrandstetter I did not. I was just asking for some more advices. The edits I did to my question were just to give more context as other people asked for. – Kalane Sep 15 '21 at 22:45
  • @Kalane: Fair enough. I seem to have misunderstood. Added an answer. – Erwin Brandstetter Sep 15 '21 at 22:49

3 Answers3

1

You want to get the rows with the maximum values. A SQL result is a table consisting of columns and rows. This is a pure SQL approach:

select what, amount, gamemode, id
from
(
  select id, gamemode, 'kills' as what, kills as amount, max(kills) over () as max_amount from mytable
  union all
  select id, gamemode, 'deaths' as what, deaths as amount, max(deaths) over () as max_amount from mytable
  union all
  select id, gamemode, 'assists' as what, assists as amount, max(assists) over () as max_amount from mytable
  union all
  ...
) all_candidates
where amount = max_amount;

There may be some JSON function build in to convert this result to JSON. I don't know. Maybe others can answer this.

Thorsten Kettner
  • 89,309
  • 7
  • 49
  • 73
  • The JSON was just an example because I'm not sure how to display SQL response in text. Thanks I will check that. Is it still something "ok" to do union all like this If I have more than 20 fields to MAX ? – Kalane Sep 15 '21 at 20:40
  • Yes. The task is not very common, but if you want all the maximum row(s) per column, then you can use `UNION ALL` to glue them all together for a final data set. This is probably the easiest approach. – Thorsten Kettner Sep 15 '21 at 20:49
  • As mentioned, a SQL query result is a table. You are showing the matches table in your result, so you know how to show a table in a request. You should show the result table just the same. – Thorsten Kettner Sep 15 '21 at 21:09
  • True, I have updated my question with the idea coming from your answer. – Kalane Sep 15 '21 at 21:19
  • Just one more question. How would you deal when multiple rows have the same max value ? For now your answer gets it both but I need only one. I tried to add ``LIMIT 1`` at the end of the 3 select subqueries you made but I have syntax error. Thanks! – Kalane Sep 15 '21 at 21:29
  • You can use `row_number() over (order by kills desc) as rn` (same for deaths etc.) and then replace `where amount = max_amount` by `where rn = 1`. This is a ranking without considering ties. You only keep one row (per column) you rank first. – Thorsten Kettner Sep 15 '21 at 21:32
  • Okok thanks, I'm just now wondering as I said to Belayer: is it a lot better than just 20 oldschool queries ? Because I think I oversimplified the problem: I need to make 2 join per query to get columns I need and to add multiple where clauses to filter the data I'm looking at. The entire query takes 40ms (against like 2ms for simplier queries) on an almost empty database so I'm a little concerned. – Kalane Sep 15 '21 at 21:49
  • Here is a little pastebin so that you can have an idea of the real query: https://pastebin.com/eT8MNKKe – Kalane Sep 15 '21 at 22:12
1

Perfect. You can use window version of max as I suspected. The only real difficulty was devising something to partition it on and not get the value for each row.

select m.*
     , max(m.kills )    over () max_kills
     , max(m.deaths )   over () max_deaths
     , max(m.assists )  over () max_assists
     , max(m.gamemode ) over () max_gamemode  
 from matches m; 

See demo. For demo I added a couple of columns just to show the inclusion of 'other columns', but did not populate them.

NOTE: Not directly related but you should avoid double quotes (") on database names. Once used they must always be used. It is just not worth the effort.

Belayer
  • 13,578
  • 2
  • 11
  • 22
  • 1
    You can remove the `partition by` clauses. If there is nothing you want to group by then just leave the parentheses empty. The advice concerning the double quotes is very good. – Thorsten Kettner Sep 15 '21 at 21:06
  • Thanks @ThorstenKettner. I knew that, just sometimes the brain does not go all the way. Kalane I have updated reply to what Thorsten in saying. – Belayer Sep 15 '21 at 21:12
  • Thanks a lot! I think the issue with this solution is that if I have 2M rows, the result will give me 2M rows too right ? Instead of for example 20 if I do the max on 20 fields. By the way the quotes were generated by TablePlus when I made the exportation but thanks for the advice! – Kalane Sep 15 '21 at 21:14
  • That would be correct. I guess I misunderstood what you ment by "some other fields " If you want **only** the max of certain them only select those, or just use a standard MAX function on each. – Belayer Sep 15 '21 at 21:18
  • Sorry for that, I've updated my question with Thorsten idea. I'm now wondering if it's a lot better than just 20 oldschool queries. Because I think I oversimplified the problem: I need to make 2 join per query to get columns I need + add multiple where clauses to filter the data I'm looking at. – Kalane Sep 15 '21 at 21:24
1

This should be fastest and simplest:

(SELECT 'kills' AS what, kills, gamemode, id   FROM matches ORDER BY kills DESC, id LIMIT 1)
UNION ALL
(SELECT 'deaths'       , deaths, gamemode, id  FROM matches ORDER BY deaths DESC, id LIMIT 1)
UNION ALL
(SELECT 'assists'      , assists, gamemode, id FROM matches ORDER BY assists DESC, id LIMIT 1)
--  more ...

db<>fiddle here

Add id as second ORDER BY expression. This way, if multiple rows tie for the highest score, the row with the smallest id is chosen.

All parentheses are required. See:

If any of the ORDER BY columns can be NULL, add NULLS LAST. See:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228