你可以將它分成兩個不同的功能。一個會查詢數據庫,另一個會將數據寫入文件。
sub run_query {
my ($sql, @args) = @_;
# if you truly want separation of concerns,
# you need to connect $dbh somewhere else
my $sth = $dbh->prepare($sql);
$sth->execute(@args);
# this creates an iterator
return sub {
return $sth->fetchrow_arrayref;
};
}
該函數接受一個SQL查詢和一些參數(記得使用佔位符!)並運行查詢。它返回代碼參考,關閉$sth
。每次調用引用時,都會獲取一行結果。當語句句柄$sth
爲空時,它將返回undef
,這是交付,你完成迭代。這可能看起來有點過分,但留在我身邊一會兒。
接下來,我們做一個函數來將數據寫入文件。
sub write_to_file {
my ($filename, $iter) = @_;
open my $fh, '>', $filename or die $!;
while (my $data = $iter->()) {
print $fh join("\t", @{$data}), "\n";
}
return;
}
這需要一個文件名和一個迭代器,它是一個代碼引用。它打開文件,然後迭代,直到沒有更多的數據。每行都寫入文件。我們不需要close $fh
,因爲它是一個詞法文件句柄,一旦$fh
在函數結束時超出範圍,它將被隱式關閉。
你現在所做的是定義一個接口。你的write_to_file
函數的接口是它需要一個文件名和一個總是返回字段的數組引用的迭代器。
讓我們把它放在一起。
my $iter = run_query('SELECT * FROM orders');
write_to_file('orders.csv', $iter);
兩行代碼。一個運行查詢,另一個寫入數據。看起來相當分離。
這種方法的好處是,現在您還可以使用相同的代碼將其他內容寫入文件。下面的代碼可以與一些API交談。它再次返回的迭代器爲每次調用提供一行結果。
sub api_query {
my ($customer_id) = @_;
my $api = API::Client->new;
my $res = $api->get_orders($customer_id); # returns [ {}, {}, {}, ... ]
my $i = 0;
return sub {
return if $i == $#{ $res };
return $res->[$i++];
}
}
您可以刪除該到上面的例子,而不是run_query()
和它的工作,因爲這個函數返回的東西,堅持同接口。您可以製作具有相同部分接口的write_to_api
或write_to_slack_bot
函數。其中一個參數是同一種迭代器。現在這些也是可以交換的。
當然這整個例子是非常人爲的。實際上,它高度依賴於程序的大小和複雜性。
如果它是一個腳本,作爲一個cronjob運行,除了每天創建一次這個報告,你不應該關心這種關注的分離。務實的做法可能是更好的選擇。
一旦你有很多這些,你會開始關心更多。那麼我的上述方法可能是可行的。但只有當你真的需要靈活的東西。
不是每個概念總是適用的,並不是每個概念總是有意義的。
請記住,有些工具更適合這些工作。您可以使用Text::CSV_XS而不是製作自己的CSV文件。或者,您可以使用ORM,如DBIx::Class,並將ResultSet對象作爲您的接口。
_lots_多少錢?你的代碼中有一些古怪的語法。如果你所做的只是傳遞一個變量,你不需要在變量周圍加引號'「」'。 Perl足夠聰明地優化它,這樣''$ sql_data「'實際上並不插入到字符串中,但是執行'foo($ sql_data)'更容易閱讀,而且更少的字符要寫入。它也更簡潔。 – simbabque
@ dan1111:不,目前他每次處理一行數據。只有一行存儲在內存中。如果他想讀取所有數據,他需要構建另一個包含'@ data'所有不同副本的數據結構。 –
@DaveCross,謝謝,我的錯誤 - 自從我使用DBI以來已經有一段時間了。 – 2016-11-07 11:09:35