ASE中數據結果集分頁功能的三種實現方法

接上篇的關於MySQL中的分頁方法,本篇簡單討論在Sybase ASE中實現數據結果分頁的方式。

本篇介紹三種方法。

第一種:利用遊標

程序開發人員比較喜歡使用遊標,因爲遊標的“循環”遍歷方式類似編程語言中的for,while,loop語句的實現方法,寫起來比較容易。使用遊標一般步驟是:爲指定的SQL語句定義一個遊標,打開並移動遊標,當移動到指定行號的記錄行之後,再按照需要提取的行數來取數據。從表面上看解決了提取指 定範圍數據的問題;但是在實際應用上,有可能會出現嚴重的性能問題。建立遊標需要耗用一定的系統資源之外;當表內的數據量有上千萬甚至到億級別並且需要取大量的數據結果時,用遊標每移動一 次就取這行數據,然後再移動遊標,這個過程將是緩慢的。在使用遊標的過程中,系統會給相應的表加上共享鎖,導致鎖競爭而嚴重影響數據庫的性能。

在此不再介紹遊標的實現方式,此法比較簡單。一個使用遊標分頁的例子:sp_splitpage_by_cursor.sql

第二種:利用臨時表和標誌列

在Sybase ASE12.5.3及以後的版本中,我們可以用top關鍵字來限定只返回結果集的前N行數據。在ASE12.5.3之前的版本中只能用set rowcount N 的方法來“曲線救國”了。

對於取結果集的第N行至第N+M行數據的要求,我們考慮利用top來實現的話,比較容易想到的是:執行兩次top,再加一次倒序排序。

步驟如下:

          (1) select top N+M * from table_name where_clause order by ID ASC     --把此結果集派生爲表:table_name1

                  (2)   select top M * from table_name1 order by ID  DESC                          --把此結果集派生爲表:table_name2

                  (3)   select * from table_name2 order by ID  ASC

上面的3條語句好像能夠實現返回第N行至第N+M行數據的要求。但是,在Sybase ASE中僅僅利用派生表而不利用臨時表是不能實現這個要求的。

僅僅是ASE中的“派生出派生表(derived table)的SQL語句中不能含有order by 子句”這個限制就足以使上面的方法行不通。還有一個限制是,上面的3個步驟中都利用ID列進行排序。如果表中沒有可用的排序列時,那麼上述方法也不能用 了。不過幸運的是,一般要求對其結果集進行分頁的表都是有可以用作排序的列的(數字型或者日期型)。

繼續尋找一個能用的方法,下面着重介紹目前通用的ASE的分頁思路。此思路的關鍵是產生identity自增列和臨時表。

在ASE中大家要是找到了不用臨時表就可以實現分頁的方法請麻煩告訴我一聲。 我嘗試了很多次,都不是很理想。

概括起來主要語句有兩條:

                (1)   select syb=identity(10),*  into #temp_table from table_name where_clause  order_by_clause

                (2)   select  * from #temp_table where_clause and syb >= N   and syb <= N+M

使用此方法實現ASE分頁的一個存儲過程爲:

-- 此存儲過程適用於所有版本的ASE中
use sybsystemprocs
go
if exists(select 1 from sybsystemprocs.dbo.sysobjects where type='P' and name='sp_page')
  drop procedure sp_page
go

create procedure sp_page @qry varchar(16384),@ipage int,@num int 
as
begin
  
  declare @rcount int
  declare @execsql varchar(16384)
  
  select @rcount=@ipage*@num
  set @execsql = 'set rowcount '|| convert(varchar,@rcount)
  set @execsql = @execsql || stuff(@qry,charindex('SELECT ',upper(@qry)),6,' SELECT sybid=identity(12),')
  set @execsql = stuff(@execsql,charindex(' FROM ',upper(@execsql)),6,' INTO #temptable FROM ')
  set @execsql = @execsql || ' SELECT * FROM #temptable  WHERE sybid >' || convert(varchar,(@ipage-1)*@num)
  set @execsql = @execsql || ' AND sybid<=' || convert(varchar,@ipage*@num) + ' set rowcount 0'
  execute(@execsql)
  
end
go

exec sp_procxmode 'sp_page',anymode
go
grant execute on sp_page to public
go

用一個例子演示一下:

(1) 建立測試表:testA

create table testA(id int not null,name varchar(30) null)
go

(2) 插入測試數據

insert into testA
select 1,'liuzhenfu'
go
insert into testA
select 2,'andkylee'
go

(3) 循環插入大量的重複數據

insert into testA
select id+(select max(id) from testA),name from testA
go 15

向表testA循環插入已有的數據,15次之後,表testA內的數據達到2^16 = 65536 行。

 

(4) 利用臨時表 + 自增標誌列來提取第100行至第200行的數據。

語句如下:

select syb=identity(10) ,* into #tempA from testA
select * from #tempA where syb>=100 and syb<=200
drop table #tempA

返回的結果爲:

1> select syb=identity(10),* into #tempA from testA
2> select * from #tempA where syb>=100 and syb<=200
3> go
(65536 rows affected)
 syb           id          name
 ------------- ----------- ---------------------------
           100         100 andkylee
           101         101 liuzhenfu
           102         102 andkylee
           103         103 liuzhenfu
           104         104 andkylee
           105         105 liuzhenfu
           106         106 andkylee
           107         107 liuzhenfu
           108         108 andkylee
           109         109 liuzhenfu
           110         110 andkylee
           111         111 liuzhenfu
           112         112 andkylee
           113         113 liuzhenfu
           114         114 andkylee
           115         115 liuzhenfu
           116         116 andkylee
           117         117 liuzhenfu
           118         118 andkylee
           119         119 liuzhenfu
           120         120 andkylee
           121         121 liuzhenfu
           122         122 andkylee
           123         123 liuzhenfu
           124         124 andkylee
           125         125 liuzhenfu
           126         126 andkylee
           127         127 liuzhenfu
           128         128 andkylee
           129         129 liuzhenfu
           130         130 andkylee
           131         131 liuzhenfu
           132         132 andkylee
           133         133 liuzhenfu
           134         134 andkylee
           135         135 liuzhenfu
           136         136 andkylee
           137         137 liuzhenfu
           138         138 andkylee
           139         139 liuzhenfu
           140         140 andkylee
           141         141 liuzhenfu
           142         142 andkylee
           143         143 liuzhenfu
           144         144 andkylee
           145         145 liuzhenfu
           146         146 andkylee
           147         147 liuzhenfu
           148         148 andkylee
           149         149 liuzhenfu
           150         150 andkylee
           151         151 liuzhenfu
           152         152 andkylee
           153         153 liuzhenfu
           154         154 andkylee
           155         155 liuzhenfu
           156         156 andkylee
           157         157 liuzhenfu
           158         158 andkylee
           159         159 liuzhenfu
           160         160 andkylee
           161         161 liuzhenfu
           162         162 andkylee
           163         163 liuzhenfu
           164         164 andkylee
           165         165 liuzhenfu
           166         166 andkylee
           167         167 liuzhenfu
           168         168 andkylee
           169         169 liuzhenfu
           170         170 andkylee
           171         171 liuzhenfu
           172         172 andkylee
           173         173 liuzhenfu
           174         174 andkylee
           175         175 liuzhenfu
           176         176 andkylee
           177         177 liuzhenfu
           178         178 andkylee
           179         179 liuzhenfu
           180         180 andkylee
           181         181 liuzhenfu
           182         182 andkylee
           183         183 liuzhenfu
           184         184 andkylee
           185         185 liuzhenfu
           186         186 andkylee
           187         187 liuzhenfu
           188         188 andkylee
           189         189 liuzhenfu
           190         190 andkylee
           191         191 liuzhenfu
           192         192 andkylee
           193         193 liuzhenfu
           194         194 andkylee
           195         195 liuzhenfu
           196         196 andkylee
           197         197 liuzhenfu
           198         198 andkylee
           199         199 liuzhenfu
           200         200 andkylee
(101 rows affected)

需要將select * from #tempA中的星號*替換爲需要返回的列名。

繼續。。。。

當要求返回滿足name='andkylee'的所有行中的第100行至第200行的數據時, 利用

select syb=identity(10),* into #tempA from testA where name='andkylee'

select * from #tempA where syb>=100 and syb<=200

drop table #tempA

 

第三種:利用rowcount

此種方法有點不足:必須利用可用作排序的列 對結果集進行排序。

還是上面的測試表testA,如果從第9000行開始選擇10行數據,那麼語句如下:

declare @id1 int
set rowcount 9000
select @id1 = id from testA order by id
set rowcount 10
select *from testA where id >= @id1 order by id
set rowcount 0
go

 

此種方法中核心語句是select @id1=id from testA order by id , 在對錶testA執行查詢的過程中,每讀取一行都會把id列的值賦給@id1這個變量,一直持續到最後一行,@id1這個變量反覆被下一行的id值刷新, 最後結果只得到最後一行的id值。如果在此select語句之前加上rowcount的限定,那麼就可用變量@id1來獲得第rowcount行的id 值,於是我們也就獲得了要返回的範圍結果集的起點了。

後面的 set rowcount 10

         select * from testA where id >= @id1 order by id

這兩句實際上可以用一句select top 10 * from testA where id >= @id1 order by id  來替代。 

這樣,兩種不同的實現形式爲:

declare @id1 int
set rowcount 9000
select @id1 = id from testA  order by id
set rowcount 0
select top 10  *from testA where  id >= @id1 order by id
go

 

分別看看執行結果吧!

第一種方式的執行結果:

1> declare @id1 int
2> set rowcount 9000
3> select @id1 = id from testA order by id
4> set rowcount 10
5> select *from testA where id >= @id1 order by id
6> set rowcount 0
7> go
(9000 rows affected)
 id          name
 ----------- ------------------------------
        9000 andkylee
        9001 liuzhenfu
        9002 andkylee
        9003 liuzhenfu
        9004 andkylee
        9005 liuzhenfu
        9006 andkylee
        9007 liuzhenfu
        9008 andkylee
        9009 liuzhenfu
(10 rows affected)
1>
 

第二種方式的執行結果:

1> declare @id1 int
2> set rowcount 9000
3> select @id1 = id from testA order by id
4> set rowcount 0
5> select top 10  *from testA where id >= @id1 order by id
6> go
(9000 rows affected)
 id          name
 ----------- ------------------------------
        9000 andkylee
        9001 liuzhenfu
        9002 andkylee
        9003 liuzhenfu
        9004 andkylee
        9005 liuzhenfu
        9006 andkylee
        9007 liuzhenfu
        9008 andkylee
        9009 liuzhenfu
(10 rows affected)
1>

當然,兩種結果一模一樣。

最後我們測試表testA中的ID列順序值打亂, 來看看以上語句的執行情況。執行:

update testA set id = id + cast( rand() * 65536 as int ) 

ID列值打亂之後,前100行的數據爲:

1> select top 100 * from testA
2> go
 id          name
 ----------- ------------------------------
       51366 liuzhenfu
       33573 andkylee
       19447 liuzhenfu
       19408 andkylee
       57839 liuzhenfu
       18817 andkylee
     ......................
       19075 liuzhenfu
       17081 andkylee
       26444 liuzhenfu
        6620 andkylee
       52344 liuzhenfu
       49348 andkylee
(100 rows affected)

我們要求返回滿足name='andkylee'的從第9000行開始的10行數據。

declare @id1 int
set rowcount 9000
select @id1 = id from testA where name='andkylee' order by id
set rowcount 10
select *from testA where name='andkylee' and id >= @id1 order by id
set rowcount 0
go

結果爲:

1> declare @id1 int
2> set rowcount 9000
3> select @id1 = id from testA where name='andkylee' order by id
4> set rowcount 10
5> select *from testA where name='andkylee' and id >= @id1 order by id
6> set rowcount 0
7> go
(9000 rows affected)
 id          name
 ----------- ------------------------------
       48639 andkylee
       48639 andkylee
       48641 andkylee
       48641 andkylee
       48642 andkylee
       48643 andkylee
       48644 andkylee
       48644 andkylee
       48650 andkylee
       48650 andkylee
(10 rows affected)

如果不對ID列進行排序, 有下面的sql語句:

declare @id1 int
set rowcount 9000
select @id1 = id from testA where name='andkylee'
set rowcount 10
select *from testA where name='andkylee' and id >= @id1 
set rowcount 0
go

相應的結果集爲:

1> declare @id1 int
2> set rowcount 9000
3> select @id1 = id from testA where name='andkylee'
4> set rowcount 10
5> select *from testA where name='andkylee' and id >= @id1
6> set rowcount 0
7> go
(9000 rows affected)
 id          name
 ----------- ------------------------------
       74076 andkylee
       74514 andkylee
       74053 andkylee
       74385 andkylee
       74339 andkylee
       74792 andkylee
       74794 andkylee
       74984 andkylee
       75052 andkylee
       74138 andkylee
(10 rows affected)
1>

可以發現這個兩句的結果是不同的。

我想既然都要求返回指定範圍的結果集, 肯定是有排序的依據了, 否則怎麼知道該返回哪個範圍呢?

還有,我給出的第三種方法,在進行表掃描的時候,即使不指定排序,也是能夠得到正確結果的。因爲表掃描時很可能會按照表內數據在物理頁面上的物理位置來返回結果。

就先介紹到這裏吧, 後續可能會根據情況進行補充。

文件下載:

使用第二種方法在ASE實現分頁的存儲過程: sp_page.sql 包含top和set rowcount兩種。

根據顯示的當前頁號進行優化處理、更通用些的存儲過程:splitpage.sql

————————————————————————————————-
—- 本文爲andkylee個人原創,請在尊重作者勞動成果的前提下進行轉載;
—- 轉載務必註明原始出處 : http://www.dbainfo.net
—- 關鍵字:ASE 分頁  top  identity  set rowcount  temp table  自增列 僞列 臨時表
————————————————————————————————-

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章