Get a Row by ID: Missing Fails the Run, Empty List Rows Does Not
In this series: Power Automate gotchas
The warehouse posted customer US-001. The flow took first() of yesterday's List rows, stuffed that GUID into Get a row by ID, and turned red. The account was never in this environment. Get a row did what it says: it asked Dataverse for one primary key and the key was not there. The "create the account, then the order" branch never ran, because a failed action stops the path. List rows with zero matches would have succeeded with an empty value array.
That is the whole trap. Get a row is not find. It is a primary-key read that must hit.
Two actions, two meanings of "nothing"
Get a row by ID needs a table and a Row ID. Row ID is the GUID (accountid, contactid, the primary key). An account number, an email, or a name is the wrong field. If that GUID does not exist, Dataverse answers not found and the connector marks the action Failed. The run is failed unless you attached a run-after path that expected failure.
List rows is a query. Filter rows is OData evaluated by Dataverse, the same idea as Filter rows on the trigger. No matches is still a successful call. The body is an array named value with length zero.
Get a row by ID
Table name Accounts
Row ID 00000000-0000-0000-0000-000000000001
→ Failed. There is no row with that id.
List rows
Table name Accounts
Filter rows accountnumber eq 'US-001'
→ Succeeded. body/value is [].
empty(outputs('List_rows')?['body/value']) is true in the second case. A Condition can take If yes and create. Get a row gives you nothing to Condition on: the action already failed.
first() does not save you. first of an empty array is empty. Feed that empty value into Get a row and you fail one step later, with a worse message, because now the Row ID was blank rather than a GUID you could search for.
SharePoint is the same shape. Get item wants the list item id. Get items with a Filter Query that matches nothing succeeds with an empty array. Do not copy a title into Get item's Id field.
Look up with List rows, then branch
When the caller has a business key — account number, email, external id — List rows is the lookup. You already have the row in first(outputs('List_rows')?['body/value']) when the array has one element. A second Get a row is a round trip you do not need unless you must change Select columns or Expand Query after you know the id.
List rows
Table name Accounts
Filter rows accountnumber eq '@{triggerBody()?['accountNumber']}'
Row count 2
Condition: empty(outputs('List_rows')?['body/value'])
If yes: Add a new row (Accounts)
then use that accountid
If no: Condition: length(outputs('List_rows')?['body/value']) is 1
If yes: use first(... )?['accountid']
If no: Terminate Status = Failed
Message = duplicate accountnumber
Row count 2 is deliberate. You need to know whether the business key is unique. first() of two rows is an arbitrary row. If account number is supposed to be unique, make it an alternate key and fail closed when List rows returns more than one.
Filter rows is an OData string, not a workflow expression. statuscode eq 1 is right. @equals(item()?['statuscode'], 1) is Filter array syntax and does not belong in this box. Interpolate the incoming value into the string, as in the example. If that value can contain a quote, sanitize it before you concatenate; a broken OData filter is a failed List rows, which is a different failure than empty.
When Get a row is the right call
You already hold a GUID from this run: the trigger body's accountid, an outputs('Add_a_new_row')?['body/accountid'], a lookup column's _parentid_value. Primary key, one row, Get a row. It is cheaper than List rows and it does not pretend the row might be missing.
Use Select columns on that Get. It is a payload trim — logical names, comma-separated — not the trigger setting that decides whether the flow starts. Expand Query belongs here when you need a related contact in the same call. If the GUID came from List rows, set Expand Query on List rows instead and skip Get.
A lookup column that is empty is not a GUID. triggerBody()?['_parentid_value'] can be empty. Passing it to Get a row fails the run. Check empty first, then Get.
Condition: empty(triggerBody()?['_parentid_value'])
If yes: the parent was not set; do not Get
If no: Get a row by ID Row ID = triggerBody()?['_parentid_value']
Failed Get is not a clean "else"
You can put Add a new row after Get a row with Configure run after = has failed. That looks like find-or-create. It is not. Failed also means throttled, forbidden, a deleted table name, a bad GUID format, or a transient 5xx. Your create path then runs because the read was unauthorized, and you invent a duplicate under a caller who should have been rejected.
List rows plus empty is a successful query with a boolean. Keep Get a row's failed path for unexpected errors, inside a scope, not as the "row does not exist" branch.
Retries do not create the row. A not-found is not a blip. Raising the retry count on Get a row only delays the red run. Waiting for another process to insert the row is a Do until on List rows (or a trigger on that table), not retry settings. Those retries are for transient connector failures.
Empty list versus a null list
List rows that ran and matched nothing: value is []. Apply to each over that array succeeds with zero iterations. That can hide a missing customer if the next action after the loop is "send confirmation" — the same class of quiet success as a Do until that hit count. Test empty before the loop when zero rows is a business problem.
List rows that never ran — skipped Condition, failed previous action — has no value. Apply to each over a null input fails. coalesce(outputs('List_rows')?['body/value'], json('[]')) makes the loop see an array. It does not decide whether zero rows is acceptable. That is still your Condition.
? on the path keeps a missing property from exploding the expression. It does not turn a failed Get a row into a blank. The action status is already Failed. Safe navigation is for optional fields on a payload you have, not for a primary-key miss.
What to put in the Row ID box
- A GUID this run already owns: trigger, a create output, a lookup
_…_valueyou checked is not empty. - Not an account number, email, or name. Those go in List rows Filter rows, or in an alternate-key upsert.
- Not
first(...)of a list you have not proven has length one. - Not a GUID copied from a maker environment into production. Ids are per organization.
Get a row by ID is the right tool when the primary key is known and the row is supposed to exist. Missing then should fail the run. When the caller has a business key, and missing is a normal branch, List rows is the lookup: success with an empty array, a Condition on empty, and a create or a terminate you chose on purpose.
Keep reading
When an HTTP Request Is Received: The Callback URL Is a Secret
The HTTP trigger URL carries a SAS signature. Anyone with it can start the flow. Restrict who can call, always Respond, and never treat that URL as public.
Power Automate Do Until: The Loop That Succeeds After Giving Up
A Do until that hits count or timeout still succeeds. Put Get inside the loop, add a Delay, and test the condition after the loop exits.
Power Automate Trigger Conditions: The Run That Never Appears
A trigger condition that is false does not start a run and does not leave run history. How the expression differs from a Condition action, and how to see the skipped checks.
Power Platform September 2026 Update: The App-Builder Skill, Agent Plugins, and the Refreshed Model-Driven UI
What matters in Microsoft's September 2026 Power Platform feature update: the model app-builder skill goes GA, the canvas authoring agent plugin goes GA, generative pages embed in forms and reach connectors, model-driven header refresh is GA with display density in preview, and Power Automate gets server-side search.
Dataverse Triggers in Power Automate: Select Columns or the Flow Wakes Up for Nothing
How Select columns and Filter rows on a Dataverse trigger cut wasted runs, and the row-version mistakes that make a flow miss the change it was built for.
Retry Policies in Power Automate: What Actually Happens When an Action Fails
How Power Automate retry policies work, which errors they cover, how to configure them, and how to keep retries from duplicating side effects.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
- No comments yet. Be the first.