Pull to refresh

PostgreSQL 9.5: что нового? Часть 3. GROUPING SETS, CUBE, ROLLUP

Reading time16 min
Views33K
Продолжаем знакомиться с новыми возможностями в PostgreSQL 9.5.
Часть 1. INSERT… ON CONFLICT DO NOTHING/UPDATE и ROW LEVEL SECURITY
Часть 2. TABLESAMPLE
Сегодня рассмотрим множественные группировки в одном запросе. Эта возможность была описана еще в стандарте SQL-99. Её удобно применять в том случае, если вам нужно сделать несколько запросов к одной и той же таблице, отличающихся только условием в GROUP BY. Для этого модификаторы GROUPING SETS, ROLLUP, CUBE указываются в качестве элемента группировки после ключевого слова GROUP BY.
Давайте посмотрим поближе, как это работает.

Пусть у нас есть схема, в которой есть данные о платежах, каждый платеж имеет тип, город, в котором этот платеж был совершен и сумму платежа. Город обязательно имеет страну и, опционально, регион. Ниже приведены запросы для создания такой схемы и заполнения её данными.
Создание схемы
DROP TABLE IF EXISTS payment;
DROP TABLE IF EXISTS payment_type;
DROP TABLE IF EXISTS city;
DROP TABLE IF EXISTS state;
DROP TABLE IF EXISTS country;

CREATE TABLE country (
  id   SERIAL PRIMARY KEY,
  name VARCHAR NOT NULL
);


CREATE TABLE state (
  id         SERIAL PRIMARY KEY,
  name       VARCHAR NOT NULL,
  country_id INT REFERENCES country (id)
);


CREATE TABLE city (
  id         SERIAL PRIMARY KEY,
  name       VARCHAR NOT NULL,
  state_id   INT     NULL REFERENCES state (id),
  country_id INT     NOT NULL REFERENCES country (id),
  population BIGINT  NOT NULL
);


CREATE TABLE payment_type (
  id   SERIAL PRIMARY KEY,
  name VARCHAR NOT NULL
);

CREATE TABLE payment (
  id              BIGSERIAL PRIMARY KEY,
  payment_type_id INT            NOT NULL REFERENCES payment_type (id),
  city_id         INT            NOT NULL REFERENCES city (id),
  amount          NUMERIC(10, 2) NOT NULL
);

INSERT INTO country (name)
VALUES ('Russia'), ('Ukraine');

INSERT INTO state (name, country_id)
VALUES ('Moscow region', 1), ('Samara region', 1), ('Kursk region', 1), ('Tatarstan', 1),
  ('Kiev region', 2), ('Lugansk region', 2), ('Lvov region', 2), ('Odessa region', 2);


–- Москва и Киев не являются частью Московской и Киевской области, согласно законам РФ и Украины
INSERT INTO city (name, state_id, country_id, population)
VALUES ('Moscow', NULL, 1, 12197596), ('Dubna', 1, 1, 75176),
  ('Samara', 2, 1, 1171820), ('Tolyatti', 2, 1, 719646), ('Syzran', 2, 1, 175222), ('Novokuybyshevsk', 2, 1, 105007),
  ('Kursk', 3, 1, 435117),
  ('Kazan', 4, 1, 1205651),
  ('Kiev', NULL, 2, 2888470), ('Irpen', 5, 2, 39972), ('Borispol', 5, 2, 60102), ('Belaya Tserkov', 5, 2, 211205),
  ('Lugansk', 6, 2, 417990), ('Lisichansk', 6, 2, 103459), ('Severodonetsk', 6, 2, 108899), ('Popasnaya', 6, 2, 21765),
  ('Lvov', 7, 2, 729038), ('Drogobych', 7, 2, 76866),
  ('Odessa', 8, 2, 1017022), ('Izmail', 8, 2, 72501);

INSERT INTO payment_type (name)
VALUES ('Online'), ('Box office'), ('Terminal');


INSERT INTO payment (payment_type_id, city_id, amount)
  SELECT
    ceil(random() * 3),
    ceil(random() * 20),
    trunc(cast(random() * 10000 AS NUMERIC), 2)
  FROM generate_series(1, 10000);


Предположим, что мы хотим получить статистику о сумме платежей в каждом городе и в каждой стране. Раньше для этого нужно было написать запрос вида:
(SELECT sum(amount), c.country_id, NULL as city_id
FROM payment AS p
INNER JOIN city AS c
ON p.city_id=c.id
GROUP BY c.country_id
ORDER BY c.country_id)
UNION ALL
(SELECT sum(amount), NULL, p.city_id
FROM payment AS p
GROUP BY p.city_id
ORDER BY p.city_id)

Результат запроса
sum country_id city_id
19794121.93 1 NULL
30138426.57 2 NULL
2420939.72 NULL 1
2611787.51 NULL 2
2357570.54 NULL 3
2796471.48 NULL 4
2327588.11 NULL 5
2563701.69 NULL 6
2442654.38 NULL 7
2273408.5 NULL 8
2509228.24 NULL 9
2716771.77 NULL 10
2745394.99 NULL 11
2554721.34 NULL 12
2526112.36 NULL 13
2818708.34 NULL 14
2437768.84 NULL 15
2246483.68 NULL 16
2384795.14 NULL 17
2437849.05 NULL 18
2470876.07 NULL 19
2289716.75 NULL 20


С версии 9.5 подобный запрос проще написать так:
SELECT
  sum(amount),
  c.country_id,
  p.city_id
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY GROUPING SETS(c.country_id, p.city_id);

Результат запроса
sum country_id city_id
19794121.93 1 NULL
30138426.57 2 NULL
2420939.72 NULL 1
2611787.51 NULL 2
2357570.54 NULL 3
2796471.48 NULL 4
2327588.11 NULL 5
2563701.69 NULL 6
2442654.38 NULL 7
2273408.5 NULL 8
2509228.24 NULL 9
2716771.77 NULL 10
2745394.99 NULL 11
2554721.34 NULL 12
2526112.36 NULL 13
2818708.34 NULL 14
2437768.84 NULL 15
2246483.68 NULL 16
2384795.14 NULL 17
2437849.05 NULL 18
2470876.07 NULL 19
2289716.75 NULL 20


Как видно, GROUPING SETS в результате запроса возвращает данные следующим образом: в каждой строке одной из колонок из перечисленных в скобках соответствует значение, в то время как остальные колонки (из списка в скобках) заполнены NULL. Колонки не перечисленные в GROUPING SETS, вычисляются как обычно.

Чтобы получить и полную сумму (без группировки) можно использовать пустую группировку — (). При пустой группировке все поля, участвующие в GROUPING SETS заполнены NULL:
SELECT
  sum(amount),
  p.city_id,
  c.country_id
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY GROUPING SETS(p.city_id, C.country_id, ());

Результат запроса
sum country_id city_id
19794121.93 1 NULL
30138426.57 2 NULL
49932548.5 NULL NULL
2420939.72 NULL 1
2611787.51 NULL 2
2357570.54 NULL 3
2796471.48 NULL 4
2327588.11 NULL 5
2563701.69 NULL 6
2442654.38 NULL 7
2273408.5 NULL 8
2509228.24 NULL 9
2716771.77 NULL 10
2745394.99 NULL 11
2554721.34 NULL 12
2526112.36 NULL 13
2818708.34 NULL 14
2437768.84 NULL 15
2246483.68 NULL 16
2384795.14 NULL 17
2437849.05 NULL 18
2470876.07 NULL 19
2289716.75 NULL 20


Попробуем теперь получить сумму платежей в разрезе по городам, регионам и странам:
SELECT
  sum(amount),
  p.city_id,
  c.state_id,
  c.country_id
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY GROUPING SETS(p.city_id, c.state_id, c.country_id);

Результат запроса
sum city_id state_id country_id
2420939.72 1 NULL NULL
2611787.51 2 NULL NULL
2357570.54 3 NULL NULL
2796471.48 4 NULL NULL
2327588.11 5 NULL NULL
2563701.69 6 NULL NULL
2442654.38 7 NULL NULL
2273408.5 8 NULL NULL
2509228.24 9 NULL NULL
2716771.77 10 NULL NULL
2745394.99 11 NULL NULL
2554721.34 12 NULL NULL
2526112.36 13 NULL NULL
2818708.34 14 NULL NULL
2437768.84 15 NULL NULL
2246483.68 16 NULL NULL
2384795.14 17 NULL NULL
2437849.05 18 NULL NULL
2470876.07 19 NULL NULL
2289716.75 20 NULL NULL
19794121.93 NULL NULL 1
30138426.57 NULL NULL 2
2611787.51 NULL 1 NULL
10045331.82 NULL 2 NULL
2442654.38 NULL 3 NULL
2273408.5 NULL 4 NULL
8016888.1 NULL 5 NULL
10029073.22 NULL 6 NULL
4822644.19 NULL 7 NULL
4760592.82 NULL 8 NULL
4930167.96 NULL NULL NULL


Странно, мы не делали пустую группировку, но получили строку в которой все поля NULL. На самом деле, это произошло потому, что у Москвы и Киева поле state_id не заполнено, поэтому GROUPING SETS справедливо сделал группировку и по state_id = NULL. Это легко проверить, выполнив следующий запрос:
SELECT sum(amount)
FROM payment
WHERE city_id IN (1, 9);

Результат запроса
sum
4930167.96


Да, наше предположение оказалось верным и суммы совпали.

Хорошо, с тем, откуда берется эта странная строка мы разобрались, но как отличить в следующем запросе, в какой из строк полная сумма, а в какой — группировка по state_id = NULL?
SELECT
  sum(amount),
  p.city_id,
  c.state_id,
  c.country_id
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY GROUPING SETS(p.city_id, c.state_id, c.country_id, ());

Результат запроса
sum city_id state_id country_id
2420939.72 1 NULL NULL
2611787.51 2 NULL NULL
2357570.54 3 NULL NULL
2796471.48 4 NULL NULL
2327588.11 5 NULL NULL
2563701.69 6 NULL NULL
2442654.38 7 NULL NULL
2273408.5 8 NULL NULL
2509228.24 9 NULL NULL
2716771.77 10 NULL NULL
2745394.99 11 NULL NULL
2554721.34 12 NULL NULL
2526112.36 13 NULL NULL
2818708.34 14 NULL NULL
2437768.84 15 NULL NULL
2246483.68 16 NULL NULL
2384795.14 17 NULL NULL
2437849.05 18 NULL NULL
2470876.07 19 NULL NULL
2289716.75 20 NULL NULL
49932548.5 NULL NULL NULL
19794121.93 NULL NULL 1
30138426.57 NULL NULL 2
2611787.51 NULL 1 NULL
10045331.82 NULL 2 NULL
2442654.38 NULL 3 NULL
2273408.5 NULL 4 NULL
8016888.1 NULL 5 NULL
10029073.22 NULL 6 NULL
4822644.19 NULL 7 NULL
4760592.82 NULL 8 NULL
4930167.96 NULL NULL NULL


Так ведь в полной сумме значение будет больше, скажете вы, и будете правы. Конечно, в данном запросе, можно понять, что строка с бóльшей суммой и есть полная сумма. Однако, если бы в таблице были не только положительные значения, но и отрицательные, определить полную сумму было бы сложнее. Ну или если использовать другую агрегатную функцию:
SELECT
  avg(amount),
  p.city_id,
  c.state_id,
  c.country_id
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY GROUPING SETS(p.city_id, c.state_id, c.country_id, ());

Результат запроса
avg city_id state_id country_id
4841.87944 1 NULL NULL
5141.313996062992126 2 NULL NULL
4850.9681893004115226 3 NULL NULL
4958.2827659574468085 4 NULL NULL
4849.1418958333333333 5 NULL NULL
5096.8224453280318091 6 NULL NULL
5208.2182942430703625 7 NULL NULL
4985.5449561403508772 8 NULL NULL
5038.6109236947791165 9 NULL NULL
5135.6744234404536862 10 NULL NULL
5219.3821102661596958 11 NULL NULL
4903.4958541266794626 12 NULL NULL
5092.9684677419354839 13 NULL NULL
5006.5867495559502664 14 NULL NULL
4964.9059877800407332 15 NULL NULL
4992.1859555555555556 16 NULL NULL
4694.4786220472440945 17 NULL NULL
5047.3065217391304348 18 NULL NULL
4883.1542885375494071 19 NULL NULL
4945.392548596112311 20 NULL NULL
4993.25485 NULL NULL NULL
4990.9535879979828543 NULL NULL 1
4994.7674129930394432 NULL NULL 2
5141.313996062992126 NULL 1 NULL
4941.1371470732907034 NULL 2 NULL
5208.2182942430703625 NULL 3 NULL
4985.5449561403508772 NULL 4 NULL
5086.8579314720812183 NULL 5 NULL
5014.53661 NULL 6 NULL
4866.4421695257315843 NULL 7 NULL
4912.8924871001031992 NULL 8 NULL
4940.0480561122244489 NULL NULL NULL


Какая строка соответствует средней сумме платежа в таблице, а какая — средней сумме по Киеву и Москве?
К счастью, решение есть: новая функция grouping(), с помощью неё мы можем узнать, участвует ли в данной строке определенная колонка в группировке. Если grouping(column_name) возвращает 0, то column_name участвует в группировке, если 1 — не участвует:
SELECT
  avg(amount),
  p.city_id,
  c.state_id,
  c.country_id,
  grouping(c.state_id)
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY GROUPING SETS(p.city_id, c.state_id, c.country_id, ());

Результат запроса
avg city_id state_id country_id grouping
4841.87944 1 NULL NULL 1
5141.313996062992126 2 NULL NULL 1
4850.9681893004115226 3 NULL NULL 1
4958.2827659574468085 4 NULL NULL 1
4849.1418958333333333 5 NULL NULL 1
5096.8224453280318091 6 NULL NULL 1
5208.2182942430703625 7 NULL NULL 1
4985.5449561403508772 8 NULL NULL 1
5038.6109236947791165 9 NULL NULL 1
5135.6744234404536862 10 NULL NULL 1
5219.3821102661596958 11 NULL NULL 1
4903.4958541266794626 12 NULL NULL 1
5092.9684677419354839 13 NULL NULL 1
5006.5867495559502664 14 NULL NULL 1
4964.9059877800407332 15 NULL NULL 1
4992.1859555555555556 16 NULL NULL 1
4694.4786220472440945 17 NULL NULL 1
5047.3065217391304348 18 NULL NULL 1
4883.1542885375494071 19 NULL NULL 1
4945.392548596112311 20 NULL NULL 1
4993.25485 NULL NULL NULL 1
4990.9535879979828543 NULL NULL 1 1
4994.7674129930394432 NULL NULL 2 1
5141.313996062992126 NULL 1 NULL 0
4941.1371470732907034 NULL 2 NULL 0
5208.2182942430703625 NULL 3 NULL 0
4985.5449561403508772 NULL 4 NULL 0
5086.8579314720812183 NULL 5 NULL 0
5014.53661 NULL 6 NULL 0
4866.4421695257315843 NULL 7 NULL 0
4912.8924871001031992 NULL 8 NULL 0
4940.0480561122244489 NULL NULL NULL 0


На самом деле, grouping возвращает битовую маску для колонок перечисленных в ней:
SELECT
  avg(amount),
  p.city_id,
  c.state_id,
  c.country_id,
  grouping(p.city_id, c.state_id, c.country_id)
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY GROUPING SETS(p.city_id, c.state_id, c.country_id, ());

Результат запроса
avg city_id state_id country_id grouping
4841.87944 1 NULL NULL 3
5141.313996062992126 2 NULL NULL 3
4850.9681893004115226 3 NULL NULL 3
4958.2827659574468085 4 NULL NULL 3
4849.1418958333333333 5 NULL NULL 3
5096.8224453280318091 6 NULL NULL 3
5208.2182942430703625 7 NULL NULL 3
4985.5449561403508772 8 NULL NULL 3
5038.6109236947791165 9 NULL NULL 3
5135.6744234404536862 10 NULL NULL 3
5219.3821102661596958 11 NULL NULL 3
4903.4958541266794626 12 NULL NULL 3
5092.9684677419354839 13 NULL NULL 3
5006.5867495559502664 14 NULL NULL 3
4964.9059877800407332 15 NULL NULL 3
4992.1859555555555556 16 NULL NULL 3
4694.4786220472440945 17 NULL NULL 3
5047.3065217391304348 18 NULL NULL 3
4883.1542885375494071 19 NULL NULL 3
4945.392548596112311 20 NULL NULL 3
4993.25485 NULL NULL NULL 7
4990.9535879979828543 NULL NULL 1 6
4994.7674129930394432 NULL NULL 2 6
5141.313996062992126 NULL 1 NULL 5
4941.1371470732907034 NULL 2 NULL 5
5208.2182942430703625 NULL 3 NULL 5
4985.5449561403508772 NULL 4 NULL 5
5086.8579314720812183 NULL 5 NULL 5
5014.53661 NULL 6 NULL 5
4866.4421695257315843 NULL 7 NULL 5
4912.8924871001031992 NULL 8 NULL 5
4940.0480561122244489 NULL NULL NULL 5


Так не очень понятно, приведем результат к типу bit(3):
SELECT
  avg(amount),
  p.city_id,
  c.state_id,
  c.country_id,
  grouping(p.city_id, c.state_id, c.country_id) :: BIT(3)
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY GROUPING SETS(p.city_id, c.state_id, c.country_id, ());

Результат запроса
avg city_id state_id country_id grouping
4841.87944 1 NULL NULL 011
5141.313996062992126 2 NULL NULL 011
4850.9681893004115226 3 NULL NULL 011
4958.2827659574468085 4 NULL NULL 011
4849.1418958333333333 5 NULL NULL 011
5096.8224453280318091 6 NULL NULL 011
5208.2182942430703625 7 NULL NULL 011
4985.5449561403508772 8 NULL NULL 011
5038.6109236947791165 9 NULL NULL 011
5135.6744234404536862 10 NULL NULL 011
5219.3821102661596958 11 NULL NULL 011
4903.4958541266794626 12 NULL NULL 011
5092.9684677419354839 13 NULL NULL 011
5006.5867495559502664 14 NULL NULL 011
4964.9059877800407332 15 NULL NULL 011
4992.1859555555555556 16 NULL NULL 011
4694.4786220472440945 17 NULL NULL 011
5047.3065217391304348 18 NULL NULL 011
4883.1542885375494071 19 NULL NULL 011
4945.392548596112311 20 NULL NULL 011
4993.25485 NULL NULL NULL 111
4990.9535879979828543 NULL NULL 1 110
4994.7674129930394432 NULL NULL 2 110
5141.313996062992126 NULL 1 NULL 101
4941.1371470732907034 NULL 2 NULL 101
5208.2182942430703625 NULL 3 NULL 101
4985.5449561403508772 NULL 4 NULL 101
5086.8579314720812183 NULL 5 NULL 101
5014.53661 NULL 6 NULL 101
4866.4421695257315843 NULL 7 NULL 101
4912.8924871001031992 NULL 8 NULL 101
4940.0480561122244489 NULL NULL NULL 101


Также можно в одном запросе использовать вместе обычную группировку и группировку с помощью GROUPING SETS:
SELECT
  avg(amount),
  c.country_id,
  p.payment_type_id,
  p.city_id,
  c.state_id
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY c.country_id, p.payment_type_id, GROUPING SETS(p.city_id, c.state_id, ());

Результат запроса
avg country_id payment_type_id city_id state_id
5024.1955882352941176 1 1 1 NULL
4871.1540119760479042 1 1 2 NULL
4891.0804861111111111 1 1 3 NULL
5130.3479896907216495 1 1 4 NULL
4739.4527586206896552 1 1 5 NULL
4803.7104 1 1 6 NULL
5028.8194375 1 1 7 NULL
4903.6742 1 1 8 NULL
4931.2117088122605364 1 1 NULL NULL
4407.8555056179775281 1 2 1 NULL
5068.5559638554216867 1 2 2 NULL
4812.6204093567251462 1 2 3 NULL
4564.1131034482758621 1 2 4 NULL
4963.2932530120481928 1 2 5 NULL
5153.3501219512195122 1 2 6 NULL
5446.8668965517241379 1 2 7 NULL
5057.8818012422360248 1 2 8 NULL
4917.934422641509434 1 2 NULL NULL
5146.2380921052631579 1 3 1 NULL
5468.14 1 3 2 NULL
4855.5371929824561404 1 3 3 NULL
5137.8994387755102041 1 3 4 NULL
4831.1288757396449704 1 3 5 NULL
5353.0667682926829268 1 3 6 NULL
5172.241280487804878 1 3 7 NULL
4989.92 1 3 8 NULL
5121.7272005988023952 1 3 NULL NULL
5224.1245625 2 1 9 NULL
5137.9207142857142857 2 1 10 NULL
5173.0209625668449198 2 1 11 NULL
4735.6070652173913043 2 1 12 NULL
5248.0194285714285714 2 1 13 NULL
4929.1857978723404255 2 1 14 NULL
5086.2014102564102564 2 1 15 NULL
4716.9701273885350318 2 1 16 NULL
4616.2608383233532934 2 1 17 NULL
4756.9175641025641026 2 1 18 NULL
4698.7787272727272727 2 1 19 NULL
5033.8821276595744681 2 1 20 NULL
4947.559810379241517 2 1 NULL NULL
5195.4805945945945946 2 2 9 NULL
5213.8818617021276596 2 2 10 NULL
5332.2921935483870968 2 2 11 NULL
4946.331030303030303 2 2 12 NULL
5020.5288888888888889 2 2 13 NULL
5019.8181914893617021 2 2 14 NULL
4875.5393452380952381 2 2 15 NULL
5169.0016551724137931 2 2 16 NULL
4605.4601807228915663 2 2 17 NULL
4930.9780838323353293 2 2 18 NULL
4985.6017441860465116 2 2 19 NULL
5137.4943046357615894 2 2 20 NULL
5035.3225511732401398 2 2 NULL NULL
4654.930718954248366 2 3 9 NULL
5048.5046242774566474 2 3 10 NULL
5171.3846739130434783 2 3 11 NULL
5042.0059302325581395 2 3 12 NULL
4997.4288095238095238 2 3 13 NULL
5071.0994117647058824 2 3 14 NULL
4941.5018562874251497 2 3 15 NULL
5110.9062837837837838 2 3 16 NULL
4853.5610857142857143 2 3 17 NULL
5451.8535625 2 3 18 NULL
4958.8998816568047337 2 3 19 NULL
4702.7937426900584795 2 3 20 NULL
5001.3644005920078934 2 3 NULL NULL
4871.1540119760479042 1 1 NULL 1
4904.9742705167173252 1 1 NULL 2
5028.8194375 1 1 NULL 3
4903.6742 1 1 NULL 4
5024.1955882352941176 1 1 NULL NULL
5068.5559638554216867 1 2 NULL 1
4868.3998074074074074 1 2 NULL 2
5446.8668965517241379 1 2 NULL 3
5057.8818012422360248 1 2 NULL 4
4407.8555056179775281 1 2 NULL NULL
5468.14 1 3 NULL 1
5045.2698285714285714 1 3 NULL 2
5172.241280487804878 1 3 NULL 3
4989.92 1 3 NULL 4
5146.2380921052631579 1 3 NULL NULL
5012.7593692022263451 2 1 NULL 5
4998.6716863905325444 2 1 NULL 6
4684.1941176470588235 2 1 NULL 7
4853.1891176470588235 2 1 NULL 8
5224.1245625 2 1 NULL NULL
5163.1096456692913386 2 2 NULL 5
5015.9978440366972477 2 2 NULL 6
4768.7078978978978979 2 2 NULL 7
5056.6103405572755418 2 2 NULL 8
5195.4805945945945946 2 2 NULL NULL
5089.1325141776937618 2 3 NULL 5
5029.1172686567164179 2 3 NULL 6
5139.3127164179104478 2 3 NULL 7
4830.0935588235294118 2 3 NULL 8
4654.930718954248366 2 3 NULL NULL


Можно объединять колонки внутри GROUPING SETS в группы
SELECT
  avg(amount),
  c.country_id,
  p.payment_type_id,
  p.city_id,
  c.state_id
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY GROUPING SETS((p.payment_type_id, c.country_id), ( c.state_id, p.city_id));

Результат запроса
avg country_id payment_type_id city_id state_id
4931.2117088122605364 1 1 NULL NULL
4947.559810379241517 2 1 NULL NULL
4917.934422641509434 1 2 NULL NULL
5035.3225511732401398 2 2 NULL NULL
5121.7272005988023952 1 3 NULL NULL
5001.3644005920078934 2 3 NULL NULL
5141.313996062992126 NULL NULL 2 1
4850.9681893004115226 NULL NULL 3 2
4958.2827659574468085 NULL NULL 4 2
4849.1418958333333333 NULL NULL 5 2
5096.8224453280318091 NULL NULL 6 2
5208.2182942430703625 NULL NULL 7 3
4985.5449561403508772 NULL NULL 8 4
5135.6744234404536862 NULL NULL 10 5
5219.3821102661596958 NULL NULL 11 5
4903.4958541266794626 NULL NULL 12 5
5092.9684677419354839 NULL NULL 13 6
5006.5867495559502664 NULL NULL 14 6
4964.9059877800407332 NULL NULL 15 6
4992.1859555555555556 NULL NULL 16 6
4694.4786220472440945 NULL NULL 17 7
5047.3065217391304348 NULL NULL 18 7
4883.1542885375494071 NULL NULL 19 8
4945.392548596112311 NULL NULL 20 8
4841.87944 NULL NULL 1 NULL
5038.6109236947791165 NULL NULL 9 NULL


Теперь перейдем к CUBE. CUBE — это что-то вроде множественного GROUPING SETS.
CUBE возвращает данные для всех возможных сочетаний колонок, перечисленных внутри. То есть для случая CUBE(c1, c2, c3)(где с1, c2, c3 — имена колонок) будут возвращены следующие сочетания:
(с1, null, null)
(null, c2, null)
(null, null, c3)
(c1, c2, null)
(c1, null, c3)
(null, c2, c3)
(c1, c2, c3)
(null, null, null)

Пример:
SELECT
  sum(amount),
  p.payment_type_id,
  c.country_id,
  p.city_id
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY CUBE(p.payment_type_id, c.country_id, p.city_id);

Результат запроса
sum payment_type_id country_id city_id
854113.25 1 1 1
813482.72 1 1 2
704315.59 1 1 3
995287.51 1 1 4
687220.65 1 1 5
840649.32 1 1 6
804611.11 1 1 7
735551.13 1 1 8
6435231.28 1 1 NULL
835859.93 1 2 9
863170.68 1 2 10
967354.92 1 2 11
871351.7 1 2 12
918403.4 1 2 13
926686.93 1 2 14
793447.42 1 2 15
740564.31 1 2 16
770915.56 1 2 17
742079.14 1 2 18
775298.49 1 2 19
709777.38 1 2 20
9914909.86 1 2 NULL
16350141.14 1 NULL NULL
784598.28 2 1 1
841380.29 2 1 2
822958.09 2 1 3
794155.68 2 1 4
823906.68 2 1 5
845149.42 2 1 6
789795.7 2 1 7
814318.97 2 1 8
6516263.11 2 1 NULL
961163.91 2 2 9
980209.79 2 2 10
826505.29 2 2 11
816144.62 2 2 12
768140.92 2 2 13
943725.82 2 2 14
819090.61 2 2 15
749505.24 2 2 16
764506.39 2 2 17
823473.34 2 2 18
857523.5 2 2 19
775761.64 2 2 20
10085751.07 2 2 NULL
16602014.18 2 NULL NULL
782228.19 3 1 1
956924.5 3 1 2
830296.86 3 1 3
1007028.29 3 1 4
816460.78 3 1 5
877902.95 3 1 6
848247.57 3 1 7
723538.4 3 1 8
6842627.54 3 1 NULL
712204.4 3 2 9
873391.3 3 2 10
951534.78 3 2 11
867225.02 3 2 12
839568.04 3 2 13
948295.59 3 2 14
825230.81 3 2 15
756414.13 3 2 16
849373.19 3 2 17
872296.57 3 2 18
838054.08 3 2 19
804177.73 3 2 20
10137765.64 3 2 NULL
16980393.18 3 NULL NULL
49932548.5 NULL NULL NULL
854113.25 1 NULL 1
784598.28 2 NULL 1
782228.19 3 NULL 1
2420939.72 NULL NULL 1
813482.72 1 NULL 2
841380.29 2 NULL 2
956924.5 3 NULL 2
2611787.51 NULL NULL 2
704315.59 1 NULL 3
822958.09 2 NULL 3
830296.86 3 NULL 3
2357570.54 NULL NULL 3
995287.51 1 NULL 4
794155.68 2 NULL 4
1007028.29 3 NULL 4
2796471.48 NULL NULL 4
687220.65 1 NULL 5
823906.68 2 NULL 5
816460.78 3 NULL 5
2327588.11 NULL NULL 5
840649.32 1 NULL 6
845149.42 2 NULL 6
877902.95 3 NULL 6
2563701.69 NULL NULL 6
804611.11 1 NULL 7
789795.7 2 NULL 7
848247.57 3 NULL 7
2442654.38 NULL NULL 7
735551.13 1 NULL 8
814318.97 2 NULL 8
723538.4 3 NULL 8
2273408.5 NULL NULL 8
835859.93 1 NULL 9
961163.91 2 NULL 9
712204.4 3 NULL 9
2509228.24 NULL NULL 9
863170.68 1 NULL 10
980209.79 2 NULL 10
873391.3 3 NULL 10
2716771.77 NULL NULL 10
967354.92 1 NULL 11
826505.29 2 NULL 11
951534.78 3 NULL 11
2745394.99 NULL NULL 11
871351.7 1 NULL 12
816144.62 2 NULL 12
867225.02 3 NULL 12
2554721.34 NULL NULL 12
918403.4 1 NULL 13
768140.92 2 NULL 13
839568.04 3 NULL 13
2526112.36 NULL NULL 13
926686.93 1 NULL 14
943725.82 2 NULL 14
948295.59 3 NULL 14
2818708.34 NULL NULL 14
793447.42 1 NULL 15
819090.61 2 NULL 15
825230.81 3 NULL 15
2437768.84 NULL NULL 15
740564.31 1 NULL 16
749505.24 2 NULL 16
756414.13 3 NULL 16
2246483.68 NULL NULL 16
770915.56 1 NULL 17
764506.39 2 NULL 17
849373.19 3 NULL 17
2384795.14 NULL NULL 17
742079.14 1 NULL 18
823473.34 2 NULL 18
872296.57 3 NULL 18
2437849.05 NULL NULL 18
775298.49 1 NULL 19
857523.5 2 NULL 19
838054.08 3 NULL 19
2470876.07 NULL NULL 19
709777.38 1 NULL 20
775761.64 2 NULL 20
804177.73 3 NULL 20
2289716.75 NULL NULL 20
2420939.72 NULL 1 1
2611787.51 NULL 1 2
2357570.54 NULL 1 3
2796471.48 NULL 1 4
2327588.11 NULL 1 5
2563701.69 NULL 1 6
2442654.38 NULL 1 7
2273408.5 NULL 1 8
19794121.93 NULL 1 NULL
2509228.24 NULL 2 9
2716771.77 NULL 2 10
2745394.99 NULL 2 11
2554721.34 NULL 2 12
2526112.36 NULL 2 13
2818708.34 NULL 2 14
2437768.84 NULL 2 15
2246483.68 NULL 2 16
2384795.14 NULL 2 17
2437849.05 NULL 2 18
2470876.07 NULL 2 19
2289716.75 NULL 2 20
30138426.57 NULL 2 NULL


Как и в GROUPING SETS можно делать группировку внутри:
SELECT
  sum(amount),
  p.payment_type_id,
  c.country_id,
  p.city_id
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY CUBE(p.payment_type_id, (c.country_id, p.city_id));

Результат запроса
sum payment_type_id country_id city_id
854113.25 1 1 1
813482.72 1 1 2
704315.59 1 1 3
995287.51 1 1 4
687220.65 1 1 5
840649.32 1 1 6
804611.11 1 1 7
735551.13 1 1 8
835859.93 1 2 9
863170.68 1 2 10
967354.92 1 2 11
871351.7 1 2 12
918403.4 1 2 13
926686.93 1 2 14
793447.42 1 2 15
740564.31 1 2 16
770915.56 1 2 17
742079.14 1 2 18
775298.49 1 2 19
709777.38 1 2 20
16350141.14 1 NULL NULL
784598.28 2 1 1
841380.29 2 1 2
822958.09 2 1 3
794155.68 2 1 4
823906.68 2 1 5
845149.42 2 1 6
789795.7 2 1 7
814318.97 2 1 8
961163.91 2 2 9
980209.79 2 2 10
826505.29 2 2 11
816144.62 2 2 12
768140.92 2 2 13
943725.82 2 2 14
819090.61 2 2 15
749505.24 2 2 16
764506.39 2 2 17
823473.34 2 2 18
857523.5 2 2 19
775761.64 2 2 20
16602014.18 2 NULL NULL
782228.19 3 1 1
956924.5 3 1 2
830296.86 3 1 3
1007028.29 3 1 4
816460.78 3 1 5
877902.95 3 1 6
848247.57 3 1 7
723538.4 3 1 8
712204.4 3 2 9
873391.3 3 2 10
951534.78 3 2 11
867225.02 3 2 12
839568.04 3 2 13
948295.59 3 2 14
825230.81 3 2 15
756414.13 3 2 16
849373.19 3 2 17
872296.57 3 2 18
838054.08 3 2 19
804177.73 3 2 20
16980393.18 3 NULL NULL
49932548.5 NULL NULL NULL
2420939.72 NULL 1 1
2611787.51 NULL 1 2
2357570.54 NULL 1 3
2796471.48 NULL 1 4
2327588.11 NULL 1 5
2563701.69 NULL 1 6
2442654.38 NULL 1 7
2273408.5 NULL 1 8
2509228.24 NULL 2 9
2716771.77 NULL 2 10
2745394.99 NULL 2 11
2554721.34 NULL 2 12
2526112.36 NULL 2 13
2818708.34 NULL 2 14
2437768.84 NULL 2 15
2246483.68 NULL 2 16
2384795.14 NULL 2 17
2437849.05 NULL 2 18
2470876.07 NULL 2 19
2289716.75 NULL 2 20


ROLLUP так же, как и CUBE — что-то вроде множественного GROUPING SETS, с тем отличием, что ROLLUP генерирует сочетания, убирая колонки по одной с конца. Таким образом, ROLLUP(c1, c2, c3, c4) вернет следующие сочетания:
(c1, c2, c3, c4)
(c1, c2, c3, null)
(c1, c2, null, null)
(c1, null, null, null)
(null, null, null, null)

Пример:
SELECT
  sum(amount),
  p.payment_type_id,
  c.country_id,
  p.city_id
FROM payment AS p
  INNER JOIN city AS c
    ON p.city_id = c.id
GROUP BY ROLLUP(p.payment_type_id, c.country_id, p.city_id);

Результат запроса
sum payment_type_id country_id city_id
854113.25 1 1 1
813482.72 1 1 2
704315.59 1 1 3
995287.51 1 1 4
687220.65 1 1 5
840649.32 1 1 6
804611.11 1 1 7
735551.13 1 1 8
6435231.28 1 1 NULL
835859.93 1 2 9
863170.68 1 2 10
967354.92 1 2 11
871351.7 1 2 12
918403.4 1 2 13
926686.93 1 2 14
793447.42 1 2 15
740564.31 1 2 16
770915.56 1 2 17
742079.14 1 2 18
775298.49 1 2 19
709777.38 1 2 20
9914909.86 1 2 NULL
16350141.14 1 NULL NULL
784598.28 2 1 1
841380.29 2 1 2
822958.09 2 1 3
794155.68 2 1 4
823906.68 2 1 5
845149.42 2 1 6
789795.7 2 1 7
814318.97 2 1 8
6516263.11 2 1 NULL
961163.91 2 2 9
980209.79 2 2 10
826505.29 2 2 11
816144.62 2 2 12
768140.92 2 2 13
943725.82 2 2 14
819090.61 2 2 15
749505.24 2 2 16
764506.39 2 2 17
823473.34 2 2 18
857523.5 2 2 19
775761.64 2 2 20
10085751.07 2 2 NULL
16602014.18 2 NULL NULL
782228.19 3 1 1
956924.5 3 1 2
830296.86 3 1 3
1007028.29 3 1 4
816460.78 3 1 5
877902.95 3 1 6
848247.57 3 1 7
723538.4 3 1 8
6842627.54 3 1 NULL
712204.4 3 2 9
873391.3 3 2 10
951534.78 3 2 11
867225.02 3 2 12
839568.04 3 2 13
948295.59 3 2 14
825230.81 3 2 15
756414.13 3 2 16
849373.19 3 2 17
872296.57 3 2 18
838054.08 3 2 19
804177.73 3 2 20
10137765.64 3 2 NULL
16980393.18 3 NULL NULL
49932548.5 NULL NULL NULL


В заключение хочу сказать, что кроме удобства написания эти запросы потенциально (сам не мерил еще) работают быстрее, так как для выполнения запроса нужен всего одно сканирование таблицы вместо нескольких, которые выполняются в случае UNION ALL.

Спасибо за внимание!
Tags:
Hubs:
Total votes 32: ↑32 and ↓0+32
Comments1

Articles