Selecting multiple max() values using a single SQL statement

前端 未结 3 1226
梦谈多话
梦谈多话 2020-12-07 04:57

I have a table that has data that looks something like this:

data_type, value
World of Warcraft, 500
Quake 3, 1500
Quake 3, 1400
World of Warcraft, 1200
Fina         


        
相关标签:
3条回答
  • 2020-12-07 05:14

    Once again, for more than just a few "data types", I suggest to use crosstab():

    SELECT * FROM crosstab(
         $$SELECT DISTINCT ON (1, 2)
                  'max' AS "type", data_type, val
           FROM   tbl
           ORDER  BY 1, 2, val DESC$$
    
        ,$$VALUES ('Final Fantasy'), ('Quake 3'), ('World of Warcraft')$$)
    AS x ("type" text, "Final Fantasy" int, "Quake 3" int, "World of Warcraft" int)
    

    Returns:

    type | Final Fantasy | Quake 3 | World of Warcraft
    -----+---------------+---------+-------------------
    max  | 500           | 1500    |    1200
    

    More explanation for the basics:
    PostgreSQL Crosstab Query

    Dynamic solution

    The tricky thing is to make this completely dynamic: to make it work for

    • an unknown number of columns (data_types in this case)
    • with unknown names (data_types again)

    At least the type is well known: integer in this case.

    In short: that's not possible with current PostgreSQL (including 9.3). There are approximations with polymorphic types and ways to circumvent the restrictions with arrays or hstore types. May be good enough for you. But it's strictly not possible to get the result with individual columns in a single SQL query. SQL is very rigid about types and wants to know what to expect back.

    However, it can be done with two queries. The first one builds the actual query to use. Building on the above simple case:

    SELECT $f$SELECT * FROM crosstab(
         $$SELECT DISTINCT ON (1, 2)
                  'max' AS "type", data_type, val
           FROM   tbl
           ORDER  BY 1, 2, val DESC$$
    
        ,$$VALUES ($f$     || string_agg(quote_literal(data_type), '), (') || $f$)$$)
    AS x ("type" text, $f$ || string_agg(quote_ident(data_type), ' int, ') || ' int)'
    FROM  (SELECT DISTINCT data_type FROM tbl) x
    

    This generates the query you actually need. Run the second one inside the same transaction to avoid concurrency issues.

    Note the strategic use of quote_literal() and quote_ident() to sanitize all kinds of illegal (for columns) names and prevent SQL injection.

    Don't get confused by multiple layers of dollar-quoting. That's necessary for building dynamic queries. I put it as simple as possible.

    0 讨论(0)
  • 2020-12-07 05:16

    If you want to return the max value for each data_type in a separate column, then you should be able to use an aggregate function with a CASE expression:

    select
      max(case when data_type='World of Warcraft' then value end) WorldofWarcraft,
      max(case when data_type='Quake 3' then value end) Quake3,
      max(case when data_type='Final Fantasy' then value end) FinalFantasy
    from yourtable;
    

    See SQL Fiddle with Demo

    0 讨论(0)
  • 2020-12-07 05:21

    If you want your data to be aggregated in single string, go with bluefeet example, if you need a recordset with a record for each type:

    select
        data_type,
        max(value) as value
    from table1
    group by data_type
    
    0 讨论(0)
提交回复
热议问题