文章目錄
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