A ControlLogix UDT updates every member in one PLC scan, yet an Ignition 8.1 Table fed by Tag History shows the same snapshot spread across many near-duplicate rows. The Tag Historian is not built for record logging. It stores each member tag independently, on its own change and deadband evaluation, with its own timestamp. A wide-format history query then builds one row for every timestamp at which any member was stored, and it fills the other columns with the previous or interpolated values. Ten members changing a few milliseconds apart as their subscription updates arrive therefore produce up to ten rows for one PLC event.
To log a snapshot as a record, use a trigger that reads every member at one instant and inserts one row into a custom database table. The SQL Bridge module does this with a triggered transaction group set to OPC Read mode. Without SQL Bridge, a gateway tag change event script does the same job using system.opc.readValues(). Do not push these values to the historian. Write them as rows in your own table and point the Table component at that table.
PLC Snapshot and Trigger Handshake
Before anything else, confirm the PLC writes the trigger after it writes the snapshot data. The trigger is the only tag Ignition subscribes to for this purpose. Every other member is read on demand after the trigger fires.
- Keep the snapshot UDT instance in the controller, and copy all members into it in one rung or routine.
- Add a trigger tag outside the snapshot. Use an integer counter that the PLC increments after the copy. A counter produces a distinct change on every event. A BOOL that toggles quickly can be missed between subscription updates.
- Add an acknowledge tag that Ignition writes back with the counter value it logged. The PLC can then detect a missed or unlogged event by comparing the counter with the acknowledge value.
- If the process can supply it, add a PLC-side timestamp member to the snapshot. A controller timestamp records when the event happened. A gateway timestamp records when the script ran.
Check: Create an Ignition tag for the trigger only, and watch it in the Designer Tag Browser while the process runs. Do not move on until it increments exactly once per PLC event and shows Good quality.
Custom Log Table and Database Connection
Create one table with one column per UDT member, plus a timestamp and the trigger value. Adapt the data types to your database.
CREATE TABLE udt_log (
id INTEGER PRIMARY KEY, -- use your DB's auto-increment syntax
t_stamp DATETIME NOT NULL,
trig_count INTEGER,
batch_id VARCHAR(40),
temp FLOAT,
pressure FLOAT,
weight FLOAT
);
CREATE INDEX ix_udt_log_tstamp ON udt_log (t_stamp);
The table and column names are placeholders. Map them to your actual UDT members. Index the timestamp column, because every Table query will filter on it.
Check: In the Gateway web page, open Config > Databases > Connections and confirm the connection reports Valid. Run a manual INSERT and SELECT against the table from the Designer's Database Query Browser before you write any script.
Gateway Tag Change Event Script
The Tag History approach and a naive tag change script both fail for the same reason. Ignition's tag values come from subscriptions, and the trigger update can reach the gateway before the updated values of the other members. Reading the members from the tag cache inside the trigger event can therefore capture a mix of old and new data. An explicit OPC read issued after the trigger fires goes to the device and returns the current snapshot. The SQL Bridge OPC Read mode does the same thing.
Create the script under Project > Gateway Events > Tag Change, and set the trigger tag path. Gateway scope requires the full provider-qualified path.
- Copy each OPC Item Path from an existing tag's editor instead of typing it by hand. The device name and the path syntax must match the Logix driver exactly.
- Keep the
initialChangeguard. Without it, every gateway restart or project save inserts a phantom row. - Use prepared statements (
?placeholders). String members such as batch IDs break concatenated SQL. - If you add a PLC timestamp member, read it with the other members and insert it in place of
system.date.now().
Check: Increment the trigger once from the PLC. Confirm that exactly one row appears in udt_log, that the acknowledge tag matches the trigger, and that the gateway logs (Status > Diagnostics > Logs, filtered on UDTLog) show no warnings.
Historian Settings on the UDT Members
The members no longer need history for this log. If you leave history enabled, the historian keeps writing the fragmented data in parallel and uses storage for no benefit.
- Open the UDT definition, not an instance, so the change applies to every instance.
- On each member used only for the snapshot log, set History Enabled to false.
- Keep history on any member that also needs trending. Trends need per-tag change storage, and the historian is the right tool for that job.
Check: Confirm on one instance that the members no longer show the history icon. Confirm that no member overrides the definition setting.
Table Component Query Binding
Bind the Table's data property to a Named Query against udt_log instead of a Tag History binding.
SELECT t_stamp, batch_id, temp, pressure, weight
FROM udt_log
WHERE t_stamp BETWEEN :startDate AND :endDate
ORDER BY t_stamp DESC
Pass the start and end dates from date pickers or a range selector. Set a polling rate only if operators need to see live rows appear. For after-the-fact review, an on-demand refresh is enough.
Check: The Table shows one row per trigger increment, and each row's values match what the PLC held at that event.
End-to-End Verification
| Symptom during test | Likely cause | Check |
|---|---|---|
| Mixed old and new values in one row | Members read from the tag cache instead of by OPC read, or the PLC sets the trigger before the copy completes | Review the script read method and the PLC rung order |
| Row missing for an event | BOOL trigger toggled faster than the subscription rate | Compare trig_count gaps with the PLC counter |
| Extra row after restart |
initialChange not handled |
Restart the gateway and count the rows |
| No rows, trigger changing | Wrong OPC item path or DB connection name | Gateway logs; OPC read results quality |
| Still duplicate-looking rows | Table still bound to Tag History | Inspect the data binding type |
- Run the process through at least ten PLC events, including two in quick succession.
- Confirm that
trig_countin the table has no gaps and no repeats. - Compare one logged row member by member against the PLC snapshot, using the controller's monitor view.
- Restart the gateway and confirm no row is added until the next real trigger.
- Confirm that the PLC acknowledge comparison shows zero unlogged events.
FAQ
What happens if I read UDT member tag values instead of doing an OPC read in the trigger script?
Tag values come from subscriptions, so the trigger can arrive before the updated members. You can log a row that mixes the previous snapshot with the new one. system.opc.readValues() forces a fresh read after the trigger.
What happens if I leave Tag History enabled on the UDT members?
The historian keeps storing each member separately, and history queries keep returning fragmented rows. Disable history in the UDT definition for members used only in the snapshot log. Keep it on members that also feed trends.
What happens if the trigger changes faster than Ignition can log it?
A BOOL trigger can toggle and return between updates, and the event is lost. Use an incrementing counter with an acknowledge tag, so gaps show up in trig_count and the PLC can detect unlogged events.
Do I need the SQL Bridge module to log a UDT snapshot to a database?
No. SQL Bridge provides a triggered transaction group with OPC Read mode, but a gateway tag change event script that uses system.opc.readValues() and a prepared INSERT produces the same one-row-per-event result.