В чем разница между операторами куба, rollup и groupBy?

Вопрос в значительной степени находится в названии. Я не могу найти подробную документацию относительно различий.

Я замечаю разницу, потому что, когда меняются функции куба и функции groupBy, я получаю разные результаты. Я заметил, что для результата с использованием "куба" я получил много нулевых значений в выражениях, которые я часто группировал.

Ответы

Ответ 1

Они не предназначены для работы одинаково. groupBy является просто эквивалентом предложения GROUP BY в стандартном SQL. Другими словами

table.groupBy($"foo", $"bar")

эквивалентно:

SELECT foo, bar, [agg-expressions] FROM table GROUP BY foo, bar

cube эквивалентно расширению cube до GROUP BY. Он принимает список столбцов и применяет агрегированные выражения ко всем возможным сочетаниям столбцов группировки. Допустим, у вас есть такие данные:

val df = Seq(("foo", 1L), ("foo", 2L), ("bar", 2L), ("bar", 2L)).toDF("x", "y")
df.show

// +---+---+
// |  x|  y|
// +---+---+
// |foo|  1|
// |foo|  2|
// |bar|  2|
// |bar|  2|
// +---+---+

и вы вычисляете cube(x, y) со счетчиком как агрегацию:

df.cube($"x", $"y").count.show

// +----+----+-----+     
// |   x|   y|count|
// +----+----+-----+
// |null|   1|    1|   <- count of records where y = 1
// |null|   2|    3|   <- count of records where y = 2
// | foo|null|    2|   <- count of records where x = foo
// | bar|   2|    2|   <- count of records where x = bar AND y = 2
// | foo|   1|    1|   <- count of records where x = foo AND y = 1
// | foo|   2|    1|   <- count of records where x = foo AND y = 2
// |null|null|    4|   <- total count of records
// | bar|null|    2|   <- count of records where x = bar
// +----+----+-----+

Аналогичная функция для cube - это rollup, которая вычисляет иерархические промежуточные итоги слева направо:

df.rollup($"x", $"y").count.show
// +----+----+-----+
// |   x|   y|count|
// +----+----+-----+
// | foo|null|    2|   <- count where x is fixed to foo
// | bar|   2|    2|   <- count where x is fixed to bar and y is fixed to  2
// | foo|   1|    1|   ...
// | foo|   2|    1|   ...
// |null|null|    4|   <- count where no column is fixed
// | bar|null|    2|   <- count where x is fixed to bar
// +----+----+-----+

Только для сравнения можно увидеть результат простой groupBy:

df.groupBy($"x", $"y").count.show

// +---+---+-----+
// |  x|  y|count|
// +---+---+-----+
// |foo|  1|    1|   <- this is identical to x = foo AND y = 1 in CUBE or ROLLUP
// |foo|  2|    1|   <- this is identical to x = foo AND y = 2 in CUBE or ROLLUP
// |bar|  2|    2|   <- this is identical to x = bar AND y = 2 in CUBE or ROLLUP
// +---+---+-----+

Подводя итог:

  • При использовании простой GROUP BY каждая строка включается только один раз в соответствующую сводку.
  • С GROUP BY CUBE(..) каждая строка включается в сводку каждой комбинации уровней, которые она представляет, в нее включены подстановочные знаки. Логически, показанное выше эквивалентно чему-то вроде этого (предполагая, что мы могли бы использовать NULL заполнители):

    SELECT NULL, NULL, COUNT(*) FROM table
    UNION ALL
    SELECT x,    NULL, COUNT(*) FROM table GROUP BY x
    UNION ALL
    SELECT NULL, y,    COUNT(*) FROM table GROUP BY y
    UNION ALL
    SELECT x,    y,    COUNT(*) FROM table GROUP BY x, y
    
  • С GROUP BY ROLLUP(...) похож на cube, но работает иерархически, заполняя столбцы слева направо.

    SELECT NULL, NULL, COUNT(*) FROM table
    UNION ALL
    SELECT x,    NULL, COUNT(*) FROM table GROUP BY x
    UNION ALL
    SELECT x,    y,    COUNT(*) FROM table GROUP BY x, y
    

rollup и cube поступают из расширений хранилища данных, поэтому, если вы хотите лучше понять, как это работает, вы также можете проверить документацию своего любимого RDMBS. Например, PostgreSQL, представленный как в 9.5, так и они относительно хорошо документированы.