質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.44%
Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

Q&A

解決済

3回答

982閲覧

【GAS】特定の値がある列を含んだ行を削除したい

kamaboco3

総合スコア2

Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

0グッド

1クリップ

投稿2023/03/22 09:53

実現したいこと

特定の値がある列を含む行を削除したいのですが、
不要な箇所が削除されたり1行しか削除されない等うまくいきません。
プログラミング初心者で初めてGASを触ります。
ご助力をお願いします。

前提

ヘッダーが5行あります。
例としてB~C列に「1」がある行を削除したいです。

列1列2列3
りんご01
みかん10
なし00
とまと01

該当のソースコード

function myFunction() {
let ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
let lastRow = ss.getLastRow();
let range = ss.getRange(6,2,lastRow-5,2).getValues().flat();
console.log(range);
for(let i = range.length -1; i >= 0; i--){ 
console.log(range[i]);
if(range[i] === 1){
console.log("ifに入ったよ");
ss.deleteRow(i + 1);
Logger.log(i);
}
}
}

試したこと

上記を実行したところ、
7行目が削除され、ヘッダーである4行目5行目も削除されました。
ヘッダーは削除されず、「1」がある行のみ削除したいです。

いろいろ調べて参考にしたのですが、おそらくif文が正しくないのだと思いますがどう修正したらいいかわかりません。

また、プログラミング初心者なのでfor文の「range.length -1」や「ss.deleteRow(i + 1)」の-1、+1が理解できていませんので、
こちらについても説明願いたいです。

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

解説は最初の回答にコメントで付記しましたので、
追加の回答です。
初心者の方の質問でよくあるのが、例では列は2つだけでしたが、実際には多くの列があって、
B列とC列だけを比較するのではなく、データの全列を比較したいというものです。
念のためそういう場合のスクリプトを for(;;)文を使った形でお示しします。

なお、今回の例のように2列の比較でもそれ以上の列でも1列でも
同じように値が1の列があれば、その行を削除します。

GAS

1//ヘッダー行より下の行のうちヘッダー列より右の列が1の行を削除 2function deleteSpecifiedColumnRow() { 3 const numHeaderRow = 5;//←ヘッダーの行数 4 const numHeaderColumn = 1;//ヘッダーの列数 5 const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); 6 const lastRow = sheet.getLastRow(); 7 const lastColumn = sheet.getLastColumn(); 8 const range = sheet.getRange(numHeaderRow + 1, numHeaderColumn + 1, lastRow - numHeaderRow, lastColumn - numHeaderColumn).getValues(); 9 // console.log(range); 10 //行数分繰り返す 11 for (let i = range.length - 1; i >= 0; i--) {//range.lengthは二次元配列の行(外側の要素)の数 12 console.log(range[i]); 13 //列数分繰り返す 14 for (let j = 0; j < range[0].length; j++) {//range[0].lengthは二次元配列の列(内側の要素)の数、ここは < で比較しているので1は引かない 15 if (range[i][j] == 1) {//値が 1 の列があるとき 16 console.log("ifに入ったよ"); 17 sheet.deleteRow(numHeaderRow + i + 1); 18 console.log((numHeaderRow + i + 1) + '行目を削除しました。'); 19 break;//1がある列をみつけたのでそれ以降の列は検索しない 20 } 21 } 22 } 23}

投稿2023/03/23 01:10

YellowGreen

総合スコア740

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

コメントのプレーンテキストではわかりにくいと思い、こちらでご返事します。

>hogehoge.length == 10
なので1引くと「hoge8」からループが開始されるのかなと思いました

について

まず、前提として

for (①; ②; ③) {

}

と書いたときの実行順序ですが、
①→②→④→③→②→④→③→②→④→③→②→④→③と②→④→③が続き③のあとで②がfalseなら終了します。

そして、
range.length==10のときの
for (let i = range.length - 1; i >= 0; i--) {
console.log(range[i]);
}
での各値は、

何周目range.length の値renge.length - 1 の値i の値range[i]は何番目の要素
1周目109910
2周目10989
3周目10978
4周目10967
5周目10956
6周目10945
7周目10934
8周目10923
9周目10912
10周目10901
11周目109-1該当なし

となり、11週目は i >= 0がfalseで実行されません。

次に、

deleteRowの引数は削除したい行だと思っていたのですが、
この内容だとヘッダー分とi(range.length - 1)も削除されるのではないかと疑問が残りました。

についてですが、
deleteRow()の引数は、おっしゃるとおり削除したい行(1始まりの行番号)です。
for (let i = range.length - 1; i >= 0; i--) {
...
for (let j = 0; j < range[0].length; j++) {
...
sheet.deleteRow(numHeaderRow + i + 1);
}
}
のときのunmHeaderRow + i + 1 の値は、
(j の値は関係ないので省きます。)

何周目numHeaderRow の値i の値numHeaderRow + i + 1 の値引数が表す行番号
1周目591515
2周目581414
3周目571313
4周目561212
5周目551111
6周目541010
7周目5399
8周目5288
9周目5177
10周目5066
11周目5-15ヘッダー行

となり11週目は実行されないのでヘッダー行は削除されません。

投稿2023/03/23 23:57

編集2023/03/24 00:06
YellowGreen

総合スコア740

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YellowGreen

2023/03/24 00:00

解決できない疑問が残るようなら、遠慮なくコメントしてください。
kamaboco3

2023/03/24 14:50

とても丁寧に説明していただき、ありがとうございます! for文の書き方はいろいろ調べて学びましたが、動きは全く無知でしたので納得しました。 今回のss.deleteRow(unmHeaderRow + i + 1)は、 for文の②がtrueの時15行目が削除⇒③へ移行、 ②がfalseの時、④は実行されず終了する(結果ヘッダーも削除されない)ことを理解しました。 また、「unmHeaderRow + i + 1」と+1にした点も、 for文でrange.lengthでマイナスしているからということも理解しました。 しかし、ここまで丁寧に説明いただいたのにまだわからない点があったので質問します。 for文でiに代入するために「range.length」に「-1」する理由がまだわかりませんでした。 表で作っていただいた「range[i]は何番目の要素」は9にならないのはなぜでしょうか? 「range[i]」は、あくまで「hogehoge.length」を見ている、ということでしょうか。 ※実際にマイナスにせず「for( let i = range.length ;~)」にすると正常に動作しませんでした。 今回の例では「range.length == 10」で10番目の要素(hogehoge[9])が最後で、 >i の初期値を配列の個数から 1 を引いた数、すなわち最後の要素を示す数 に対し、 >renge.length - 1 の値:9 と9番目の要素になっているように思いました。 とても初歩的で申し訳ないのですが、 私の認識を正したいのでご教示いただけたら幸いです。
YellowGreen

2023/03/24 20:52

シートの行番号は見てわかるとおり1から始まります。 スクリプトで複数のセルの値を変数に代入するとその変数は値の配列になります。 スクリプトでは、配列の要素を特定するにはその配列に[]をつけて[と]との間に要素のインデックス番号を入れて指定します。このときのインデックスは、0から始まることになってます。 なので配列range = [‘a', 'b', 'c'] があるときに、range[0] == 'a'でありrange[1] == 'b'そしてrange[2] == 'c'です。 この配列の要素は3つです。なのでrange.length == 3になります。 さて、for文でこの配列の最初の要素から始めて最後の要素まで同じ処理を繰り返したいときは、どう書くでしょう。 最初の要素を指定するときはrange[0]なので、 for (let i = 0; i < range.length; i++) { 処理の内容 } になります。range.length - 1としていないのは、比較の条件が <= ではなくて < だからです。 では、逆順に最後の要素から最初の要素まで同じ処理を繰り返したいときは、どう書きますか? for (までは同じです。 次に i の初期化が必要です。 先ほどは、let i = 0 として最初の要素を指定しました。 今回は最後の要素を指定することになります。 最後の要素を指定するにはどうすればいいでしょうか。 わかっているのは要素の数です。 でも、そのまま[と]の間に要素の数を入れてしまうと上で確認したようにrange[3]はありません。 要素の数(3)から2を得たいのです。
YellowGreen

2023/03/24 21:25

次に、ではなぜ配列の要素は1ではなく0から始めることにしてあるのでしょうか。 スプレッドシートのシートの行番号は1から始まるのだから合わせてくれた方がわかりやすいですよね。 たぶんその理由は、もともとプログラミング言語が考案された時から数を0から数えていたからだと思います。 スプレッドシートのようなアプリケーションができる前からプログラミング言語で数を数える時に0から始めるのが都合が良かったからでしょうか。コンピューターが扱う2進法で0は変数を何桁にしても全ての桁を0で埋めるだけで表現できますから。 スプレッドシートのようなアプリケーションは、プログラミング言語を知らない人が使いますから、0始まりでは違和感があるので1始まりにしてプログラミングをする側が1を足したり引いたりして違いを調整することになったんだと思います。 あくまでも「思います。」ですが。
YellowGreen

2023/03/24 22:09

プログラミング言語で数を数える時に0から始めるのが都合が良かったからでしょうか。コンピューターが扱う2進法で0は変数を何桁にしても全ての桁を0で埋めるだけで表現できますから。 この部分は、そうではなく、例えば2進法で8桁で表現できる数字が、符号なしの場合0から255までだっので自然に0始まりになったのかもしれません。
kamaboco3

2023/03/26 11:50

初歩的な質問に対しても丁寧にご教示いただき、大変ありがとうございました! for文で最後の要素から書く際のセオリーが理解できました! 「let i = hoge」のhogeにはインデックス番号が代入されるから、 >そのまま[と]の間に要素の数を入れてしまうと上で確認したようにrange[3]はありません。 >要素の数(3)から2を得たいのです。 を行うのにマイナスにすることが漸く理解できました。 比較演算子についても、もっと勉強したいと思います。 この度は本当にありがとうございました。
YellowGreen

2023/03/26 11:59

そうですね。逆順の処理は、行削除の際に採用されることの多い処理ですが、それ以外の処理が付随するような時に非常に読みにくいコードになります。 私は、普通に正順 for (let i = 0; i < hoge.length; i++) { としておいて、 if (hoge[i] == geho) { deleteRow(i + 1); i--; } } と行削除と一緒に i も減算する処理の方がわかりやすいと思います。
guest

0

値を取得するセル範囲や削除すべき行番号が明確になるように
あらかじめヘッダーの行数を変数に代入しておくとコードが読みやすくなります。

セルの値を配列に取得して一次元配列に変換するコードになっていますが、
行単位で複数の列の値を比較したいので二次元配列のままにしておきます。

元のスクリプトに沿って、変数宣言や変数名を少し修正したコードの例です。

GAS

1//ヘッダー行より下の行のうちB列またはC列の値が1の行を削除 2function deleteSpecifiedRow() { 3 const numHeaderRow = 5;//←ヘッダーの行数 4 const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); 5 const lastRow = sheet.getLastRow(); 6 const range = sheet.getRange(numHeaderRow + 1, 2, lastRow - numHeaderRow, 2).getValues();//←.flat()を削除 7 // console.log(range); 8 for (let i = range.length - 1; i >= 0; i--) { 9 console.log(range[i]); 10 if (range[i][0] == 1 || range[i][1] == 1) {//←2つの列を比較して行数を得たいので二次元配列のまま比較 11 console.log("ifに入ったよ"); 12 sheet.deleteRow(numHeaderRow + i + 1); 13 console.log((numHeaderRow + i + 1) + '行目を削除しました。'); 14 } 15 } 16} 17

投稿2023/03/22 23:59

YellowGreen

総合スコア740

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YellowGreen

2023/03/23 00:30

はじめにfor文について 行番号は、10行目までなら、1,2,3,...10と1から始まる数(行番号)で特定できますが、 配列は、要素が10個のときは、0,1,2,...9と0から始める数(インデックス)で特定できます。 また、配列内の要素の数は、配列名.lengthで取得できます。 配列hogehogeに10個の要素があるときは、hogehoge.length == 10ですが、 配列内の要素はhogehoge[0]が最初の要素、hogehoge[9]が最後(10個目)の要素です。 このようなことから、 forループの中で条件の指定は for (i = range.length - 1; i >= 0; i--) { としているのは、 i の初期値を配列の個数から 1 を引いた数、すなわち最後の要素を示す数としておき、 ループを繰り返すごとに i を 1 ずつ減算し、 i が 0 以上であるならループ内の処理を繰り返す という条件になっています。 次にdeleteRowについては、 まず、rangeを二次元配列のままとしているので、 その要素は [ [0, 1],←りんごの行 このうち、range[0][1]が0 range[0][1]が1 [1, 0],←みかんの行 [ 0, 0]←なしの行 [ 0, 1]←とまとの行 このうちrange[3][0]が0 range[3][1]が1 ] となっているので、 上記のとおり配列の要素を示す数(インデックス)が0から始まることから deleteRowの引数とする行番号を得るには、ヘッダーの行数にインデックスの i を加算して、 行が1から始まるのでさらに1を加算する必要があります。 繰り返すと const values = sheet.getRange(2, 1, 10, 2).getValues(); などとしてB1:C10のセル範囲の値をvaluesという二次元配列に取得したときに 配列の i 番目の要素は、i + 1 番目の行の各列の値になるということです。 values[9][1]がC10セルの値になります。
YellowGreen

2023/03/23 05:24

変数名を変えているのは、 ss は「ス」プレッド「シ」ート(エクセルでのブック)を代入する変数とする方がコードを読みやすい。 sheet はスプレッドシートに含まれる個別の「シート」を代入する変数とする方がコードが読みやすい。 letをconstにしているのは、変数の値を後から変更する(宣言(初期化)以外の場所で「変数 = xxxx;」とする)なら let しないなら const にしておくと、後々、コードを修正したときに既に初期化済みのconstに値を代入するコードを書くとエラーになるので、バグを未然に防ぐことができる(代入すべきときは、宣言をletに変更する)。 などの理由です。
YellowGreen

2023/03/23 05:27

繰り返すと const values = sheet.getRange(2, 1, 10, 2).getValues(); などとしてB1:C10のセル範囲の値をvaluesという二次元配列に取得したときに 配列の i 番目の要素は、i + 1 番目の行の各列の値になるということです。 values[9][1]がC10セルの値になります。 は誤りでした const values = sheet.getRange(1, 2, 10, 2).getValues(); として取得したときでした。
kamaboco3

2023/03/23 13:02

大変丁寧に説明いただき、本当にありがとうございます! おっしゃるとおり実際には比較したい列が例より多く、 ヘッダー列もあるので追加で回答いただいたスクリプトを参考にしたところ、正常に削除されました。 本件とずれる質問でしたら申し訳ないのですが、 少し理解しきれなかった点があったので宜しければご教示いただけますでしょうか。 ・for文 >i の初期値を配列の個数から 1 を引いた数、すなわち最後の要素を示す数としておき 例えば配列が10あるときの場合は前述のように今回は「i = hoge.length -1」と書きますが、 >hogehoge.length == 10 なので1引くと「hoge[8](9個目)」からループが開始されるのかなと思いました(動作は正常なので単純な疑問です)。 また、「i >= 0」や「let j = 0」の0は配列の0番目という意味で認識に間違いはないでしょうか。 ・deleteRow 今回、「sheet.deleteRow(numHeaderRow + i + 1)」とご指摘いただいて正常に動きました。 deleteRowの引数は削除したい行だと思っていたのですが、 この内容だとヘッダー分とi(range.length - 1)も削除されるのではないかと疑問が残りました。 プログラミング初心者で引数について中途半端な知識になっているので正したいです。 初歩的で大変申し訳ないのですがご教示いただけたら幸いです。 本件とずれていれば、別途質問したいと思います。
YellowGreen

2023/03/23 23:59

ここでは、表組みが使えないので、 新たな回答としてコメントしていますので、 そちらをご覧ください。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.44%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問