Here is how the fault shows up on screen. A table bound to tag history repeats the same part number, cycle time, color, and shift on every one-second row. The transaction group table next to it looks clean, except for the occasional row where the cycle time or the GREEN/RED flag belongs to the previous part.
You are looking at two separate faults. Chase them separately. The setup in question is Ignition 7.7.4 with these pieces:
- A part number tag,
current_pp, on the default 1 s scan class. - A driven scan class keyed to a change in
current_pp. - Cycle time, average time, color, and shift tags on that driven class, with history enabled and the historical scan class set to
Evaluate on change. - A historical transaction group triggered by
current_pp.
Skip the Fixes That Don't Touch the Cause
These are the usual first moves for this symptom. Each one burns time.
-
Setting the historical scan class to
Evaluate on changeto stop duplicate rows. That setting controls when the historian evaluates the tag's value. It does not control how many rows your history query hands back to the table. The duplicate rows stay. -
Changing the rate of
current_pp. The 1 s default rate sets how fast Ignition sees the part number change. It does not create history rows for the other tags. That is not the fault. - Enabling tag history on every tag the group already logs. The transaction group already writes the row. With history on as well, you store every value twice, in two places that disagree by a scan. Tag history is only useful here during commissioning, as a cross-check. Turn it off afterward.
- Driving the data tags from a scan class keyed to the part number change. It feels efficient. It is the source of the wrong-data rows (see below).
- Swapping the historical group for a standard group. Both group types trigger and read values the same way. A historical group simply only inserts rows. The swap changes nothing about timing.
Separate What the Historian Stores from What the Query Returns
Start here. The Ignition historian decides to store a sample when three things line up:
- The tag is evaluated at its historical scan class rate, or on change.
- The new value differs from the last stored value by more than the historical deadband.
- Or the maximum time between samples has elapsed, in which case the historian stores the value even if it has not changed.
With a zero or small deadband and no forced maximum time, a tag that holds steady does not get a new row every second.
A history binding or tag history query works differently. It returns a resampled dataset. With a fixed sample size or an interval-based sample, the query builds one row per interval and fills each row with the last known value, or an interpolated one. A part number that sits at 55 for four minutes then shows up as 240 identical rows at 1 s resolution. The database holds one or two.
Decide which case you have before touching any setting:
- Open the Database Query Browser in the Designer.
- Query the raw history partition table directly for that tag, over the same time window.
- If the raw table shows one row per value change, storage is correct. The duplicates come from the query. Switch the binding to return values as stored, on change, instead of at a fixed or interval sample size.
- If the raw table really does hold identical values every second, open the tag's history properties. Check the maximum time between samples, and check whether quality or timestamp changes are forcing writes.
Find the Race Behind the Wrong Rows
The wrong-data rows come from timing, not configuration. Walk through what happens when the part number goes from 10 to 11:
-
current_ppupdates on its 1 s scan. - The driven scan class sees the change and schedules a read of the cycle time, average, and other data tags.
- The transaction group sees the same change on its own timer and executes.
- The expression tags re-evaluate whenever their scan class next runs.
Steps 2, 3, and 4 run on independent timers. Nothing makes the group wait for the driven read to finish. Whether the group gets the new cycle time or the old one depends on which timer fires first. That is why the row is right most of the time and wrong some of the time.
The color expression makes the race worse:
if({[.]Previous_CycleTime} <= 10
, 'GREEN'
, 'RED'
)
That expression evaluates against whatever Previous_CycleTime held when the expression tag last scanned. If Previous_CycleTime has not updated yet, the color belongs to the previous part.
On the question of how long the driven class keeps scanning: a change-based driving condition does not hold the tags at a fast rate until the part finishes. The class executes when the driving value changes. Between changes, the tags poll at the class's slow rate, or not at all if the slow rate is zero. Open the scan class and read the slow rate. If it is zero, your data tags are frozen between part changes. Any mid-part change in the PLC never reaches Ignition until the next part number change.
The shift expression has the same exposure:
if({[~]Time/Hour} >= 7 && {[~]Time/Hour} < 15,
'A',
if({[~]Time/Hour} >= 15 && {[~]Time/Hour} < 23
, 'B'
, 'C'
)
)
On the driven class, this tag only re-evaluates when a part changes. After a long stop across 07:00, 15:00, or 23:00, the shift label stays stale until the next part. Keep the shift tag on the default 1 s class.
Match the Symptom to the Cause
| Symptom | Likely cause | Check first |
|---|---|---|
| Identical rows every second in a history-bound table | Query resampling at a fixed or interval sample size | Raw history table in the Database Query Browser |
| Identical values stored every second in the raw table | Forced maximum time between samples, or quality/timestamp churn | Tag history properties on the affected tag |
| Group row carries the previous part's cycle time | Group trigger and driven scan class read on the same event | Compare the group row timestamp with the tag's last-change timestamp |
| Color flag disagrees with the logged cycle time | Expression tag evaluated before Previous_CycleTime updated |
Scan class assigned to the color tag |
| Shift label wrong after a stop across a shift boundary | Shift expression tag on the driven class, no re-evaluation | Scan class assigned to the shift tag |
| Data tags never change mid-part | Driven class slow rate set to zero | Scan class slow rate |
| Same data in two tables that disagree slightly | Tag history and transaction group both logging | History enabled on group member tags |
Rebuild the Chain With a Handshake Trigger
The dependable pattern moves the part-complete event into the PLC, then gives Ignition a trigger that fires only after the data is stable.
PLC side, preferred. It takes a handful of rungs, so ask whoever owns the program.
- When a part finishes, copy all pertinent data into a separate set of buffer tags: part number, cycle time, average, color condition, and anything else logged. The next cycle can then start without overwriting the data you are about to record.
- Start a 3 s timer. With cycle times in the 18-25 s range, 3 s leaves plenty of margin for Ignition's OPC tags to update. Keep the delay well inside your shortest cycle.
- When the timer completes, increment an integer. That integer is the transaction group trigger.
Ignition side:
- Delete the driven scan class, or stop using it for logged data.
- Point the transaction group at the buffer tags, not the live working tags.
- Set the group trigger to execute on a change of the counter integer. Enable the option to execute only once while the trigger is active.
- Enable the trigger option that prevents execution when the group starts. This stops a gateway restart from inserting a phantom row.
- Disable tag history on the group's member tags once the comparison work is done.
No PLC access? Trigger the group on the tag the PLC writes last in its part-complete sequence, not on the part number. Previous_CycleTime is a candidate if it only updates at part completion. Keep all data tags on the 1 s class so they are current when the trigger fires. This narrows the race window but cannot close it. Only a buffer plus a delayed counter guarantees consistent rows. Put the handshake request on the PLC owner's list.
Compute the color in the group rather than in a separate expression tag. It is then evaluated against the exact cycle time value written to the same row.
Log End-of-Shift Values Exactly Once
In this setup, a Boolean goes true for 30 s just before each new shift. During that window, logic copies the last part made, the last average, and the total downtime into end-of-shift tags, then zeroes the working tags. A historical group triggered on that Boolean, with a trigger condition of != 0 and execute-once-while-active, is the right tool. It gives three rows per day, one per shift. A standard group adds nothing here.
The wrong-data risk is the same race. The group fires on the rising edge, possibly before the copy into the end-of-shift tags has reached Ignition. Fix it in one of these ways, best first:
- Have the copy logic increment a counter after the copy completes, and trigger on that counter.
- Trigger when the Boolean returns to 0 instead of on the rising edge. The end-of-shift tags have been stable for 30 s by then. Enable the option that prevents triggering on group start, because the Boolean sits at 0 when the gateway starts.
- Keep the end-of-shift tags on the 1 s class, not on a driven class, so they are current whenever the trigger fires.
For the shift column, log the shift that just ended. The Boolean runs before the new shift starts, so the hour still maps to the old shift. That only holds if the shift tag is scanning at 1 s.
Keep the Scan Class Count Sane
A driven scan class per station, 11 per machine and 22 across two machines, is not the problem it looks like. Two points still matter:
- Load comes from how many tags each class polls and how fast, not from how many classes exist. Each class adds its own polling or subscription group at the driver. Check device load and request timing in the OPC-UA server's driver diagnostics on the gateway.
- Per-station driven classes rebuild the race condition eleven times over when their purpose is to time data capture. If a station needs its own logged record, give it its own completion counter and its own transaction group. Leave the tags on the shared 1 s class.
Keep driven scan classes for their real job: reducing polling on idle equipment, for example running fast only while a machine-running bit is true.
Verify Before You Trust the Table
- Run ten or more parts. For each group row, compare the logged cycle time against the PLC buffer value read online. Every row must match its own part number.
- Check that the color column matches the logged cycle time against the 10 threshold on every row. A single mismatch means the color is still computed outside the triggered read.
- Query the raw history table. There should be one row per value change, and no rows at all for tags where you disabled history.
- Restart the group, or the gateway on a test system. Confirm no row is inserted at startup.
- Watch a shift change. Confirm exactly one end-of-shift row with the outgoing shift label and non-zero totals.
FAQ
How do I stop Ignition tag history from showing duplicate rows every second?
Query the raw history table in the Database Query Browser first. If it shows one row per change, set the history binding to return values as stored or on change instead of at a fixed or interval sample size. If the raw table itself repeats, check the tag's maximum time between samples.
How do I trigger an Ignition transaction group only once per part?
Trigger on an integer counter the PLC increments after it buffers the part data and waits about 3 s. Enable execute-once-while-active and the option that prevents triggering on group start.
How do I tell if too many scan classes are slowing down Ignition?
Check driver diagnostics for request load and response times rather than counting scan classes. Twenty-odd classes is fine if tag counts and rates are reasonable. Consolidate classes when their only purpose is timing data capture.
When should I escalate an Ignition logging problem to Inductive Automation support?
Escalate when the raw history table shows writes that its deadband and sample settings cannot explain, or when a counter-triggered group still logs stale values with verified-stable PLC buffers. Send the gateway version, the group and tag export, and the gateway logs from the failure window to Inductive Automation's official support channel.