Power AutomateでExcelテーブルの「最終行(最新行)」だけをメール本文に入れて1通送るつもりが、いつの間にかApply to eachが付いて行数分のメールが飛ぶ――この“あるある”を、原因から対策まで具体例つきで整理します。
よくある症状:Excelの最新行だけを使いたいのに、メールが複数通送信される
次のようなフローを作ったときに、意図しない挙動に悩むケースが多いです。
- Excel(テーブル)から「最後に追加・更新された行」だけを取り出す
- その行の値(担当者、案件名、日時、メモなど)をメール本文に差し込む
- メールは1通だけ送る
しかし実際には、Excelの「表に存在する行を一覧表示(List rows present in a table)」が返すデータが配列(複数行)であるため、メール本文で列を参照した瞬間にPower Automateが「これは複数件だね」と判断し、自動でApply to each(各行に適用)を付けます。
その結果、例えば7行取得できてしまうと、7通メールが送られる……という状態になります。
原因:Excelの「一覧表示」は“配列(value)”を返す仕様
Power AutomateでExcelテーブルを読むときの要点はここです。
| アクション | 主な戻り値 | Power Automate側の扱い | 起きやすい問題 |
|---|---|---|---|
| 表に存在する行を一覧表示(List rows present in a table) | value(行の配列) | 複数件(配列) | 列参照するとApply to eachが自動付与 |
| 行を取得(Get a row) | 単一行(オブジェクト) | 単一件 | キー列が必要(最新行の特定には工夫が要る) |
「一覧表示」の返り値イメージは、ざっくりこうです(実際はもっと情報が付くこともあります)。
{
"value": [
{ "ID": 101, "Title": "A案件", "UpdatedAt": "2026-01-04T09:10:00Z" },
{ "ID": 102, "Title": "B案件", "UpdatedAt": "2026-01-04T09:30:00Z" }
]
}
メール本文で「Title」などを動的なコンテンツから選ぶと、Power Automateは「value配列の各要素に対してTitleを取る」と解釈し、ループが付与されます。
解決の基本方針:メール作成前に“配列→1行(オブジェクト)”に確定する
Apply to eachを回避するコツは単純で、メール作成の前に次のどちらかを満たせばOKです。
- そもそも取得結果を1件にしてしまう(推奨)
- 取得結果(配列)から1行だけ取り出して変数やComposeに保持する
一度「単一行(オブジェクト)」として確定できれば、以降はそのオブジェクトのプロパティ参照になるので、メールで列を差し込んでもループが発生しません。
方法A(おすすめ):Top Count=1 + Order Byで“取得時点で1行だけ”に絞る
最短で安定しやすいのがこの方法です。「最新行」を並び順で先頭に持ってきて、先頭1件だけ取る、という考え方です。
事前準備:Excel側に“並び替え可能な列”を用意する
「最新」を判定する軸がないと、Power Automate側で“最終行”を正しく決められません。Excel側に次のどちらかを用意するのが現実的です。
| 判定列 | 用途 | 例 | ポイント |
|---|---|---|---|
| 連番(ID / No) | 追加順を確実に判定 | 1,2,3… | 重複しないように運用(手入力でもOK) |
| 更新日時(UpdatedAt) | 更新がある運用でも最新を判定 | 2026-01-04 18:30 | 日時としてソートできる形式で持つ(文字列より日時型推奨) |
手入力で更新日時がブレるのが不安なら、Power Automateで行を追加・更新するタイミングでutcNow()を入れる運用にすると安定します。
Power Automate設定:詳細オプションで絞り込む
「表に存在する行を一覧表示」を配置したら、詳細オプション(高度なオプション)を開いて、次を設定します。
- Order By:並び替え列を降順(
desc) - Top Count:
1
例:
Order By = UpdatedAt descTop Count = 1
IDで判定するなら:
Order By = ID descTop Count = 1
これで「一覧表示」自体の結果は配列のままですが、中身は1件だけになります。次に、その1件を「単一行」として確定させます。
Composeで“単一行”に確定させる(ここがApply to each回避の肝)
次に「作成(Compose)」アクションを追加し、式に以下を入れます。
first(body('表に存在する行を一覧表示')?['value'])
Order Byをdescにしているので、先頭(first)が「最新行」です。
このComposeに「最新行」や「lastRow」など分かりやすい名前を付けておくと、後が楽です。
メール本文はComposeの出力を参照する
メール送信(Outlookの「メールの送信(V2)」など)では、Excelの動的コンテンツ(一覧表示の列)を直接選ばず、Composeの出力を式で参照します。
outputs('最新行')?['Title']
outputs('最新行')?['UpdatedAt']
outputs('最新行')?['担当者']
列名にスペースや記号がある場合でも、['列名']形式なら参照できます。
この参照に統一すると、メール側にApply to eachが付く確率を大きく下げられます。
実装イメージ(フローの並び)
| 順番 | アクション | 役割 |
|---|---|---|
| 1 | (任意)トリガー:手動/スケジュール/フォーム送信など | フロー開始 |
| 2 | 表に存在する行を一覧表示(Order By + Top Count) | 最新候補1件だけ取得 |
| 3 | 作成(Compose):first(value) | 配列→単一行(オブジェクト)へ確定 |
| 4 | メール送信 | Composeを参照して1通だけ送る |
方法B:last() / first()を使って“配列の最後(または最初)”を取り出す
質問でよく挙がるのが、次のような式です。
last(body('表に存在する行を一覧表示')?['value'])
これは配列の最後の要素を取り出す式としては正しいです。ただし、実務で落とし穴になりがちなのが「その配列の順序が本当に“最新順”なのか」です。
last()が向いているケース
- 「一覧表示」の結果が、運用上の“最終行”と同じ順序で返ってくることが確実
- または、Order Byで順序を明示できていて、昇順(asc)で取得している(→最後が最新)
たとえば、Order ByをID ascにしているなら、最後が最大IDなので最新になります。この場合は、last()の考え方が合います。
安全にする:空配列対策を入れる
テーブルが空のときにfirst()やlast()を実行するとエラーになりやすいので、最低限次のようなガードを入れるのがおすすめです。
if(
empty(body('表に存在する行を一覧表示')?['value']),
null,
last(body('表に存在する行を一覧表示')?['value'])
)
この式をComposeに入れ、後続のメール送信の前に「nullだったら送らない」条件分岐を入れると、運用で止まりにくくなります。
順序が保証されないときの注意点
Excelコネクタの「一覧表示」は、ファイル状況・コネクタの解釈・テーブルの状態により、必ずしも「目に見える行順=返ってくる順」と一致しない場合があります。
このため、Order Byで並びを確定できないのにlast()に賭けるのは、長期運用ほど事故りやすいです。
結論として、方法Bを採用するならOrder Byで順序を確定するか、次の方法Cのように比較ロジックで最新を決めるのが堅いです。
方法C:Apply to eachは“使うが”、メール送信はループの外で1回だけ行う
「どうしてもOrder Byが効かない」「最新判定が少し複雑(更新日時が空の行がある等)」という場合は、Apply to eachそのものをゼロにするより、メール送信をループの外に置く方が安定します。
ポイントは、ループ内では最新行を変数に上書きしていくだけにし、最後にその変数を使ってメールを1通送ることです。
パターン1:ループの最後の行を変数に保持(順序が信頼できる場合)
- 変数を初期化(種類:オブジェクト)
例:varLatestRowを{}で初期化 - 表に存在する行を一覧表示
- Apply to each(value)内で「変数の設定」
値:item() - Apply to eachの外でメール送信(本文で
variables('varLatestRow')を参照)
このやり方は単純ですが、前提として「Apply to eachで回る順序=欲しい順序」である必要があります。順序が怪しいなら、次の比較パターンが安全です。
パターン2:IDや更新日時を比較して“最大の行”を採用(順序に依存しない)
順序に依存しない「最新判定」をやるなら、変数を2つ使うと分かりやすいです。
- varMaxId(整数):現時点の最大ID
- varLatestRow(オブジェクト):最大IDの行データ
流れは次の通りです。
- 変数を初期化
varMaxId:0
varLatestRow:{} - 表に存在する行を一覧表示(必要ならフィルターも)
- Apply to each(value)の中で条件分岐
条件例:int(item()?['ID']) > variables('varMaxId') - 条件が真なら、varMaxIdを更新し、varLatestRowにitem()を格納
- ループの外で、varLatestRowを使ってメール送信(1通)
更新日時で判定する場合は、ISO 8601形式(例:2026-01-04T09:30:00Z)で揃えると比較しやすくなります。Excelの日時型のまま扱う場合は、文字列比較で事故らないよう、Power Automate側で変換する運用も検討してください。
メール本文での参照例(変数)
variables('varLatestRow')?['Title']
variables('varLatestRow')?['担当者']
variables('varLatestRow')?['UpdatedAt']
これなら、Apply to eachが存在してもメール送信アクションはループの外に1回だけ置けるため、複数通送信の事故を止められます。
方法D:Office Scriptsで“最終行だけ”を返す(配列を扱わないのでループが消える)
少し発想を変えて、Excel側で「最終行のオブジェクト」を作って返してしまう方法です。Power Automate側では「スクリプトの戻り値=単一オブジェクト」になるため、Apply to eachを避けやすくなります。
Excel Online(Business)で使える「スクリプトの実行(Run script)」とOffice Scriptsを使う構成です(組織の設定によっては使えない場合があります)。
Office Scripts例:テーブルの最終行をオブジェクト化して返す
function main(workbook: ExcelScript.Workbook) {
const table = workbook.getTable("Table1");
const headers = table.getHeaderRowRange().getValues()[0] as string[];
const bodyRange = table.getRangeBetweenHeaderAndTotal();
const values = bodyRange.getValues();
if (values.length === 0) {
return { hasRow: false };
}
const last = values[values.length - 1];
const row: any = {};
headers.forEach((h, i) => (row[h] = last[i]));
return { hasRow: true, row };
}
Power Automate側では、戻り値のrow配下を参照してメール本文に差し込む形になります。
「Excelコネクタの一覧表示の順序が信用できない」「最新行の定義がExcel側の見た目の最終行で良い」といったケースでは、実装がスッキリします。
最新行を取り違えないための“Excel設計”のコツ
Power Automateの式や設定よりも、実はExcel側の設計で事故率が大きく変わります。運用で揉めやすいポイントを先に潰しておくのが、結局いちばん安上がりです。
おすすめのテーブル列構成
| 列名例 | 型のイメージ | 役割 | 運用の注意 |
|---|---|---|---|
| ID | 数値 | 最新判定の主キー | 重複させない(可能なら自動採番) |
| UpdatedAt | 日時 | 更新の新しさ判定 | 手入力よりフローでutcNow()を入れると安定 |
| Status | 文字列 | 送信対象の絞り込み | 例:Completedのみ送る、などFilter Queryに活用 |
| MailSent | Yes/No | 二重送信防止 | 送信後にTRUEへ更新すると安全(運用次第) |
特に「二重送信防止」のMailSent列は、フローのトリガーが定期実行だったり、再試行が走ったりする運用で効いてきます。
“最新行を送る”だけでも、運用上は「前回送った行をもう一度送らない」仕組みが求められることが多いです。
よくある落とし穴:Apply to eachが消えない/また増える
方法AやBを試しても、なぜかメールのところにループが付いてしまうときは、原因がほぼ決まっています。
落とし穴:メール本文で“一覧表示の列”を直接選んでいる
一度でもメール本文に、Excelの「一覧表示」アクション由来の列(動的コンテンツ)を直接差し込むと、Power Automateは「複数行に対して差し込む」扱いにし、ループを生成します。
対策はシンプルです。
- メール本文はCompose(または変数)だけを参照する
- 列の差し込みは、動的コンテンツではなく式(
outputs()/variables())で統一する
落とし穴:列名にスペースがあり、参照が崩れている
列名が「担当 者」などスペースを含むときは、次のように角括弧で指定します。
outputs('最新行')?['担当 者']
落とし穴:テーブルが空でfirst/lastが落ちる
実運用で「月初は空」「入力が遅れて空」などは普通に起こります。空配列チェックを入れて、空なら送らない(または“データなし”通知を送る)条件分岐にすると止まりにくいです。
if(
empty(body('表に存在する行を一覧表示')?['value']),
null,
first(body('表に存在する行を一覧表示')?['value'])
)
落とし穴:更新日時が文字列で、ソート結果が期待と違う
例えば「2026/1/4 9:5」のような形式だと、文字列としての並び替えになり、descでも期待通りにならないことがあります。
更新日時は可能ならExcelの日時型にし、フローで入れるならISO 8601(例:2026-01-04T09:05:00Z)で揃えると事故が減ります。
落とし穴:件数が多いのに“最新判定”で全件を見ていない
方法Cのように全件を走査して最新を決める場合、一覧表示アクションの既定の取得件数(ページング設定)により、途中までしか取得できていないと誤判定になります。
この場合は、アクションの設定でページングを有効にし、必要件数を取得できるようにしてください。逆に方法A(Top Count=1)なら、全件取得をしない分だけ負荷も小さく、安定しやすいです。
メール本文を“見やすく”するテンプレート例(最新行を表で差し込む)
最後に、最新行の値をメールに入れるときのテンプレート例です。Outlookの「メールの送信(V2)」はHTML本文を扱えるので、表にすると読み手が楽になります。
件名例:
【最新更新】@{outputs('最新行')?['ID']} - @{outputs('最新行')?['Title']}
本文例(HTML):
<p>最新行(最新更新)の内容です。</p>
<table border="1" cellpadding="6" style="border-collapse:collapse;">
<tr>
<th>ID</th>
<td>@{outputs('最新行')?['ID']}</td>
</tr>
<tr>
<th>タイトル</th>
<td>@{outputs('最新行')?['Title']}</td>
</tr>
<tr>
<th>更新日時</th>
<td>@{outputs('最新行')?['UpdatedAt']}</td>
</tr>
<tr>
<th>担当者</th>
<td>@{outputs('最新行')?['担当者']}</td>
</tr>
</table>
この形にしておくと、差し込み箇所がすべてCompose由来になり、Apply to eachが勝手に付いて“複数通送信”になる事故を避けやすくなります。
元のスレッドでの結論と、現場での現実解
同様の質問はコミュニティでも頻出で、元のスレッドでは採用回答が「このフォーラムの管轄ではないのでPower Automate Communityへ」という案内で終わっているケースもあります。
ただし実務上は、ここまで紹介したように「配列のままメールに渡さない」を徹底するだけで、多くのケースが解決します。
迷ったら次の順で選ぶのが無難です。
- まず方法A:Order By + Top Count=1(最短・読みやすい・負荷も軽い)
- 順序や判定が怪しければ方法C(比較して最大を採用)
- Excel側で解決したい・返す形を固定したいなら方法D(Office Scripts)
「最終行=見た目の一番下」なのか、「最終更新=UpdatedAtが最大」なのかで正解が変わるので、Excel側に判定軸(ID/UpdatedAt)を用意し、フロー側は“1行のオブジェクトに確定してからメールへ渡す”構造にすると、長期運用で壊れにくいフローになります。

コメント