I am looking to autogenerate consecutive numbers per group by an attribute form default expression whilst completing a survey using QField for QGIS.


  • A job number will only be created when a separate column, "SpanClear" = No.
  • I also require the job numbers to reset to 1 for each unique group in the column named "CircuitID".
  • As this is a survey to be completed in QField, the attributes start off empty/null and will be populated on a mobile device
  • Each time a job is identified (by "SpanClear" = 'No'), the jobs should be consecutively numbered, 1, 2, 3... etc.
  • I require an expression to add in the default value of the attributes form (if there is a better way, please let me know)

I have searched through many previous questions and although there are workarounds using plugins, virtual layers and tools in QGIS, I require an expression so it will work in QField.

I want to automate the numbering to reduce chances of errors during surveying.

The expression I have created so far is:

    WHEN "SpanClear" = 'No'
    "JobNo" is NULL
    THEN $id=array_first(array_agg($id, "CircuitID"))
    WHEN "JobNo" is not NULL
    THEN  array_agg((maximum("JobNo")+1),"CircuitID")

The above expression names the first job per group "1", but any following jobs are set to "0".

Is what I am asking for even possible through an expression? I'm not a developer so I may have missed something obvious.

How it will look in QField

Table to autogenerate consecutive job numbers per group BY EXPRESSION for surveying in QField for QGIS

How the current expression is autopopulating

Table when autopopulated using current expression

How the expression looks in Attributes Form

Expression in dialog box


1 Answer 1


I am not sure what you meant to achieve with the $id=array_first(array_agg($id, "CircuitID")) part of your expression, as that's what is returning the 1 and 0 values.

It's checking whether the current feature's $id value matches the first $id value in the same CircuitID group (according to no particular order...). If it does - it returns a 1 (true), if not, a 0 (false). Only one feature can result in a match, hence why you start with one 1 and all the rest in that CircuitID group have a value of 0.

I presume you meant to use that as a condition for another expression that would generate the incremental value, but because you preceded it with THEN - it's actually returning the check itself.

This default value expression in the JobNo widget will get you started:

CASE WHEN "SpanClear" = 'Yes' OR "SpanClear" IS NULL 





The first 3 lines should be self-explanatory, but here is what is happening with line 4 working inside out:

  1. maximum() function. You don't need to use the array_agg() function here as maximum() can already aggregate/group by within the function itself. This finds the maximum value for JobNo within the group that has the same CircuitID as your current feature.

  2. The second coalesce() function. Checks if the result of step 1 is NULL, and if it is, returns 0 instead. This is for situations where there is no maximum() because you are entering a new CircuitID (so no existing features to group), or the existing CircuitID group has not been updated (so all JobNo values in that group are NULL and a maximum cannot be calculated).

  3. +1 - this increments the result of step 2 (which is either the maximum JobNo for your CircuitID group, so you go one up, or it's 0, so you start your sequence at 1).

  4. The first coalesce() function. Checks if JobNo already has a value in it - if it does, then it leaves it alone. Otherwise, it applies the above sequence. This is so that if you check 'Apply default value on update' in the widget, and you go back to a feature that already has a JobNo applied (say job number 2 out of a current max of 4 in that CircuitID group), if you update a completely unrelated field like comments, it doesn't recalculate the JobNo (overwrite the existing value of 2 to 5, the new maximum + 1).

Make sure your form is properly designed so you make sure CircuitID and SpanClear cannot be null for a form to be saved.

You may need to tweak the expression depending on how you want your form to behave when you go back and edit records.

One option may be to disable 'Apply default value on update' so that once the JobNo is calculated the first time, it is locked in place.

But what if you initially said SpanClear = No for a record and now you want to revise that to SpanClear = Yes (or vice-versa?)

If it already had a JobNo do you want to retain it overwrite it with NULL? If the latter, do you want all the other JobNo values in that CircuitID group to readjust (in which case you'll need a virtual field)? or are you ok with there being a gap in the sequence? etc.

  • This worked perfectly, thank you. Thanks for taking the time to also explain how it works. I am just starting out in writing expressions so I was taking code from other answers but not understanding what the code was doing.
    – ddSWL
    Commented Mar 17, 2023 at 12:04
  • Regarding your questions on overwriting the job number, I think disabling the 'Apply default value on update' would be the best option. I know that jobs can be missed, however, and will be required to be added at a later time. This means job numbers are not always geographically linear. Thank you for your help, I will test in the field and provide feedback.
    – ddSWL
    Commented Mar 17, 2023 at 12:15
  • No worries, it is always easier to help when the question has all the info needed in it including screenshots. When you test the expression out and you find this answer solves your question, don't forget to mark it as answered to help others down the track when they have the same issue!
    – she_weeds
    Commented Mar 18, 2023 at 0:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.