10.1.5部分CUBE
部分CUBE和部分ROLLUP类似,把不想要的小计和合计的列放到group by中,不放到cube中就可以了。比如:
SELECT r.name region, TO_CHAR(TO_DATE(o.month, 'MM'), 'Month') month,
SUM(o.tot_sales) sums
FROM all_orders o JOIN region r
ON r.region_id = o.region_id
GROUP BY r.name, CUBE(o.month);
REGION MONTH SUMS
---------------------------------------- ---------------- -------------------------------------------------
New England 19756923
New England 1月 1527645
New England 2月 1847238
New England 3月 1699449
New England 4月 1792866
New England 5月 1698855
New England 6月 1510062
New England 7月 1678002
New England 8月 1642968
New England 9月 1726767
New England 10月 1648944
New England 11月 1384185
New England 12月 1599942
Mid-Atlantic 18923298
Mid-Atlantic 1月 1832091
Mid-Atlantic 2月 1286028
Mid-Atlantic 3月 1911093
Mid-Atlantic 4月 1623438
Mid-Atlantic 5月 1778805
Mid-Atlantic 6月 1504455
Mid-Atlantic 7月 1820742
Mid-Atlantic 8月 1381560
Mid-Atlantic 9月 1178694
Mid-Atlantic 10月 1530351
Mid-Atlantic 11月 1598667
Mid-Atlantic 12月 1477374
Southeast US 20605485
Southeast US 1月 1137063
Southeast US 2月 1855269
Southeast US 3月 1967979
Southeast US 4月 1830051
Southeast US 5月 1983282
Southeast US 6月 1705716
Southeast US 7月 1670976
Southeast US 8月 1436295
Southeast US 9月 1905633
Southeast US 10月 1610523
Southeast US 11月 1661598
Southeast US 12月 1841100
从上面结果可以看出,当将区域名从cube中移到group by中,则总会按区域名统计,则结果中移除了按月小计和总计的结果。我们可以发现,如果cube中只有一个列,那么和rollup的结果一致,也就是上面的语句等价于:
SELECT r.name region, TO_CHAR(TO_DATE(o.month, 'MM'), 'Month') month,
SUM(o.tot_sales) sums
FROM all_orders o JOIN region r
ON r.region_id = o.region_id
GROUP BY r.name, rollup(o.month);
注:
对上面的rollup和cube的小计或总计中,有的列是null的表示按此列小计,如果前面的非汇总列都是null则表示总计。在实际应用中不可能把这些null给别人看,因为别人不知道你是用rollup或cube计算的。这时候可能会用到nvl或其它的转换,详细请看下节grouing函数。
10.1.6 GROUPING函数
在实际应用中,使用rollup或cube可以统计小计和合计的值,那么在小计和合计中会出现列的值为NULL的情况,客户就不知道什么意思了。为了增强客户的可读性,我们可能会想到使用NVL函数,如下:
SELECT NVL(TO_CHAR(o.year), 'All Years') year,
NVL(TO_CHAR(TO_DATE(o.month, 'MM'), 'Month'), 'First Quarter') month,
NVL(r.name, 'All Regions') region, SUM(o.tot_sales) sums
FROM all_orders o JOIN region r
ON r.region_id = o.region_id
WHERE o.month BETWEEN 1 AND 3
GROUP BY ROLLUP (o.year, o.month, r.name);
YEAR MONTH REGION SUMS
---------- -------------------- -------------------- -----------------------------------------------------
2000 1月 New England 1018430
2000 1月 Mid-Atlantic 1221394
2000 1月 Southeast US 758042
2000 1月 All Regions 2997866
2000 2月 New England 1231492
2000 2月 Mid-Atlantic 857352
2000 2月 Southeast US 1236846
2000 2月 All Regions 3325690
2000 3月 New England 1132966
2000 3月 Mid-Atlantic 1274062
2000 3月 Southeast US 1311986
2000 3月 All Regions 3719014
2000 First Quarter All Regions 10042570
2001 1月 New England 509215
2001 1月 Mid-Atlantic 610697
2001 1月 Southeast US 379021
2001 1月 All Regions 1498933
2001 2月 New England 615746
2001 2月 Mid-Atlantic 428676
2001 2月 Southeast US 618423
2001 2月 All Regions 1662845
2001 3月 New England 566483
2001 3月 Mid-Atlantic 637031
2001 3月 Southeast US 655993
2001 3月 All Regions 1859507
2001 First Quarter All Regions 5021285
All Years First Quarter All Regions 15063855
可以看出,原来是NULL的都被有意义的值代替,增加了用户可读性。使用NVL函数对这个例子来说是非常适合的,但是另外一个情况我们要考虑一下,如果这些列中本身就有NULL值的存在,那么我们使用NVL函数之后,到底是小计还是本身的值呢???带着这个问题,我们来看一个例子:
SELECT * FROM disputed_orders;
ORDER_NBR CUST_NBR SALES_EMP_ID SALE_PRICE ORDER_DT EXPECTED_SHIP_DT STATUS
--------- -------- ------------ ---------- ----------- -------------- ---------
1001 1 7354 99 22-jul-2001 23-jul-2001 DELIVERED
1000 1 7354 19-jul-2001 24-jul-2001
1002 5 7368 12-jul-2001 25-jul-2001
1003 4 7654 56 16-jul-2001 26-jul-2001 DELIVERED
1004 4 7654 34 18-jul-2001 27-jul-2001 PENDING
1005 8 7654 99 22-jul-2001 24-jul-2001 DELIVERED
1006 1 7354 22-jul-2001 28-jul-2001
1007 5 7368 25 20-jul-2001 22-jul-2001 PENDING
1008 5 7368 25 21-jul-2001 23-jul-2001 PENDING
1009 1 7354 56 18-jul-2001 22-jul-2001 DELIVERED
1012 1 7354 99 22-jul-2001 23-jul-2001 DELIVERED
1011 1 7354 19-jul-2001 24-jul-2001
1015 5 7368 12-jul-2001 25-jul-2001
1017 4 7654 56 16-jul-2001 26-jul-2001 DELIVERED
1019 4 7654 34 18-jul-2001 27-jul-2001 PENDING
1021 8 7654 99 22-jul-2001 24-jul-2001 DELIVERED
1023 1 7354 22-jul-2001 28-jul-2001
1025 5 7368 25 20-jul-2001 22-jul-2001 PENDING
1027 5 7368 25 21-jul-2001 23-jul-2001 PENDING
1029 1 7354 56 18-jul-2001 22-jul-2001 DELIVERED
可以看到,有的status值为NULL。那么现在我们需要汇总每个customer对应状态的数目以及所有customer每个状态的数目最后合计。使用NVL函数如下:
SELECT NVL(TO_CHAR(cust_nbr), 'All Customers') customer,
NVL(status, 'All Status') status,
COUNT(*) FROM disputed_orders
GROUP BY CUBE(cust_nbr, status);
CUSTOMER STATUS COUNT(*)
-------------------- -------------------- ----------
All Customers All Status 6
All Customers All Status 20
All Customers PENDING 6
All Customers DELIVERED 8
1 All Status 4
1 All Status 8
1 DELIVERED 4
4 All Status 4
4 PENDING 2
4 DELIVERED 2
5 All Status 2
5 All Status 6
5 PENDING 4
8 All Status 2
8 DELIVERED 2
从上面的结果可以看出,这种使用NVL函数没有任何意义,得出的结果混淆,比如黄色部分,对用户1有两个all status,其实其中一个是本身表中的status为null,因此统计混乱。
Oracle为了解决这个问题,专门有一个grouping函数。grouping函数解释如下:
1. grouping函数只有一个参数,接受来自于group by,rollup,cube,grouping sets的列。
2. grouping函数对rollup,cube的汇总列(小计或合计行的列值)置为1,其他的非汇总列置为0,如在单独的group by中使用肯定全为0,因为没有通过rollup或cube的小计或合计行。
3. grouping函数可以有效地避免nvl函数带来的小计或合计与本身表中有null带来的混淆问题。
4. 使用方式为:
SELECT . . . [GROUPING(grouping_column_name)] . . .
FROM . . .
GROUP BY . . . {ROLLUP | CUBE} (grouping_column_name)
下面用grouping函数改写上面的的例子如下:
SELECT decode(grouping(cust_nbr),1,'ALL CUSTOMER',cust_nbr) customer,
decode(grouping(status),1,'ALL STATUS',nvl(status,'原始值为空')) status,
COUNT(*) FROM disputed_orders
GROUP BY CUBE(cust_nbr, status);
CUSTOMER STATUS COUNT(*)
-------------------- -------------------- ----------
ALL CUSTOMER 原始值为空 6
ALL CUSTOMER ALL STATUS 20
ALL CUSTOMER PENDING 6
ALL CUSTOMER DELIVERED 8
1 原始值为空 4
1 ALL STATUS 8
1 DELIVERED 4
4 ALL STATUS 4
4 PENDING 2
4 DELIVERED 2
5 原始值为空 2
5 ALL STATUS 6
5 PENDING 4
8 ALL STATUS 2
8 DELIVERED 2
使用decode和grouping函数,避免了null的问题,现在黄色部分对status为null的也统计了。汇总和status为null的不再混淆。
再看一个例子:
SELECT DECODE(GROUPING(o.year), 1, 'All Years', o.year) Year,
DECODE(GROUPING(o.month), 1, 'All Months',
TO_CHAR(TO_DATE(o.month, 'MM'), 'Month')) Month,
DECODE(GROUPING(r.name), 1, 'All Regions', r.name) Region, SUM(o.tot_sales) sums
FROM all_orders o JOIN region r
ON r.region_id = o.region_id
WHERE o.month BETWEEN 1 AND 3
GROUP BY ROLLUP (o.year, o.month, r.name);
YEAR MONTH REGION SUMS
---------- -------------------- -------------------- ----------
2000 January New England 1018430
2000 January Mid-Atlantic 1221394
2000 January Southeast US 758042
2000 January All Regions 2997866
2000 February New England 1231492
2000 February Mid-Atlantic 857352
2000 February Southeast US 1236846
2000 February All Regions 3325690
2000 March New England 1132966
2000 March Mid-Atlantic 1274062
2000 March Southeast US 1311986
2000 March All Regions 3719014
2000 All Months All Regions 10042570
2001 January New England 509215
2001 January Mid-Atlantic 610697
2001 January Southeast US 379021
2001 January All Regions 1498933
2001 February New England 615746
2001 February Mid-Atlantic 428676
2001 February Southeast US 618423
2001 February All Regions 1662845
2001 March New England 566483
2001 March Mid-Atlantic 637031
2001 March Southeast US 655993
2001 March All Regions 1859507
2001 All Months All Regions 5021285
All Years All Months All Regions 15063855
使用deocode(当然也可以用case,没有decode简单)联合grouping函数,使报表的小计和合计列描述有意义,避免原始数据有null而使用nvl函数带来混淆的问题。