Home > Back-end >  Complex aggregation with select
Complex aggregation with select

Time:11-30

I have table in DB like this (ID column is not a unique UUID, just some object ID, primary key still exists, but removed for example)

ID Option Value Number of searches Search date
1 abc a 1 2021-01-01
1 abc b 2 2021-01-01
1 abc a 3 2021-01-02
1 abc b 4 2021-01-02
1 def a 5 2021-01-01
1 def b 6 2021-01-01
1 def a 7 2021-01-02
1 def b 8 2021-01-02
2 ... ... ... ...
... ... ... ... ...
N xyz xyz M any date

I want to get a some kind of statistic report like

ID Total searches Option Total number of option searches Value Total value searches
1 20 abc 10 a 4
b 6
def 26 a 12
b 14

Is it possible in some way? UNION isn't working were, clause GROUP BY also have no idea how can solve that

I can do it easily in kotlin, just request everything and aggregate to classes like that

    data class SearchAggregate (
        val id: String,
        val options: List<Option>,
        val values: List<Value>
    )
    
    data class Option (
        val name: String,
        val totalSearches: Long
    )

    data class Value(
        val name: String,
        val totalSearches: Long
    )

and export to file but I have to request data by SQL

CodePudding user response:

You can use the COUNT() window function in a subquery to preprocess the data. For example:

select
  id,
  max(total_searches) as total_searches,
  option,
  max(total_options) as total_options,
  value,
  max(total_values) as total_values
from (
  select
    id,
    count(*) over(partition by id) as total_searches,
    option,
    count(*) over(partition by id, option) as total_options,
    value,
    count(*) over(partition by id, option, value) as total_values
  from t
) x
group by id, option, value

See running example at DB Fiddle #1.

Or you can use a shorter query, as in:

select
  id,
  sum(cnt) over(partition by id) as total_searches,
  option,
  sum(cnt) over(partition by id, option) as total_searches,
  value,
  cnt
from (
  select id, option, value, count(*) as cnt from t group by id, option, value
) x

See running example at DB Fiddle #2.

CodePudding user response:

The first option is to use ROLLUP, as that is the intended SQL pattern. It doesn't give you the results in the format you asked for. That's a reflection on the format you asked for not being normalised.

SELECT
  id,
  option,
  value,
  SUM(`Number of searches`)   AS total_searches
FROM
  your_table
GROUP BY
  ROLLUP(
    id,
    option,
    value
  )

It's concise, standard practice, SQL Friendly, etc, etc.

Thinking in terms of these normalised patterns will make your use of SQL much more effective.

That said, you CAN use SQL to aggregate and restructure the results. You get the structure you want, but with more code and increased maintenance, lower flexibility, etc.

SELECT
  id,
  SUM(SUM(`Number of searches`)) OVER (PARTITION BY id)   as total_by_id,
  option,
  SUM(SUM(`Number of searches`)) OVER (PARTITION BY id, option)   as total_by_id_option,
  value,
  SUM(`Number of searches`)   AS total_by_id_option_value
FROM
  your_table
GROUP BY
  id,
  option,
  value

That doesn't leave blanks where you have them, but that's because to do so is a SQL Anti-Pattern, and should be handled in your presentation layer, not in the database.

Oh, and please don't use column names with spaces; stick to alphanumeric characters with underscores.

Demo : https://www.db-fiddle.com/f/fX3tNL82gqgVCRoa3v6snP/5

  • Related