一例“詭異”報表SQL需求分析

realkid4發表於2013-07-07

我們在實際的工作中,經常會遇到各式各樣的報表結構和需求。讓報表能夠生成、高效的生成取決於多種因素,包括業務需求的平衡折中、支援架構設計和詳細資料庫設計權衡等。最直接我們遇到的問題,就是SQL語句的實現。本篇介紹一個實際的案例(經過修改),希望能夠為將來有需要的朋友提供綿薄之力。

 

1、問題概述和需求

 

我們依然選在Oracle11gR2進行試驗。

 

 

SQL> select * from v$version;

 

BANNER

--------------------------------------------------------------------------------

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production

PL/SQL Release 11.2.0.1.0 - Production

CORE        11.2.0.1.0         Production

TNS for Linux: Version 11.2.0.1.0 - Production

NLSRTL Version 11.2.0.1.0 - Production

 

 

資料表結構涉及到三個表內容:t_mastert_child1t_child2。兩個child表是t_master表的子表結構,透過外來鍵連結。T_child1t_child2之間沒有數量關係,m*n

 

 

SQL> create table t_master(id number, mas_name varchar2(10));

Table created

 

SQL> alter table t_master add constraint pk_t_master primary key (id);

Table altered

 

SQL> create table t_child1 (id number, mid number, child1_name  varchar2(10));

Table created

 

SQL> alter table t_child1 add constraint pk_t_child1 primary key (id);

Table altered

 

SQL> alter table t_child1 add constraint fk_child1_master foreign key (mid) references  t_master(id);

Table altered

 

 

SQL> create table t_child2 (id number, mid number, child2_name varchar2(10));

Table created

 

SQL> alter table t_child2 add constraint pk_child2 primary key (id);

Table altered

 

SQL> alter table t_child2 add constraint fk_child2_master foreign key (mid) references t_master(id);

Table altered

 

 

資料表內容描述。

 

 

SQL> select * from t_master;

        ID MAS_NAME

---------- ----------

         1 1_name

         2 2_name

         3 3_name

         4 4_name

         5 5_name

         6 6_name

 

6 rows selected

 

SQL> select * from t_child2;

        ID        MID CHILD2_NAME

---------- ---------- -----------

         1          1 1

         2          1 123

         3          1 123

         4          2 df

         5          2 dff

         6          3 fse

 

6 rows selected

 

 

SQL> select * from t_child1 order by mid;

        ID        MID CHILD1_NAME

---------- ---------- -----------

         1          1 kll

         3          1 dfell

         2          2 dfkll

         4          3 fwe

         5          4 fwe

         6          5 few

         7          5 fewd

 

7 rows selected

 

兩個子表透過id列進行連線,同一個t_mater對一個的子表取值數量是不一定的。那麼,報表需求是什麼呢?

 

 

        ID MAS_NAME   CHILD1_NAME CHILD2_NAME

---------- ---------- ----------- -----------

         1 1_name     kll         1

         1 1_name     dfell       123

         1 1_name                 123

         2 2_name     dfkll       df

         2 2_name                 dff

         3 3_name     fwe         fse

         4 4_name     fwe        

         5 5_name     few        

         5 5_name     fewd       

         6 6_name                

 

 

兩個子表的列child1child2以列的方式作為結果集合。但是最大難點在於列的數量,同一個t_master的取值主鍵對應的資料行數,是透過子錶行數最大的一個進行確認。例如:t_masterid=1對應child1child2的個數分別為2條和3條。結果要顯示成3行,並且child1對應的位置要顯示為空。

 

2、抽絲剝繭、層層遞進

 

我們最常見處理複雜SQL的方法就是抽絲剝繭、層層遞進,不斷靠近我們的目標集合。

 

首先,我們一般意義上將三個表連線起來的方法,不是很好用。

 

SQL> select m.id, m.mas_name, c1.child1_name, c2.child2_name

  2  from t_master m,

  3       t_child1 c1,

  4       t_child2 c2

  5  where m.id=c1.mid

  6    and m.id=c2.mid;

 

        ID MAS_NAME   CHILD1_NAME CHILD2_NAME

---------- ---------- ----------- -----------

         1 1_name     kll         123

         1 1_name     kll         123

         1 1_name     kll         1

         2 2_name     dfkll       dff

         2 2_name     dfkll       df

         1 1_name     dfell       123

         1 1_name     dfell       123

         1 1_name     dfell       1

         3 3_name     fwe         fse

 

9 rows selected

 

 

 

SQL> select m.id, m.mas_name, c1.child1_name, c2.child2_name

  2  from t_master m

  3  left join t_child1 c1

  4  on c1.mid=m.id

  5  left join t_child2 c2

  6  on c2.mid=m.id;

 

        ID MAS_NAME   CHILD1_NAME CHILD2_NAME

---------- ---------- ----------- -----------

         1 1_name     kll         123

         1 1_name     kll         123

         1 1_name     kll         1

         2 2_name     dfkll       dff

         2 2_name     dfkll       df

         1 1_name     dfell       123

         1 1_name     dfell       123

         1 1_name     dfell       1

         3 3_name     fwe         fse

         4 4_name     fwe        

         5 5_name     few        

         5 5_name     fewd       

         6 6_name                

 

13 rows selected

 

 

兩種結構都有問題。第一種結構在於沒有將不包括子記錄的t_master記錄顯示出來。而第二種結構的問題在於一個對應id值,只能有一個t_child1t_child2記錄,不能重合。

 

那麼,怎麼辦呢?

 

從結果集合的情況看,單次t_mastert_child1/t_child2進行關聯,可以構成一半近似的結果集合。

 

 

SQL> select m.id, m.mas_name, c1.child1_name

  2  from t_master m

  3  left join t_child1 c1

  4  on m.id=c1.mid;

 

        ID MAS_NAME   CHILD1_NAME

---------- ---------- -----------

         1 1_name     kll

         1 1_name     dfell

         2 2_name     dfkll

         3 3_name     fwe

         4 4_name     fwe

         5 5_name     few

         5 5_name     fewd

         6 6_name    

 

8 rows selected

 

 

如果單獨構建結果集合,並且合併起來,看來可以得到類似的結果。

 

 

SQL> select *

  2  from

  3  (select m.id, m.mas_name, c1.child1_name, rownum nm

  4   from t_master m

  5   left join t_child1 c1

  6   on m.id=c1.mid

  7   where m.id=1) part1,

  8  (select m.id, m.mas_name, c2.child2_name, rownum nm

  9   from t_master m

 10   left join t_child2 c2

 11   on m.id=c2.mid

 12   where m.id=1) part2

 13  where part1.id=part2.id

 14    and part1.nm=part2.nm;

 

        ID MAS_NAME   CHILD1_NAME         NM         ID MAS_NAME   CHILD2_NAME         NM

---------- ---------- ----------- ---------- ---------- ---------- ----------- ----------

         1 1_name     kll                1          1 1_name     1            1

         1 1_name     dfell       2          1 1_name     123                  2

 

 

注意,為了進行試驗,我們選擇了一個t_masterid進行試驗。我們在兩個部分結果集合中使用rownum進行排序,合併條件設定為t_masterrm相同。但是這個結果集合也有一些問題,就是id=1對應的子表是23條記錄,結果集不正確。解決的方法是使用full outer join方法。

 

 

 

SQL> select part1.id, part1.mas_name, part1.child1_name, part2.child2_name

  2  from

  3  (select m.id, m.mas_name, c1.child1_name, rownum nm

  4   from t_master m

  5   left join t_child1 c1

  6   on m.id=c1.mid

  7   where m.id=1) part1

  8  full outer join

  9  (select m.id, m.mas_name, c2.child2_name, rownum nm

 10   from t_master m

 11   left join t_child2 c2

 12   on m.id=c2.mid

 13   where m.id=1) part2

 14  on part1.id=part2.id and part1.nm=part2.nm;

 

        ID MAS_NAME   CHILD1_NAME CHILD2_NAME

---------- ---------- ----------- -----------

         1 1_name     kll         1

         1 1_name     dfell       123

                                  123

 

 

離正確結果接近了一步。問題有兩個目前,一個是IDMAS_NAME還沒有正確配合結果,有的資料行還顯示為空。另一個提出id=1的條件之後,rownum就不能使用了。

 

下一步,使用nvl能解決第一個問題。

 

 

SQL> select nvl(part1.id,part2.id) ID, nvl(part1.mas_name,part2.mas_name) MAS_NAME,

  2         part1.child1_name, part2.child2_name

  3  from

  4  (select m.id, m.mas_name, c1.child1_name, rownum nm

  5   from t_master m

  6   left join t_child1 c1

  7   on m.id=c1.mid

  8   where m.id=1) part1

  9  full outer join

 10  (select m.id, m.mas_name, c2.child2_name, rownum nm

 11   from t_master m

 12   left join t_child2 c2

 13   on m.id=c2.mid

 14   where m.id=1) part2

 15  on part1.id=part2.id and part1.nm=part2.nm;

 

        ID MAS_NAME   CHILD1_NAME CHILD2_NAME

---------- ---------- ----------- -----------

         1 1_name     kll         1

         1 1_name     dfell       123

         1 1_name                 123

 

 

3、最後結果

 

替換掉rownum的確比較費時間。和rownum類似的一種高階功能就是row_number分析函式。分析函式可以實現分割槽內詳細的行計數功能。

 

下面可以展示下row_number的功能。

 

 

SQL> select m.id, m.mas_name, c1.child1_name, row_number()over(partition by m.id order by c1.id) nm

  2   from t_master m

  3   left join t_child1 c1

  4   on m.id=c1.mid

  5  ;

 

        ID MAS_NAME   CHILD1_NAME         NM

---------- ---------- ----------- ----------

         1 1_name     kll                  1

         1 1_name     dfell                2

         2 2_name     dfkll                1

         3 3_name     fwe                  1

         4 4_name     fwe                  1

         5 5_name     few                  1

         5 5_name     fewd                 2

         6 6_name                          1

 

8 rows selected

 

 

Row_number函式可以實現在一個資料範圍(partition)內的計數行功能。加入到我們的SQL中,如下。

 

 

SQL> select nvl(part1.id,part2.id) ID, nvl(part1.mas_name,part2.mas_name) MAS_NAME,

  2         part1.child1_name, part2.child2_name

  3  from

  4  (select m.id, m.mas_name, c1.child1_name, row_number()over(partition by m.id order by c1.id) nm

  5   from t_master m

  6   left join t_child1 c1

  7   on m.id=c1.mid

  8   ) part1

  9  full outer join

 10  (select m.id, m.mas_name, c2.child2_name, row_number()over(partition by m.id order by c2.id) nm

 11   from t_master m

 12   left join t_child2 c2

 13   on m.id=c2.mid

 14   ) part2

 15  on part1.id=part2.id and part1.nm=part2.nm

 16  order by ID;

 

        ID MAS_NAME   CHILD1_NAME CHILD2_NAME

---------- ---------- ----------- -----------

         1 1_name     kll         1

         1 1_name     dfell       123

         1 1_name                 123

         2 2_name     dfkll       df

         2 2_name                 dff

         3 3_name     fwe         fse

         4 4_name     fwe        

         5 5_name     few        

         5 5_name     fewd       

         6 6_name                 

 

10 rows selected

 

 

需求實現。

 

4、結論

 

在應用系統中,報表往往是重要的功能模組。如何實現報表需求,要把握一個尺度,就是“儘量不要將一句SQL的功能實現為多條”。這就考驗我們的SQL功底和技術。使用抽絲剝繭、層層遞進的方法,可以方便的將我們的應用需求加以實現。

 

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/17203031/viewspace-765688/,如需轉載,請註明出處,否則將追究法律責任。

相關文章