The recipe rows land out of order because the block transaction group places rows by the auto-number index column (steps_ndx). It does not place them by the recipe step number. When you edit step 2 and it is re-inserted as auto number 35, it moves to the bottom of the block. Fix the order in the database, where the data is still rows. Add integer step and substep columns, or an explicit sequence column, and read in that order. Sorting ~1000 tags in a script after the transaction is a fallback only, and it corrupts data if it writes back into tags the group owns. The PLC AOI needs no change for either approach.
Symptom patterns in database-backed recipe step tables
A block transaction group maps a set of database rows to an array-like block of tags, one row per block position. Every ordering symptom below comes from one of two faults: the row index is used as the order, or the step key is stored as a floating-point number.
| Observed symptom | Root cause | Corrective action |
|---|---|---|
| A newly created recipe displays in the correct order | Rows were inserted in step order, so the auto numbers happen to rise with the step numbers | None needed. The order is correct by coincidence. |
| An edited step (for example step 2) moves to the last row after the transaction | The edited row was re-inserted with a new auto number (35), and the block order follows steps_ndx
|
Order by the step key, not by the primary key |
| The recipe-vs-PLC comparison table flags mismatches after an edit, although the content is identical | The comparison is positional, and the tag positions shifted | Fix the source order, or compare by step key |
Steps 1.1 and 1.10 cannot be told apart |
A REAL/float column stores both as the same value | Store the step as integer major/minor columns, or as a string |
1.10 sorts before 1.9
|
Numeric ordering of a hierarchical key | Use an integer substep column |
| Sorted tags revert to the old order a few seconds later | The group executes again and rewrites the block in index order | Sort at the source, or write the sorted data to a separate tag set |
Row order, auto-number keys, and REAL step values
A SQL table is an unordered set. The only order you can rely on is an explicit ORDER BY in the query that reads it. An auto-number primary key identifies a row. It says nothing about sequence. It increases with insertion time, so any edit that deletes and re-inserts a row, or appends a replacement row, gives that step the highest index in the table. A block group keyed on steps_ndx then fills block positions in index order:
| Block position | After edit (by steps_ndx) |
Required (by step) |
|---|---|---|
| 1 | 1.01 (ndx 1) | 1.01 (ndx 1) |
| 2 | 1.02 (ndx 2) | 1.02 (ndx 2) |
| 3 | 1.03 (ndx 3) | 1.03 (ndx 3) |
| 4 | 3 (ndx 5) | 2 (ndx 35) |
| 5 | 4 (ndx 6) | 3 (ndx 5) |
| 6 | 2 (ndx 35) | 4 (ndx 6) |
The step number column is a second, separate problem. 1.01, 1.02, 1.03 is a two-level key: top-level step 1, substeps 01 through 03. A REAL cannot represent that. It stores 1.1, 1.10, and 1.100 as the same number. It orders 1.10 ahead of 1.9. Most decimal fractions also have no exact binary representation, so equality tests on the column are unreliable. With the current data, numeric ordering happens to give the right answer, because the editor used a consistent two-digit substep. That holds only as long as nobody enters 1.1 next to 1.01.
Constraints imposed by the PLC recipe AOI
The PLC side fixes what the database may change:
- The download script transposes the recipe tags into PLC arrays. The array index is the download order, so the row order in Ignition becomes the execution array order.
- The PLC receives only the integer step. The example recipe downloads as
step[1] = 1,step[2] = 1,step[3] = 1,step[4] = 2. - The AOI compares each element's step number against a counter. All elements that share an integer step activate together, so 1.01, 1.02, and 1.03 run as one step 1.
- The fractional part exists only to set display and download order. Its value is arbitrary and chosen by the recipe editor.
- Changing the AOI requires a PLC download and therefore a shutdown. No shutdown date is scheduled.
Any fix must therefore keep three outputs unchanged: the integer step value per element, the element order, and the array layout the transposition script produces. Splitting the step into major and minor integer columns meets all three. The major value is exactly what the PLC already receives, and the minor value never leaves SQL.
Step and substep columns with an ordered sequence
Two integer columns make ordering trivial in both SQL and Python. A third column, a sequence number (a dense 1..N position within one recipe), gives the block group a stable key that already matches the display order. The column names below are examples. The SQL shown is Microsoft SQL Server syntax, so adapt it for your database engine.
- Add the columns:
ALTER TABLE recipe_steps ADD step_major INT NULL, step_minor INT NULL, seq INT NULL; - Backfill from the existing REAL column. This assumes every stored substep uses exactly two decimal places, as in
1.01. Check for any values that break that assumption before you trust the result.UPDATE recipe_steps SET step_major = FLOOR(step_no), step_minor = CAST(ROUND((step_no - FLOOR(step_no)) * 100, 0) AS INT); - Renumber the sequence within each recipe. Put
steps_ndxlast in the ordering so ties resolve the same way every time: - Run the renumber statement on every recipe save, as part of the same button-triggered action that performs the edit. Run it before the block transaction reads the rows.
- Point the block group's row index at
seqinstead ofsteps_ndx. Leavesteps_ndxas the primary key. Never renumber or reuse the auto-number key to force order. - If the group cannot key on a custom column, bind the display table and the download script to a query with
ORDER BY step_major, step_minor. The block tags then stop being the source of order. - In the download transposition, send
step_majoras the PLC step value. For the example recipe this gives the array1, 1, 1, 2, 3, 4, which is identical to today's download. - Change the recipe editor to write
step_majorandstep_minordirectly. Keep the REAL column only until nothing reads it.
Script-side sort of the recipe tag structure
Sort in a script only when the database schema is frozen. Keep three rules. Read the whole block in one bulk call, not ~1000 individual reads. Sort rows, not individual tags. Write the result to a separate display/download tag set, never back into the tags the block group is bound to. If the group is bidirectional, writing sorted values into its tags pushes step 2's data into the row keyed steps_ndx 5, and so on. That overwrites database records with the wrong content. If the group runs on a timer, it also overwrites the sorted order on its next execution.
If the step is later stored as a string such as "1.10", replace the float key with a split key: major, _, minor = s.partition("."), then return (0, int(major), int(minor or 0)). That key orders 1.9 before 1.10. Trigger the script from the group's completion or handshake signal, not from the same button press. Otherwise the script can read the block before the transaction has written it.
Checks on the reordered recipe and PLC download
- Query the edited recipe with
ORDER BY seq. Expected: 1.01, 1.02, 1.03, 2, 3, 4, withseq1 through 6 andsteps_ndx1, 2, 3, 35, 5, 6. - Check
step_major/step_minoragainst the REAL column for every row. Expected: (1,1), (1,2), (1,3), (2,0), (3,0), (4,0). A row showing (1,10) where you expected (1,1) marks a stored1.1that needs re-keying. - Look for duplicate sequence numbers within one recipe. Expected: zero rows returned by a
GROUP BY recipe_id, seq HAVING COUNT(*) > 1query. - Trigger the block transaction and read the block tags. Expected: block position 4 holds the step 2 data from row 35.
- Trigger the transaction a second time without editing anything. Expected: the order is unchanged and the database row for
steps_ndx35 has the same content as before. - Run the download transposition to a test copy of the arrays, or compare before a live write. Expected: PLC step array
1, 1, 1, 2, 3, 4, the same as a download of the unedited recipe. - Open the recipe-vs-PLC comparison table for a recipe that matches the controller. Expected: no mismatch flags on any row.
Recurring errors in recipe ordering on SCADA-to-PLC systems
- Treating the primary key as a sequence. Any edit, delete-and-reinsert, or restore from backup changes the order. Order comes from a column that you control.
-
Floating-point hierarchical keys.
1.1and1.10collapse to one value, and1.10sorts before1.9. The editor's freedom to pick1.1, 1.2, 1.3or1.01, 1.02, 1.03guarantees that both conventions will eventually appear in one recipe. -
Implicit REAL-to-integer conversion. If the integer step is ever derived inside the controller instead of the script, note that a Logix REAL-to-DINT move rounds rather than truncates. A step of
1.5or higher would become 2. Derive the integer explicitly on the SCADA side (step_major, orint(math.floor(step))). - Sorting tags the group writes back. A bidirectional block group turns a display sort into a database corruption. A timer-driven group undoes the sort. Write sorted data to its own tag set.
- Positional comparison against the PLC. A positional row-by-row compare reports false mismatches whenever the order changes. Once order is deterministic, positional compare is valid, because the PLC array order is the download order.
-
Renumbering in the wrong place. Running the
sequpdate after the block transaction leaves the tags one edit behind. Verify the fix by editing a step, triggering the save, and confirming that the block shows the edited step in its new position on the first transaction, not the second.
FAQ
Why does my Ignition block group put edited rows at the bottom?
The block group fills positions in order of its index column, and an auto-number key gives every re-inserted row the highest value, such as 35. Key the group on a sequence column renumbered with on each save.
Why does storing recipe step 1.01 as a REAL cause problems?
A REAL cannot distinguish 1.1 from 1.10 and sorts 1.10 ahead of 1.9, so a two-level step key loses information. Store it as two integer columns (step, substep) or as a string split on the decimal point.
Why do my sorted tags go back to the old order?
The block group executed again and rewrote the block in index order, or it pushed your sorted values back into the database if it is bidirectional. Sort at the SQL source, or write the sorted rows to a separate tag set the group does not own.
How do I sort recipe rows in Ignition Python by a float step with a tiebreak?
Read the block in one bulk call, group the values into rows, and use sorted(rows, key=lambda r: (float(r[0]), r[1])), where r[1] is steps_ndx. Then write the flattened result in one bulk call. Python's sort is stable, so equal steps keep a repeatable order. For a final check, confirm that the PLC download array still reads 1, 1, 1, 2, 3, 4 for the example recipe.