線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:10880
推到 Plurk!
推到 Facebook!

TQuery如何承接大量的資料

答題得分者是:sryang
carstyc
資深會員


發表:16
回覆:254
積分:329
註冊:2003-07-18

發送簡訊給我
#1 引用回覆 回覆 發表時間:2007-09-21 15:55:43 IP:203.79.xxx.xxx 訂閱
各位先進:

請教一個問題,我想要從一台 ORACLE 上的某幾個TABLE資料 匯出到另一台ORACLE.

我一開始用兩個TAdoConnection,兩個TAdoQuery,來複製資料到另一台DB,跑了三萬筆資料就掛點了。

後來我改用TDataBase 及 TQuery,結果筆數有顯著的成長,跑了十幾萬筆才掛點。

問題是這幾個TABLE都有好幾佰萬筆。


請問TQuery 在 move next record 時,有辦法釋放掉之前record的記憶體嗎。如果它一直不釋放掉不用的record,那愈後面所佔用的資源一定愈來愈多,多到系統撐不住就爆掉。


還是我該用什麼不一樣的概念及架構,才能寫出一個程式,來完成這項不可能的任務。就像DELPHI的DataPump,我也有試過DELPHI 提供的 DATAPUMP 去匯資料,它好像也有問題,在沒有錯誤訊息的狀況下,還沒匯完資料就結束了。



也許有人會懷疑,為什麼不用 EXP IMP,因為我沒有權限可以EXP。

我也有試過TOAD去DUMP資料,TOAD也被我搞到掛點。


煩請各路高手協助一下,謝謝
syntax
尊榮會員


發表:26
回覆:1139
積分:1258
註冊:2002-04-23

發送簡訊給我
#2 引用回覆 回覆 發表時間:2007-09-22 02:44:26 IP:61.64.xxx.xxx 訂閱
有沒有考慮
1. 資料庫本身的複製功能?

2. 資料庫本身所提供的 Dump 與匯入的功能?

3. SQL 語法上的標準 dump 方式?

因為你只說你用 TAdo ,但是沒說明你到底下了何種 SQL 語法?以及你複製的方式(實際運作,不是一句話:「我將A複製到B」,這樣而已)?

===================引 用 carstyc 文 章===================
各位先進:

請教一個問題,我想要從一台 ORACLE 上的某幾個TABLE資料 匯出到另一台ORACLE.

我一開始用兩個TAdoConnection,兩個TAdoQuery,來複製資料到另一台DB,跑了三萬筆資料就掛點了。

後來我改用TDataBase 及 TQuery,結果筆數有顯著的成長,跑了十幾萬筆才掛點。

問題是這幾個TABLE都有好幾佰萬筆。


請問TQuery 在 move next record 時,有辦法釋放掉之前record的記憶體嗎。如果它一直不釋放掉不用的record,那愈後面所佔用的資源一定愈來愈多,多到系統撐不住就爆掉。


還是我該用什麼不一樣的概念及架構,才能寫出一個程式,來完成這項不可能的任務。就像DELPHI的DataPump,我也有試過DELPHI 提供的 DATAPUMP 去匯資料,它好像也有問題,在沒有錯誤訊息的狀況下,還沒匯完資料就結束了。



也許有人會懷疑,為什麼不用 EXP IMP,因為我沒有權限可以EXP。

我也有試過TOAD去DUMP資料,TOAD也被我搞到掛點。


煩請各路高手協助一下,謝謝
sryang
尊榮會員


發表:38
回覆:742
積分:876
註冊:2002-06-27

發送簡訊給我
#3 引用回覆 回覆 發表時間:2007-09-22 09:21:41 IP:124.10.xxx.xxx 訂閱
既然您想要用程式的方法來解決,那麼我提供一些個人經驗給您參考
首先,用 BDE 或是 ADO 都是可以的。問題在於使用的方式
您提到,您用了兩個 TQuery 或 TADOQuery。大量資料的狀況之下這是非掛不可的
因為用來 insert 進目的資料庫用的 TQuery 或 TADOQuery 會因為大量資料的加入而導致錯誤
既然您的目的是要「insert 進目的資料庫」那麼為什麼要耗用記憶體來暫存這些資料呢?
所以,個人的使用習慣是使用 TQuery.ExecSql 或是使用 TADOCommand.Execute 來執行 INSERT 的語法
並且為了速度及記憶體需求的考量,搭配分批 commit

範例:
[code delphi]
procedure BatchMove();
var i: integer;
begin
Query1.SQL.Clear;
Query1.SQL.Add('SELECT FIELD1, FIELD2, FIELD3 FROM TABLE1');
Query1.Open;

Query2.SQL.Clear;
Query2.SQL.Add('INSERT INTO TABLE2 (FIELD1, FIELD2, FIELD3) VALUES (:FIELD1, :FIELD2, :FIELD3)');

i := 1;
Database2.StartTransaction;
try
while not Query1.Eof do
begin
Query2.ParamByName('FIELD1').Value := Query1.FieldByName('FIELD1').Value;
Query2.ParamByName('FIELD2').Value := Query1.FieldByName('FIELD2').Value;
Query2.ParamByName('FIELD3').Value := Query1.FieldByName('FIELD3').Value;
Query2.ExecSql;

// 每 1000 筆 commit 一次
Inc(i);
if i >= 1000 then
begin
i := 1;
Database2.Commit;
Database2.StartTransaction;
end;
end;
finally
Query1.Close;
end;
end;
[/code]
------
歡迎參訪 "腦殘賤貓的備忘錄" http://maolaoda.blogspot.com/
編輯記錄
sryang 重新編輯於 2007-09-22 09:25:12, 註解 無‧
carstyc
資深會員


發表:16
回覆:254
積分:329
註冊:2003-07-18

發送簡訊給我
#4 引用回覆 回覆 發表時間:2007-09-22 22:36:11 IP:219.84.xxx.xxx 訂閱

Syntax兄您好:

1.小弟文中有提到,因為沒有資料庫Dump的權限,所以沒辦法使用資料庫本身的複製功能,只能用SELECT 的方式把資料弄出來。

2.至於ADOQuery中下了何種語法,就最單純的 Select * from TableName 而已

至於另一個負責塞資料的ADOQuery,也只是用 Insert 的語法而已。

以上說明,謝謝


===================引 用 syntax 文 章===================
有沒有考慮
1. 資料庫本身的複製功能?

2. 資料庫本身所提供的 Dump 與匯入的功能?

3. SQL 語法上的標準 dump 方式?

因為你只說你用 TAdo ,但是沒說明你到底下了何種 SQL 語法?以及你複製的方式(實際運作,不是一句話:「我將A複製到B」,這樣而已)?

===================引 用 carstyc 文 章===================
各位先進:

請教一個問題,我想要從一台 ORACLE 上的某幾個TABLE資料 匯出到另一台ORACLE.

我一開始用兩個TAdoConnection,兩個TAdoQuery,來複製資料到另一台DB,跑了三萬筆資料就掛點了。

後來我改用TDataBase 及 TQuery,結果筆數有顯著的成長,跑了十幾萬筆才掛點。

問題是這幾個TABLE都有好幾佰萬筆。


請問TQuery 在 move next record 時,有辦法釋放掉之前record的記憶體嗎。如果它一直不釋放掉不用的record,那愈後面所佔用的資源一定愈來愈多,多到系統撐不住就爆掉。


還是我該用什麼不一樣的概念及架構,才能寫出一個程式,來完成這項不可能的任務。就像DELPHI的DataPump,我也有試過DELPHI 提供的 DATAPUMP 去匯資料,它好像也有問題,在沒有錯誤訊息的狀況下,還沒匯完資料就結束了。



也許有人會懷疑,為什麼不用 EXP IMP,因為我沒有權限可以EXP。

我也有試過TOAD去DUMP資料,TOAD也被我搞到掛點。


煩請各路高手協助一下,謝謝
carstyc
資深會員


發表:16
回覆:254
積分:329
註冊:2003-07-18

發送簡訊給我
#5 引用回覆 回覆 發表時間:2007-09-22 22:47:33 IP:219.84.xxx.xxx 訂閱
sryang兄您好:

感謝您的範例,但也許小弟描述的不夠清楚。

就以您的範例來說,我碰到的問題不在於Query2塞資料,Query2 Insert Data的部份我已經用其它方式處理掉了,這部份沒問題。

我的問題在於 Query1 ,如果它Select 的Table 有一仟萬筆資料時,可能執行 Query1.next 五十萬次後,Query1就會承受不了這麼大的資料量而『爆炸』了。也就是說,雖然Query1的cursor雖然停在第五十萬筆資料,但前面的四十九萬九仟九佰九十九筆資料都『暫存』在電腦的記憶體中。如此下來,就算電腦有再多的Memory,也不夠塞一仟萬筆資料。我該怎麼解決這個問題。

所以這就是我的問題所在,不知Sryang兄有了解了小弟的困擾了嗎?

以上說明,謝謝






===================引 用 sryang 文 章===================
既然您想要用程式的方法來解決,那麼我提供一些個人經驗給您參考
首先,用 BDE 或是 ADO 都是可以的。問題在於使用的方式
您提到,您用了兩個 TQuery 或 TADOQuery。大量資料的狀況之下這是非掛不可的
因為用來 insert 進目的資料庫用的 TQuery 或 TADOQuery 會因為大量資料的加入而導致錯誤
既然您的目的是要「insert 進目的資料庫」那麼為什麼要耗用記憶體來暫存這些資料呢?
所以,個人的使用習慣是使用 TQuery.ExecSql 或是使用 TADOCommand.Execute 來執行 INSERT 的語法
並且為了速度及記憶體需求的考量,搭配分批 commit

範例:
[code delphi]
procedure BatchMove();
var i: integer;
begin
Query1.SQL.Clear;
Query1.SQL.Add('SELECT FIELD1, FIELD2, FIELD3 FROM TABLE1');
Query1.Open;

Query2.SQL.Clear;
Query2.SQL.Add('INSERT INTO TABLE2 (FIELD1, FIELD2, FIELD3) VALUES (:FIELD1, :FIELD2, :FIELD3)');

i := 1;
Database2.StartTransaction;
try
while not Query1.Eof do
begin
Query2.ParamByName('FIELD1').Value := Query1.FieldByName('FIELD1').Value;
Query2.ParamByName('FIELD2').Value := Query1.FieldByName('FIELD2').Value;
Query2.ParamByName('FIELD3').Value := Query1.FieldByName('FIELD3').Value;
Query2.ExecSql;

// 每 1000 筆 commit 一次
Inc(i);
if i >= 1000 then
begin
i := 1;
Database2.Commit;
Database2.StartTransaction;
end;
end;
finally
Query1.Close;
end;
end;
[/code]
shunchia63
高階會員


發表:26
回覆:141
積分:198
註冊:2007-05-22

發送簡訊給我
#6 引用回覆 回覆 發表時間:2007-09-22 23:26:47 IP:61.64.xxx.xxx 訂閱
你可以用分頁的SQL    解決   你懷疑的部份


例 Page1 :找1~1萬
Page2 :找1萬~2萬

......


1000萬要跑多久 ?? 1day ??
TxLog DB不會爆掉嗎 ??

carstyc
資深會員


發表:16
回覆:254
積分:329
註冊:2003-07-18

發送簡訊給我
#7 引用回覆 回覆 發表時間:2007-09-22 23:56:50 IP:219.84.xxx.xxx 訂閱
Shunchia63兄您好:

您指的分頁,是指我在SQL Command裡面直接用條件把它區分開嗎。

因為我要處理的TABLE有幾百個,雖說超過上百萬筆的Table只十幾二十個,但針對每個去找出適當的Key值,然後做不同的分頁SQL條件,也是件蠻麻煩的事。

我只是想了解一下,一般像有些工具,可以將不同的資料庫資料轉移到另一台資料庫中。這些工具可以把ORACLE的資料匯出到SQL Server,它們也一定會碰到這種問題,這樣的工具是如何解決這種問題。



===================引 用 shunchia63 文 章===================
你可以用分頁的SQL 解決 你懷疑的部份


例 Page1 :找1~1萬
Page2 :找1萬~2萬

......


1000萬要跑多久 ?? 1day ??
TxLog DB不會爆掉嗎 ??

sryang
尊榮會員


發表:38
回覆:742
積分:876
註冊:2002-06-27

發送簡訊給我
#8 引用回覆 回覆 發表時間:2007-09-23 00:22:55 IP:124.10.xxx.xxx 訂閱
既然這樣的話,可能就要使用 server side cursor 了
請參考這兩篇:
http://delphi.ktop.com.tw/board.php?cid=30&fid=66&tid=48618
http://delphi.ktop.com.tw/board.php?cid=30&fid=66&tid=50515

===================引 用 carstyc 文 章===================
sryang兄您好:
感謝您的範例,但也許小弟描述的不夠清楚。
就以您的範例來說,我碰到的問題不在於Query2塞資料,Query2 Insert Data的部份我已經用其它方式處理掉了,這部份沒問題。
我的問題在於 Query1 ,如果它Select 的Table 有一仟萬筆資料時,可能執行 Query1.next 五十萬次後,Query1就會承受不了這麼大的資料量而『爆炸』了。也就是說,雖然Query1的cursor雖然停在第五十萬筆資料,但前面的四十九萬九仟九佰九十九筆資料都『暫存』在電腦的記憶體中。如此下來,就算電腦有再多的Memory,也不夠塞一仟萬筆資料。我該怎麼解決這個問題。
所以這就是我的問題所在,不知Sryang兄有了解了小弟的困擾了嗎?
以上說明,謝謝
------
歡迎參訪 "腦殘賤貓的備忘錄" http://maolaoda.blogspot.com/
編輯記錄
sryang 重新編輯於 2007-09-23 00:26:53, 註解 無‧
carstyc
資深會員


發表:16
回覆:254
積分:329
註冊:2003-07-18

發送簡訊給我
#9 引用回覆 回覆 發表時間:2007-09-23 08:28:16 IP:219.84.xxx.xxx 訂閱
Sryang兄您好:

謝謝您的回覆,但我看了一下這兩篇文章,似乎都是在談如何接回Store Procedure的Cusror,不像是我的問題。

另 Server Side Cursor的方式我也有試過,但似乎沒有解決資料量大的問題,還是一樣會『爆炸』。

難道Data Dump的小工具真的這麼難寫嗎?

===================引 用 sryang 文 章===================
既然這樣的話,可能就要使用 server side cursor 了
請參考這兩篇:
http://delphi.ktop.com.tw/board.php?cid=30&fid=66&tid=48618
http://delphi.ktop.com.tw/board.php?cid=30&fid=66&tid=50515

bighm
一般會員


發表:5
回覆:21
積分:15
註冊:2006-10-29

發送簡訊給我
#10 引用回覆 回覆 發表時間:2007-09-23 19:48:31 IP:59.104.xxx.xxx 訂閱
請教一下,您一定要使用delphi 達到此效果嗎?
如果不一定,小弟這裡有幾個小方法,您參考看看:

1. 利用類似 PLSQL Develop 或 toad 等工具,產出 sql 檔,如果沒有類似的工具,就用 spool 自己做出來 sql 檔
2. 利用 oracle 內的 spool,想辦法把 insert into 的語法做出來,方法如下:
a. 進入sql*plus

[code sql]
set pagesize 50000
set linesize 1000
spool c:\insert.sql
select 'INSERT INTO TABLE2 (F1, F2) VALUES ('''||F1||''','''||F2||''');' from table1
spool off
[/code]

以上動作,可將 insert into 子句產出,剩下就不是問題了
ps. sppo的用途為將螢幕上的字元印到指定檔案中,所以產出來的檔案,請記得先把不要的字元清除掉

PPS. TADOQuery的設定,好像一定會暫存到記憶體中,您可以試試看 .NET 的 OLEDBDataReader搭配 OLEDBDataCommand 來試試看(不保証可行,因為我不曉得您的server 端的暫存區夠不夠撐得住,
不過我還是比較建用 pl/sql或 SQL*Lader 來處理
編輯記錄
bighm 重新編輯於 2007-09-23 19:58:17, 註解 無‧
carstyc
資深會員


發表:16
回覆:254
積分:329
註冊:2003-07-18

發送簡訊給我
#11 引用回覆 回覆 發表時間:2007-09-24 01:41:12 IP:219.84.xxx.xxx 訂閱
Bighm兄您好:

您提的這個方法,其實我也有想過,只是前幾個回覆中有提過,這樣的Table有幾十個至幾佰個。如果每個都要去整理Insert的語法。其實也是蠻花時間的。

而且我有試過用Toad去匯出資料到文字檔,它的方法其實很類似您說的方法,只是出來的 .sql 檔案曾經高達4G多,然後Toad也『當掉』了,作業系統也掛了,只能重開機。SQLPlus我倒是沒試過,我會試試看,不過我想失敗的機會應該也蠻高的。

至於 .NET 的開發環境我目前沒有,有機會我再想辦法弄來試看看。但是我還是比較想知道,難道DELPHI的TQuery或TADOQuery碰到筆數大的TABLE,真的只能放棄嗎?用DELPHI真的寫不出資料Export的工具嗎?

那位有經驗的高高手,幫個忙解惑一下吧。

===================引 用 bighm 文 章===================
請教一下,您一定要使用delphi 達到此效果嗎?
如果不一定,小弟這裡有幾個小方法,您參考看看:

1. 利用類似 PLSQL Develop 或 toad 等工具,產出 sql 檔,如果沒有類似的工具,就用 spool 自己做出來 sql 檔
2. 利用 oracle 內的 spool,想辦法把 insert into 的語法做出來,方法如下:
a. 進入sql*plus

[code sql]
set pagesize 50000
set linesize 1000
spool c:\insert.sql
select 'INSERT INTO TABLE2 (F1, F2) VALUES ('''||F1||''','''||F2||''');' from table1
spool off
[/code]

以上動作,可將 insert into 子句產出,剩下就不是問題了
ps. sppo的用途為將螢幕上的字元印到指定檔案中,所以產出來的檔案,請記得先把不要的字元清除掉

PPS. TADOQuery的設定,好像一定會暫存到記憶體中,您可以試試看 .NET 的 OLEDBDataReader搭配 OLEDBDataCommand 來試試看(不保証可行,因為我不曉得您的server 端的暫存區夠不夠撐得住,
不過我還是比較建用 pl/sql或 SQL*Lader 來處理
bestlong
站務副站長


發表:125
回覆:734
積分:506
註冊:2002-10-19

發送簡訊給我
#12 引用回覆 回覆 發表時間:2007-09-25 23:41:25 IP:220.140.xxx.xxx 未訂閱
不要妄想用一個 TQuery 來處理超大量的資料量,越多的欄位越是不可能的任務,特別是有備註欄位的資料表更慘。

你可以讓撈資料的架構作個改變來降低對記憶體的需求
假定你的三個欄位都是 int 型態然後有一個主鍵欄位
當你用一個 TQuery 來讀取一百萬筆資料全部需要的記憶體 = 一百萬 x 3 x int 欄位所需的記憶體(可能是 32bit)

改用兩個 TQuery 來處理資料來源,例如 qyList 來做資料索引只讀取主鍵欄位 qyRow 來讀取完整的資料列(依據 qyList 的紀錄位置)
這樣對記憶體的最大需求在 qyList 上就是原來單一 TQuery 的三分之一記憶體需求
至於 qyRow 只會需要一筆紀錄的記憶體需求可以忽略不計

大致程式碼結構如下
[code delphi]
procedure TForm1.FormCreate(Sender: TObject);
begin
qyList.SQL.Add('select 主鍵欄 from tbname');
qyRow.SQL.Add('select * from tbname where 主鍵欄 = :F1');
end;

procedure TForm1.qyListBeforeScroll(DataSet: TDataSet);
begin
qyRow.Close;
end;

procedure TForm1.qyListAfterScroll(DataSet: TDataSet);
begin
qyRow.Open;
end;

procedure TForm1.qyRowBeforeOpen(DataSet: TDataSet);
begin
qyRow.ParamByName('F1').AsString := qyList.FieldByName('主鍵欄').AsString;
end;

procedure TForm1.btnCopyDataClick(Sender: TObject);
begin
qyList.Open;
while not qyList.Eof do
begin
qyDist.ParamByName('FIELD1').Value := qyRow.FieldByName('FIELD1').Value;
qyDist.ParamByName('FIELD2').Value := qyRow.FieldByName('FIELD2').Value;
qyDist.ParamByName('FIELD3').Value := qyRow.FieldByName('FIELD3').Value;
qyDist.ExecSql;
qyList.Next;
end;
qyList.Close;
end;
[/code]

這樣的處理技巧相信可以解決你的問題
------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
carstyc
資深會員


發表:16
回覆:254
積分:329
註冊:2003-07-18

發送簡訊給我
#13 引用回覆 回覆 發表時間:2007-09-26 00:31:26 IP:219.84.xxx.xxx 訂閱
Bestlong兄您好:

感謝您的指點,我有懂您在說什麼,這樣也許是個方法,我會去試試。

但我個人是比較好奇想知道,一般的Dump工具也會是用如此方式處理嗎,這樣變成要先去找到適當的『Key』值,才有辦法使用Bestlong兄您說的方法,只是一般這種工具軟體有辦法正確的去得知適當的Key值嗎?還是他們用其他的怪方法。


各位大大請勿見怪,覺得小弟怎麼吹毛求疵,小弟只是好奇,市面上好用的Data Dump工具它們怎麼處理這樣的問題。這個結果也許會讓很多人都學習到不一樣的『進階』技巧。

以上說明,謝謝


===================引 用 bestlong 文 章===================
不要妄想用一個 TQuery 來處理超大量的資料量,越多的欄位越是不可能的任務,特別是有備註欄位的資料表更慘。

你可以讓撈資料的架構作個改變來降低對記憶體的需求
假定你的三個欄位都是 int 型態然後有一個主鍵欄位
當你用一個 TQuery 來讀取一百萬筆資料全部需要的記憶體 = 一百萬 x 3 x int 欄位所需的記憶體(可能是 32bit)

改用兩個 TQuery 來處理資料來源,例如 qyList 來做資料索引只讀取主鍵欄位 qyRow 來讀取完整的資料列(依據 qyList 的紀錄位置)
這樣對記憶體的最大需求在 qyList 上就是原來單一 TQuery 的三分之一記憶體需求
至於 qyRow 只會需要一筆紀錄的記憶體需求可以忽略不計

大致程式碼結構如下
[code delphi]
procedure TForm1.FormCreate(Sender: TObject);
begin
qyList.SQL.Add('select 主鍵欄 from tbname');
qyRow.SQL.Add('select * from tbname where 主鍵欄 = :F1');
end;

procedure TForm1.qyListBeforeScroll(DataSet: TDataSet);
begin
qyRow.Close;
end;

procedure TForm1.qyListAfterScroll(DataSet: TDataSet);
begin
qyRow.Open;
end;

procedure TForm1.qyRowBeforeOpen(DataSet: TDataSet);
begin
qyRow.ParamByName('F1').AsString := qyList.FieldByName('主鍵欄').AsString;
end;

procedure TForm1.btnCopyDataClick(Sender: TObject);
begin
qyList.Open;
while not qyList.Eof do
begin
qyDist.ParamByName('FIELD1').Value := qyRow.FieldByName('FIELD1').Value;
qyDist.ParamByName('FIELD2').Value := qyRow.FieldByName('FIELD2').Value;
qyDist.ParamByName('FIELD3').Value := qyRow.FieldByName('FIELD3').Value;
qyDist.ExecSql;
qyList.Next;
end;
qyList.Close;
end;
[/code]

這樣的處理技巧相信可以解決你的問題
bestlong
站務副站長


發表:125
回覆:734
積分:506
註冊:2002-10-19

發送簡訊給我
#14 引用回覆 回覆 發表時間:2007-09-26 09:22:55 IP:60.248.xxx.xxx 未訂閱
我這裡所提供的方法,僅能處理有唯一值欄位資料表的狀況。
若是 datadump 工具可就沒這麼單純,還必須可以處理沒有唯一值欄位資料表的狀況。

如果你只是使用 TQuery 這個元件來處理以及思考,就會造成你處理的方式受限於元件的特性。

TQuery 讀取到的資料都會 Cache 在記憶體中,
某方面是為了讓資料可以雙向操作也就是可以由 First 到 Last 還可以由 Last 到 First 的移動。

而 Dump 的思考就只需要單向操作由 First 到 Last 移動,而且還可以用過就丟棄,

所以使用 TQuery 來處理 Dump 只是一個可能解卻不是一個完美解,
在少量資料環境下會正常運作,可是當碰到超大量資料的環境下就難說了,
總之電腦再怎麼發達或是高檔絕對都是一個有限資源的運作環境,
做好有限資源的運用管理,才能更加提昇軟體的品質。
------
http://blog.bestlong.idv.tw/
http://www.bestlong.idv.tw/
http://delphi-ktop.bestlong.idv.tw/
sryang
尊榮會員


發表:38
回覆:742
積分:876
註冊:2002-06-27

發送簡訊給我
#15 引用回覆 回覆 發表時間:2007-09-26 12:48:41 IP:59.125.xxx.xxx 訂閱
所以,直接呼叫 BDE API 來取得資料,就沒有暫存資料的問題了。樓主可以試試看
以下程式是參考 BDE.HLP 裡面的範例寫的,確定可以執行
[code delphi]
procedure TForm1.Button1Click(Sender: TObject);
var
hDB: hDBIDb; // Database handle
hCur: hDBICur; // Handle to the data cursor
szTableName: array [0..DBIMAXNAMELEN] of Char; // Table name
CursorProps: CURProps; // Data cursor properties
RecordBuffer: pBYTE; // Buffer into which to load a record
FieldBuffer: array [0..2047] of Char; // Variable for a field
IsBlank: BOOL; // Variable for blank fields
EndOfFile: DBIResult;
i: integer;
s: string;
begin
// 初始 BDE
Check(DbiInit(nil));
// 連接資料庫
Check(DbiOpenDatabase('BDE ALIAS NAME', 'ORACLE', dbiREADONLY, dbiOPENSHARED, '密碼', 0, nil, nil, hDB));
// 開啟 Table
szTableName := 'TABLE1';
Check(DbiOpenTable(hDB, szTableName, szPARADOX, nil, nil, 0, dbiREADONLY, dbiOPENSHARED, xltFIELD, False, nil, hCur));
// 取得 Cursor 屬性
Check(DbiGetCursorProps(hCur, CursorProps));
// 配置一塊可以放得下一筆紀錄的記憶體
RecordBuffer := AllocMem(CursorProps.iRecBufSize * SizeOf(BYTE));
// 移動到第一筆
Check(DbiSetToBegin(hCur));
// 讀取一筆
EndOfFile := DbiGetNextRecord(hCur, dbiNOLOCK, RecordBuffer, nil);
while EndOfFile = DBIERR_NONE do
begin
s := '';
// 讀取各欄位資料
for i := 1 to CursorProps.iFields do
begin
// 檢查欄位是否為 NULL
Check(DbiGetField(hCur, i, RecordBuffer, nil, IsBlank));
if not IsBlank then
begin
// 讀取欄位資料
Check(DbiGetField(hCur, i, RecordBuffer, pBYTE(@FieldBuffer), IsBlank));
s := s StrPas(FieldBuffer) #9
end
else
s := s '(null)' #9
end;
// 輸出到 Memo
Memo1.Lines.Add(s);
// 讀取下一筆
EndOfFile := DbiGetNextRecord(hCur, dbiNOLOCK, RecordBuffer, nil);
end;

// 釋放紀錄緩衝區
FreeMem(RecordBuffer);
// 關閉游標
if not (hCur = nil) then Check(DbiCloseCursor(hCur));
// 關閉資料庫連接
if not (hDB = nil) then Check(DbiCloseDatabase(hDB));
// 釋放 BDE
Check(DbiExit);
end;
[/code]
------
歡迎參訪 "腦殘賤貓的備忘錄" http://maolaoda.blogspot.com/
編輯記錄
sryang 重新編輯於 2007-09-26 12:51:11, 註解 無‧
carstyc
資深會員


發表:16
回覆:254
積分:329
註冊:2003-07-18

發送簡訊給我
#16 引用回覆 回覆 發表時間:2007-09-26 13:06:53 IP:203.79.xxx.xxx 訂閱
Sryang大大您好:

對了啦,您說的這個方式應該就是我想要的。我再來試看看可不可行。


感謝您


===================引 用 sryang 文 章===================
所以,直接呼叫 BDE API 來取得資料,就沒有暫存資料的問題了。樓主可以試試看
以下程式是參考 BDE.HLP 裡面的範例寫的,確定可以執行
helper197
一般會員


發表:8
回覆:10
積分:3
註冊:2008-08-20

發送簡訊給我
#17 引用回覆 回覆 發表時間:2009-01-17 20:16:34 IP:59.112.xxx.xxx 訂閱
請教bestlong Sir 這段程式
該如何用BCB表達呢??

procedure TForm1.qyRowBeforeOpen(DataSet: TDataSet);
begin
qyRow.ParamByName('F1').AsString := qyList.FieldByName('主鍵欄').AsString;
end;

主要是這段 我看不懂 整段程序內 不需要qyRow.Next(); 嗎??

還是說只要 透過 procedure TForm1.qyRowBeforeOpen(DataSet: TDataSet);

qyList.Next 則 qyRow 自己就會 Next呢??

謝謝您


===================引 用 bestlong 文 章===================
不要妄想用一個 TQuery 來處理超大量的資料量,越多的欄位越是不可能的任務,特別是有備註欄位的資料表更慘。

你可以讓撈資料的架構作個改變來降低對記憶體的需求
假定你的三個欄位都是 int 型態然後有一個主鍵欄位
當你用一個 TQuery 來讀取一百萬筆資料全部需要的記憶體 = 一百萬 x 3 x int 欄位所需的記憶體(可能是 32bit)

改用兩個 TQuery 來處理資料來源,例如 qyList 來做資料索引只讀取主鍵欄位 qyRow 來讀取完整的資料列(依據 qyList 的紀錄位置)
這樣對記憶體的最大需求在 qyList 上就是原來單一 TQuery 的三分之一記憶體需求
至於 qyRow 只會需要一筆紀錄的記憶體需求可以忽略不計

大致程式碼結構如下
[code delphi]
procedure TForm1.FormCreate(Sender: TObject);
begin
qyList.SQL.Add('select 主鍵欄 from tbname');
qyRow.SQL.Add('select * from tbname where 主鍵欄 = :F1');
end;

procedure TForm1.qyListBeforeScroll(DataSet: TDataSet);
begin
qyRow.Close;
end;

procedure TForm1.qyListAfterScroll(DataSet: TDataSet);
begin
qyRow.Open;
end;

procedure TForm1.qyRowBeforeOpen(DataSet: TDataSet);
begin
qyRow.ParamByName('F1').AsString := qyList.FieldByName('主鍵欄').AsString;
end;

procedure TForm1.btnCopyDataClick(Sender: TObject);
begin
qyList.Open;
while not qyList.Eof do
begin
qyDist.ParamByName('FIELD1').Value := qyRow.FieldByName('FIELD1').Value;
qyDist.ParamByName('FIELD2').Value := qyRow.FieldByName('FIELD2').Value;
qyDist.ParamByName('FIELD3').Value := qyRow.FieldByName('FIELD3').Value;
qyDist.ExecSql;
qyList.Next;
end;
qyList.Close;
end;
[/code]

這樣的處理技巧相信可以解決你的問題
小傑克
資深會員


發表:5
回覆:209
積分:357
註冊:2009-02-16

發送簡訊給我
#18 引用回覆 回覆 發表時間:2009-02-16 14:42:02 IP:59.112.xxx.xxx 訂閱
如果樓上的問題只是在用TAdoQuery讀取大量資料的話,可以試試把 TAdoQuery的屬性CursorLocation 設定成clUseServer 這就可以把DataSet裡面ADOQuery1.IsSequenced  這個屬性變成false 也就是叫delphi 不要自己保留buffer去做RecNo 這樣就不會有讀到爆的問題,而且也不會有越讀越慢的狀況
保證讀取非常快
------
額有朝天骨,眼中有靈光
編輯記錄
小傑克 重新編輯於 2009-02-16 14:50:20, 註解 無‧
系統時間:2018-01-18 23:34:50
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!