文章目录
GROUPING SETS
第一题:
题目链接🔗GROUPING SETS
首先看题:
I have a table of customers:
create table qz_customers (
cust_id integer primary key
, segment varchar2(10)
, category varchar2(10)
, country varchar2(10)
)
/
insert into qz_customers values (1, 'Medium', 'Commerce', 'Germany')
/
insert into qz_customers values (2, 'Medium', 'Private' , 'Denmark')
/
insert into qz_customers values (3, 'Medium', 'Commerce', 'Germany')
/
insert into qz_customers values (4, 'Large' , 'Commerce', 'Germany')
/
insert into qz_customers values (5, 'Large' , 'Commerce', 'Holland')
/
insert into qz_customers values (6, 'Small' , 'Commerce', 'Germany')
/
insert into qz_customers values (7, 'Small' , 'Private' , 'Denmark')
/
insert into qz_customers values (8, 'Small' , 'Private' , 'Denmark')
/
insert into qz_customers values (9, 'Small' , 'Commerce', 'Holland')
/
commit
/
The table specifies:
which segment (size) the customer belongs to
which category (private or commercial) the customer belongs to
which country the customer resides in
I would like a list of how many customers we have, grouped by:
segment and category
segment
country
grand total
For that purpose I have this incomplete query:
select case grouping(c.segment)
when 1 then 'Total'
else c.segment
end as segment
, case grouping(c.category)
when 1 then 'Total'
else c.category
end as category
, case grouping(c.country)
when 1 then 'Total'
else c.country
end as country
, count(*) as customers
from qz_customers c
##REPLACE##
order by grouping(c.segment) , c.segment
, grouping(c.category), c.category
, grouping(c.country) , c.country
/
Which of the choices contain code that can replace ##REPLACE## to make the query return this desired output:
SEGMENT CATEGORY COUNTRY CUSTOMERS
---------- ---------- ---------- ----------
Large Commerce Total 2
Large Total Total 2
Medium Commerce Total 2
Medium Private Total 1
Medium Total Total 3
Small Commerce Total 2
Small Private Total 2
Small Total Total 4
Total Total Denmark 3
Total Total Germany 4
Total Total Holland 2
Total Total Total 9
题目要求主要看这句:
I would like a list of how many customers we have, grouped by:
segment and category
segment
country
grand total
选项一
group by cube(c.segment, c.category, c.country)
having grouping_id(c.segment, c.category, c.country) in (1,3,6,7)
这里是给出的解析:
GROUP BY CUBE creates all 8 sub- and grand-total combinations of the three columns. Then the HAVING clause filters, so we only keep the 4 that we want. GROUPING_ID is a bit-pattern where ‘1’ in the bit pattern is the value of the GROUPING() function for that column. So “001” is where segment and category have values but country is total, or in other words 1 is subtotals at segment/category level. “110” is where segment and category are total but country has values, or in other words 6 is subtotals at country level. And so on.
CUBE就是把GROUP BY字句中聚合键的“所有可能的组合”的汇总结果集中到一个结果中,这里有三个字段所以有8种统计方式,但是根据题目中的要求,只需统计四种;
GROUPING_ID中的1代表GROUPING函数中这一行的值(为合计值时为1);
所以001(即1)表示segment 和 category 有值 而 country 是合计值;110(即6)表示segment 和 category 有合计值 而 country 是值。
看图更加直观:
选项二
group by rollup(c.segment, c.category, c.country)
解析:
This rollup creates subtotal groupings for segment/category/country, segment/category, segment, and grand total.
直接roullup三个字段会得到 segment/category/country, segment/category, segment, and grand total
,不符合要求。
选项三
group by grouping sets (
(c.segment, c.category)
, (c.segment)
, (c.country)
, ()
)
解析:
Using GROUPING SETS we can explicitly specify which combinations we want, rather than using CUBE or ROLLUP. This is the specification for the 4 different combinations we want. The empty set of parentheses means grand total.
最后一句: “()”代表了grand total
加上(c.segment, c.category), (c.segment), (c.country)
,符合题目要求
选项四
group by grouping sets (
rollup(c.segment, c.category)
, (c.country)
, ()
)
解析:
ROLLUP can be used within GROUPING SETS. ROLLUP here creates the sets (c.segment, c.category), (c.segment) and (). As we also manually have a (), we get a wrong output with two grand totals
选项五
group by grouping sets (
rollup(c.segment, c.category)
, rollup(c.country)
)
解析:
The first ROLLUP creates the sets (c.segment, c.category), (c.segment) and (). Then the second ROLLUP creates the sets (c.country) and (). So again we have two grand totals in a wrong output like the previous choice.
四五都是一样的错误,由于rollup()
会产生“()”,即grand total
,输出会重复两个grand total
选项六
group by grouping sets (
rollup(c.segment, c.category)
, (c.country)
)
所以六才是对的
选项七
group by grouping sets (
(c.segment, c.category)
, (c.segment)
, rollup(c.country)
)
解析:
The ROLLUP creates the sets (c.country) and (). Then we manually specify the other two sets and end up with the same 4 correct sets as choice 3 and the previous choice.
第二题:
题目链接🔗GROUPING SETS
首先看题:
I create and populate the following table:
CREATE TABLE plch_sales
(
product VARCHAR2(10)
, country VARCHAR2(10)
, year NUMBER
, sales NUMBER
)
/
BEGIN
INSERT INTO plch_sales VALUES ('BANANA', 'US', 2009, 200);
INSERT INTO plch_sales VALUES ('BANANA', 'US', 2010, 300);
INSERT INTO plch_sales VALUES ('BANANA', 'GB', 2009, 400);
INSERT INTO plch_sales VALUES ('BANANA', 'GB', 2010, 350);
INSERT INTO plch_sales VALUES ('BANANA', 'DK', 2010, 250);
INSERT INTO plch_sales VALUES ('APPLE' , 'US', 2009, 100);
INSERT INTO plch_sales VALUES ('APPLE' , 'GB', 2009, 150);
INSERT INTO plch_sales VALUES ('APPLE' , 'GB', 2010, 150);
INSERT INTO plch_sales VALUES ('APPLE' , 'DK', 2009, 250);
INSERT INTO plch_sales VALUES ('APPLE' , 'DK', 2010, 250);
INSERT INTO plch_sales VALUES ('PEAR' , 'GB', 2010, 150);
INSERT INTO plch_sales VALUES ('PEAR' , 'DK', 2009, 300);
INSERT INTO plch_sales VALUES ('PEAR' , 'DK', 2010, 350);
COMMIT;
END;
/
My boss wants sales totals listed by product, by country and a grand total.
Which of the choices produce this desired output:
TOTAL PRODUCT COUNTRY SALES
------- ---------- ---------- ----------
Product APPLE TOTAL 900
Product BANANA TOTAL 1500
Product PEAR TOTAL 800
Country TOTAL DK 1400
Country TOTAL GB 1200
Country TOTAL US 600
Grand TOTAL TOTAL 3200
重点是这句:
My boss wants sales totals listed by product, by country and a grand total.
即boss要求根据 product、country、总计 进行分组合计。
Choice 1 ×
SELECT 'Product' total
, CASE GROUPING(s.product)
WHEN 1 THEN 'TOTAL'
ELSE s.product
END product
, 'TOTAL' country
, SUM(s.sales) sales
FROM plch_sales s
GROUP BY s.product
UNION ALL
SELECT 'Country' total
, 'TOTAL' product
, CASE GROUPING(s.country)
WHEN 1 THEN 'TOTAL'
ELSE s.country
END country
, SUM(s.sales) sales
FROM plch_sales s
GROUP BY s.country
UNION ALL
SELECT 'Grand' total
, 'TOTAL' product
, 'TOTAL' country
, SUM(s.sales) sales
FROM plch_sales s
ORDER BY product, country;
- This creates all the required sub- and grandtotals as required in a do-it-yourself implementation of GROUPING SETS.
- But the ORDER BY orders the result by the column ALIASES product and country - therefore the grand total comes before the US country subtotal. Beware ordering by column aliases with the same name as the columns.
- aliases:n. 别名(alias的复数)
ORDER BY product, country
这里的排序是union后的表格进行统一排序,所以会将将’total’和‘US’比较,'total’排名反而靠前,不符合题意。
Choice 2 √
SELECT * FROM (
SELECT 'Product' total
, CASE GROUPING(s.product)
WHEN 1 THEN 'TOTAL'
ELSE s.product
END product
, 'TOTAL' country
, SUM(s.sales) sales
FROM plch_sales s
GROUP BY s.product
ORDER BY s.product
)
UNION ALL
SELECT * FROM (
SELECT 'Country' total
, 'TOTAL' product
, CASE GROUPING(s.country)
WHEN 1 THEN 'TOTAL'
ELSE s.country
END country
, SUM(s.sales) sales
FROM plch_sales s
GROUP BY s.country
ORDER BY s.country
)
UNION ALL
SELECT 'Grand' total
, 'TOTAL' product
, 'TOTAL' country
, SUM(s.sales) sales
FROM plch_sales s;
-
This is a do-it-yourself implementation of GROUPING SETS making three sets of sums and doing UNION ALL. Note the ORDER BY has to be in each part to make this work (or each part would have had to select something to use in an outer ORDER BY.)
-
与上一选项不同的是,这里是将查询结果分开排序后再 UNION ALL
-
Warning: This relies on UNION ALL preserving the row order of the subqueries. This does work (as the optimizer will not do unnecessary sorting after the UNION ALL) but it is undocumented. As such there is a (slight) possibility it might change in future releases.
-
这里的结果顺序取决于UNION ALL 之前的排序
An alternative(可替代的) answer would be this:
SELECT s2.total
, s2.product
, s2.country
, s2.sales
FROM (
SELECT 1 subselect
, GROUPING(s.product) subtotal
, 'Product' total
, CASE GROUPING(s.product)
WHEN 1 THEN 'TOTAL'
ELSE s.product
END product
, 'TOTAL' country
, SUM(s.sales) sales
FROM plch_sales s
GROUP BY s.product
UNION ALL
SELECT 2 subselect
, GROUPING(s.country) subtotal
, 'Country' total
, 'TOTAL' product
, CASE GROUPING(s.country)
WHEN 1 THEN 'TOTAL'
ELSE s.country
END country
, SUM(s.sales) sales
FROM plch_sales s
GROUP BY s.country
UNION ALL
SELECT 3 subselect
, 1 subtotal
, 'Grand' total
, 'TOTAL' product
, 'TOTAL' country
, SUM(s.sales) sales
FROM plch_sales s
) s2
ORDER BY s2.subselect
, s2.subtotal
, s2.product
, s2.country;
Choice 3 ×
SELECT CASE GROUPING_ID(s.product, s.country)
WHEN 1 THEN 'Product'
WHEN 2 THEN 'Country'
WHEN 3 THEN 'Grand'
END total
, CASE GROUPING(s.product)
WHEN 1 THEN 'TOTAL'
ELSE s.product
END product
, CASE GROUPING(s.country)
WHEN 1 THEN 'TOTAL'
ELSE s.country
END country
, SUM(s.sales) sales
FROM plch_sales s
GROUP BY ROLLUP(s.product, s.country)
ORDER BY s.product, s.country;
- ROLLUP(s.product, s.country) will create totals for (product,country) and (product) and grand total. It will not create the (country) total.
cube(A,B) :totals for (A)/(B)/(A,B)/grand total
rollup(A,B) :totals for (A,B)/(A)/grand total
Choice 4 √
SELECT CASE GROUPING_ID(s.product, s.country)
WHEN 1 THEN 'Product'
WHEN 2 THEN 'Country'
WHEN 3 THEN 'Grand'
END total
, CASE GROUPING(s.product)
WHEN 1 THEN 'TOTAL'
ELSE s.product
END product
, CASE GROUPING(s.country)
WHEN 1 THEN 'TOTAL'
ELSE s.country
END country
, SUM(s.sales) sales
FROM plch_sales s
GROUP BY GROUPING SETS(
(s.product),
(s.country),
()
)
ORDER BY s.product, s.country;
- GROUPING SETS allow you to easily define several combinations you wish to GROUP BY.
- It creates all three GROUP BY combinations and does a UNION ALL on the results.
- 题目一已经遇到过,不多说
Choice 5 √
SELECT CASE GROUPING_ID(s.product, s.country)
WHEN 1 THEN 'Product'
WHEN 2 THEN 'Country'
WHEN 3 THEN 'Grand'
END total
, CASE GROUPING(s.product)
WHEN 1 THEN 'TOTAL'
ELSE s.product
END product
, CASE GROUPING(s.country)
WHEN 1 THEN 'TOTAL'
ELSE s.country
END country
, SUM(s.sales) sales
FROM plch_sales s
GROUP BY CUBE(s.product, s.country)
HAVING GROUPING_ID(s.product, s.country) > 0
ORDER BY s.product, s.country;
- CUBE creates all 4 possible GROUP BY combinations. HAVING GROUPING_ID removes the (product,country) combination.
- 这四种情况分别是:(product,country)00、(country)10、(product)01、grand total 11;
GROUPING_ID > 0
排除了(product,country);符合题意。
Choice 6 ×
SELECT CASE GROUPING_ID(product, country)
WHEN 1 THEN 'Product'
WHEN 2 THEN 'Country'
WHEN 3 THEN 'Grand'
END total
, CASE GROUPING(product)
WHEN 1 THEN 'TOTAL'
ELSE product
END product
, CASE GROUPING(country)
WHEN 1 THEN 'TOTAL'
ELSE country
END country
, SUM(sales) sales
FROM plch_sales
GROUP BY GROUPING SETS(
(product),
(country),
()
)
ORDER BY product, country;
- This would be fine - except the ORDER BY clause will order by product/country aliases, thus country TOTAL comes before US.
- 与选项一一样的错误,排序后不符合题目要求
Background
- GROUP BY子句中的表达式可以包含FROM子句中的表,视图或实例化视图的任何列,无论这些列是否出现在select list中。
- GROUP BY子句对行进行分组后,排序需要自行 order by。
Explaination
GROUP BY GROUPING SETS(
(product),
(country),
()
)
-
In the GROUPING SETS clause three sets are defined here: (s.product), (s.country) and (), which actually creates a set of aggregates GROUP BY s.product, a set of aggregates GROUP BY s.country, and finally a set of aggregates with no GROUP BY (a grand total.)
-
GROUPING SETS is a generalization of ROLLUP and CUBE that also can be used to create several aggregations. ROLLUP is an easy shortcut for getting normal “subtotals” for a report, CUBE gets all combinations.
-
GROUP BY ROLLUP(s.product, s.country) is like doing GROUPING SETS ( (s.product,s.country), (s.product), () )
-
GROUP BY CUBE(s.product, s.country) is like doing GROUPING SETS ( (s.product,s.country), (s.product), (s.country), () )
-
To determine if s.product / s.country is null because the data is null or is null because it is a subtotal, the function GROUPING returns 1 if it is a subtotal, otherwise 0. GROUPING_ID is a bitmap of GROUPING - GROUPING_ID(s.product,s.country) is the same as 1GROUPING(s.product)+2GROUPING(s.country).
示例🔗
Oracle documentation example of GROUPING SETS:
http://download.oracle.com/docs/cd/E11882_01/server.112/e17118/statements_10002.htm#i2091446
Examples by Bob Watkins:
http://www.techrepublic.com/article/group-by-grouping-sets-for-custom-rollups-in-oracle/6134424
Tom Kyte on GROUPING_ID and GROUPING SETS:
http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:37355353762363