機能は最低限の代物でしたので、今回は実務に耐えうるよう改良を行いました。
目標は、
- GASで必須の、コード実行高速化のため、配列に格納して処理する
- しかし、配列を一切意識したくない!
また、行削除・行追加・値入力に関して、配列で処理した後、clearContent()→setValues()で更新する方法がありますが、今回はappendRow()、deleteRow()、setValue()で直接書き換えるようにしました。理由は以下です。
- 作っているシステムが、500件超の読み込みに対し編集は10件/1回程度と少ないこと
- 共有シートを書き換えるので、もし閲覧中の人がいても、リアルタイムで更新され、clear時のホワイトアウトを発生させないため
動作しない、こんなシートに実装したいけどどうすればいいの?、こうするともっと早くなる、などありましたら、コメント・お問い合わせ(リンク)頂けると嬉しいです。
クラス本体
///// SheetWrapper Class ///// // sheetId:スプレッドシートのURLからIdを指定
// sheetName:スプレッドシートのシート名
// tableTopRow:表が始まる行を指定(1列目が項目、2列目がデータの場合、1を指定)
var SheetWrapper = function(sheetId, sheetName, tableTopRow){
// スプレッドシートのデータを二次元配列に格納。初期化・行追加・行削除以外でGoogleAppsScriptのAPI関数は使わない
this.sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName);
var values = this.sheet.getDataRange().getValues();
for(var y = 1; y < tableTopRow; y++){
values.shift(); // 余分な行を詰める
}
this.shift = tableTopRow;
this.currentY = 1; // 行方向ポインタ
this.maxX = values[0].length;
this.maxY = values.length;
// 列項目名->列毎データの連想配列を作成
this.columns = {};
for(var c = 0; c < this.maxX; c++){
var item = values[0][c]; // 列項目名
this.columns[item] = [c + 1]; // 先頭に列番号を格納
for(var r = 1; r < this.maxY; r++){
this.columns[item].push(values[r][c]);
}
}
};
SheetWrapper.prototype = {
// 表操作関数
getByItem : function(itemName){
return this.columns[itemName][this.currentY];
},
searchItem : function(itemName, val){
this.currentY = this.columns[itemName].indexOf(val);
return this.currentY;
},
moveNext : function(){this.currentY++;},
moveFirst : function(){this.currentY = 1;},
EOF : function(){
return this.currentY > this.maxY;
},
getRealRow : function(){
return this.currentY + this.shift;
},
appendRow : function(arr){
this.sheet.appendRow(arr)
var a = 0;
for(var item in this.columns){
// pushするarrの列数を揃える
arr.length = Object.keys(this.columns).length;
this.columns[item].push(arr[a]);
a++;
}
this.currentY = this.maxY;
this.maxY++;
},
deleteRow : function(){
this.sheet.deleteRow(this.getRealRow());
for(var item in this.columns){
this.columns[item].splice(this.currentY, 1);
}
this.maxY--;
},
setValue : function(itemName, val){
this.sheet.getRange(this.getRealRow(), this.columns[itemName][0]).setValue(val);
this.columns[itemName][this.currentY] = val;
}
};
Excelでも同じようなクラスを作ったことがあります(記事へのリンク)。あちらは配列に入れなくても十分な早さで動いたのですが…。モデルにしたのはADO(ActiveX Data Objects)で、 列番号や行番号を意識せずに、項目名とcurrentRecordで操作する形にしました。
また、tableTopRow=表の項目が何行目から始まるか、を最初に指定すれば、後からそのズレを意識しなくて済むようにしました。本当は1行目から始まる綺麗な表に統一したいんですけどね。セル結合を使ってゴテゴテ頭を飾るのが好きな人いるでしょ?(笑)
項目行の次にデータが来ない場合(モット汚い表)は、ifとmoveNext()で抜けるようにしてください…。
変数
- this.shift …引数tableTopRowを保持し、シートの実際の行数を計算
- this.currentY …レコードのカレントポジション。
- this.maxX …最大列数
- this.maxY …最大行数
- this.columns …表データをカラム毎の連想配列に変換した結果
関数
- getByItem(itemName) …カレントレコードの項目名itemNameの値
- searchItem(itemName, val) …項目名itemNameから値valを検索し、カレントレコードを移動
- moveNext() …カレントレコードをひとつ後に移動
- moveFirst() …カレントレコードを最初に移動
- EOF() …レコードの終わりを返す
- getRealRow() …シートの実際の行数を返す
- appendRow(arr) …行をarr配列の値で追加
- deleteRow() …カレントレコードを削除
- setValue(itemName, val) …カレントレコードの項目名itemNameに値valをセット
使用例
function getNew() {
var mySheet = new SheetWrapper('dummy_sheet_id_1', 'タスク', 6);
var doneSheet = new SheetWrapper('dummy_sheet_id_2', '完了タスク', 1);
while(!mySheet.EOF()){
if(mySheet.getByItem('完了フラグ') == '完了'){
if(doneSheet.searchItem('オーダーID', mySheet.getByItem('オーダーID')) < 0){
doneSheet.appendRow([Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd'),
mySheet.getByItem('オーダーID')]);
}
}
mySheet.moveNext();
}
0 件のコメント:
コメントを投稿