《Pro Oracle SQL》Chapter7--7.9GROUPING SETS and ROLLUP()

 7.9 GROUPING SETS and ROLLUP()       (page 214)
    There is yet another method that may be used to obtain the results seen in the previous two examples.  The GROUPING SETS() extension to GROUP BY made its debut with Oracle 9i.  The entire GROUP BY … HAVING clause of the previous example can be replaced with GROUP BY GROUPING SETS().  However, just because you can do something doesn’t mean you should.  Let’s look at example to understand just why you may not want to use GROUPING SETS(). Lines 31–33 in Listing 7-16 can be replaced by lines 31–36 in Listing 7-18.
    还有其他的方法可以获得之前两个例子的结果。GROUPING SETS()函数对GROUP BY的扩展,在ORALCE 9i时初次登场 。之前例子中的整个GROUP BY...HAVING子句可以用GROUP BY GROUPING SETS()替换。然而,你可以这样做并不意味着你必须这样做。让我们看一个例子来理解为什么你可能不愿意使用GROUPING SETS()。列表7-16的行31-33可以用列表7-18的31-36行替换。

Listing 7-18. GROUPING SETS()
  1  with tsales as (
  2  select /*+ gather_plan_statistics */
  3      s.quantity_sold
  4      , s.amount_sold
  5      , to_char(mod(cust_year_of_birth,10) * 10 ) || '-' ||
  6        to_char((mod(cust_year_of_birth,10) * 10 ) + 10) age_range
  7      , nvl(c.cust_income_level,'A: Below 30,000') cust_income_level
  8      , p.prod_name
  9      , p.prod_desc
 10      , p.prod_category 
 11      , (pf.unit_cost * s.quantity_sold)  total_cost
 12      , s.amount_sold - (pf.unit_cost * s.quantity_sold)  profit
 13    from sh.sales s
 14    join sh.customers c on c.cust_id = s.cust_id
 15    join sh.products p on p.prod_id = s.prod_id
 16    join sh.times t on t.time_id = s.time_id
 17    join sh.costs pf on
 18      pf.channel_id = s.channel_id
 19      and pf.prod_id = s.prod_id
 20      and pf.promo_id = s.promo_id
 21      and pf.time_id = s.time_id
 22    where  (t.fiscal_year = 2001)
 23  )
 24  select
 25    'Q' || to_char(grouping_id(cust_income_level,age_range)+1) query_tag
 26    , prod_category
 27    , decode(grouping(cust_income_level),1,'ALL INCOME',cust_income_level)
       cust_income_level
 28    , decode(grouping(age_range),1,'ALL AGE',age_range) age_range
 29    , sum(profit) profit
 30  from tsales
 31  group by prod_category, grouping sets(
 32    rollup(prod_category), -- sub total by product category
 33    (cust_income_level),  -- aggregate by category and income levels only
 34    (age_range),          -- aggregate by category and age only
 35    (cust_income_level,age_range) -- aggregates by category, all age and income
 36  )
 37  --having group_id() < 1
 38 order by prod_category, profit;
 QUERY                                                      AGE
TAG    PRODUCT CATEGORY               INCOME LEVEL         RANGE             PROFIT
------ ----------------------      --------------------    -------- ---------------
...
Q2     Software/Other                 E: 90,000 - 109,999     ALL AGE      $124,416.04
Q2     Software/Other                 F: 110,000 - 129,999    ALL AGE      $169,482.11
Q4     Software/Other                 ALL INCOME                ALL AGE      $873,603.25
Q4     Software/Other                 ALL INCOME                ALL AGE      $873,603.25
 
756 rows selected. 

    The output shown in Listing 7-18 is similar to that seen when the SQL from Listing 7-16 is executed
with all of the output categories enabled.  That is a major difference between using GROUP BY CUBE
HAVING GROUPING_ID() and GROUP BY GROUPING SETS.  The former may be used to easily modify the
output simply by setting variables to the correct values, while output from the latter cannot be modified except by modifying or dynamically generating the SQL.  Modifying the SQL means there will be more code to maintain and more resources consumed in the database. Dynamically generating the SQL is, well, usually just not a good idea if it can be avoided: it will consume more database resources, and it is much harder to troubleshoot when problems arise.

    在列表7-18中展示的输出结果相似于列表7-16的SQL,其所有输出(开关)目录置有效(那是一段SQL plus脚本),的执行结果。这就是使用GROUP BY CUBE HAVING GROUPING_ID() 和GROUP BY GROUPING SETS的主要不同。前者可能用起来容易修改输出,只需简单的设定变量成正确值。而后者则不能修改,除非修改或者动态的生成SQL。修改SQL意味着有更多的代码维护和消耗更多的数据库资源。动态的生成SQL,同样,通常不是一个好主意如果能避免的话:它将消耗更多的数据库资源,且它较难定位问题。
     As mentioned previously, the output in Listing 7-18 is similar to that in Listing 7-16, but not the
same.  The last two lines of the output shown are duplicates.  Sometimes the GROUPING_SETS() extension can cause duplicates to appear in the output. In this case, the duplicates are caused by the ROLLUP(PROD_CATEGORY) line.  You can prove that to yourself by removing ROLLUP() from the code in Listing 7-18 and rerunning it. The duplicate lines will no longer appear. However, the totals for each
Product Category will no longer appear either.  The solution is to use the GROUP_ID() function to identify those duplicate rows, and insert it into a HAVING clause.
    如前面所述,列表7-18的输出相似于列表7-16,但不是一样的。输出中的最后两行是重复的。有些时候GROUPING_SETS() 扩展式会产生输出重复。在本例中,重复是由ROLLUP(PROD_CATEGORY)行导致的。你可以验证去除列表7-18代码中的ROLLUP()然后重新运行它。重复的行将不再出现。然而,每一产品目录(Product Category)的总计也不再出现。解决的办法是用GROUP_ID()函数去标识这些重复行,然后把它插入HAVING子句中去。
    The HAVING clause can be seen commented out in Listing 7-18.  If you uncomment it and then re-run the script, the output will appear as expected without the duplicate rows.  Interestingly, if the ROLLUP(PROD_CATEGORY) line is replaced with (NULL), the HAVING clause can be removed, and the output will appear as expected.
    在列表7-18中可见HAVING子句被注释掉了。如果你去掉注释,然后重新运行脚本,输出将不会再出现重复行。有趣的是,如果ROLLUP(PROD_CATEGORY)行被替换成NULL,HAVING子句也能去除,且输出想要的结果。
    The ROLLUP() extension to GROUP BY can also be used by itself to create running subtotals that would otherwise require multiple queries joined by UNION ALL.  Suppose that someone from the Sales Department asked you to create a report showing totals of all purchases by customers whose last name begins with “Sul.”  In addition, there need to be subtotals for each year by customer, each product category by customer, and a grand total of all sales.   This kind of task is easily handled by ROLLUP().  Listing 7-19 shows one way to write a query to satisfy that request.
    GROUP BY的ROLLUP()扩展式本身可用来创建累加中的小计,否则就要使用UNION ALL连接多次查询。 设想销售部门的某人要你创建一报告展示所有姓以"Sul"开头的顾客的所有购买值的总计。另外,还要顾客每年的小计,顾客各产品种类,以及所有销售额的总计。 这种任务使用ROLLUP()就容易做到。列表7-19展示了写一个查询满足需求的方法。
    Notice that the DECODE() and GROUPING() functions are again used to indicate subtotal rows. Also, the grand total is forced to appear at the end of the report by the use of GROUPING(M.CUST_NAME).   As the only time this value will be > 0 is when the total for all customers is calculated, the grand total appears at the end of the report as expected. 
    注意又使用了DECODE()和GROUPING()函数指出小计行。同样,使用GROUPING(M.CUST_NAME)将总计行强制放到了报告的末尾。 因为该值仅有一次会大于0,就是当所有客户的总计计算的时候,所以总计如期出现在了报告的末端。
Listing 7-19.  ROLLUP() Subtotals
  1  with mysales as (
  2    select
  3      c.cust_last_name ||',' || c.cust_first_name cust_name
  4      , p.prod_category
  5      , to_char(trunc(time_id,'YYYY'),'YYYY') sale_year
  6      , p.prod_name
  7      , s.amount_sold
  8    from sh.sales s
  9    join sh.products p on p.prod_id = s.prod_id
 10    join sh.customers c on c.cust_id = s.cust_id
 11    where c.cust_last_name like 'Sul%'
 12    --where s.time_id = to_date('01/01/2001','mm/dd/yyyy')
 13  )
 14  select
 15    decode(grouping(m.cust_name),1,'GRAND TOTAL',m.cust_name) cust_name
 16    , decode(grouping(m.sale_year),1,'TOTAL BY YEAR',m.sale_year) sale_year
 17    , decode(grouping(m.prod_category),1,'TOTAL BY CATEGORY',m.prod_category)
prod_category
 18    , sum(m.amount_sold) amount_sold
 19  from mysales m
 20  group by rollup(m.cust_name, m.prod_category, m.sale_year)
 21  order by grouping(m.cust_name), 1,2,3;
CUSTOMER                       SALE_YEAR     PRODUCT CATEGORY                     AMT SOLD
------------------------------ ------------- ------------------------------ --------------

Sullivan,Rue                   1998          Peripherals and Accessories              $259.90
Sullivan,Rue                   1998          Software/Other                              $19.59
Sullivan,Rue                   2000          Electronics                                    $2,213.30
Sullivan,Rue                   2000          Hardware                                      $1,359.06
Sullivan,Rue                   2000          Peripherals and Accessories              $1,169.94
Sullivan,Rue                   2000          Photo                                           $331.33
Sullivan,Rue                   2000          Software/Other                              $933.87
Sullivan,Rue                   TOTAL BY YEAR Electronics                               $2,213.30
Sullivan,Rue                   TOTAL BY YEAR Hardware                                 $1,359.06
Sullivan,Rue                   TOTAL BY YEAR Peripherals and Accessories         $1,429.84
Sullivan,Rue                   TOTAL BY YEAR Photo                                      $331.33
Sullivan,Rue                   TOTAL BY YEAR Software/Other                         $953.46
Sullivan,Rue                   TOTAL BY YEAR TOTAL BY CATEGORY                 $6,286.99
GRAND TOTAL                 TOTAL BY YEAR TOTAL BY CATEGORY                $86,994.89
 
68 rows selected. 

猜你喜欢

转载自caohong286.iteye.com/blog/1315268