Appearance
Transaction Groups
Last updated
License feature: Transaction GroupsA transaction group writes a snapshot of several tag values as one row — in a local Data Table or in a table on a remote SQL Server, MySQL, PostgreSQL, Oracle or ODBC database — every hour, every time a part counter changes, when a machine raises a "part done" bit, when a condition becomes true, or when a schedule says so. It can then write a handshake tag so the machine knows the row was stored. Use it for production logs, shift snapshots and batch records.
Where and who
Visualize → Data Infrastructure, tab Transaction Groups. Needs the transaction_groups licensed feature. Viewing needs any role; creating, editing and Execute now need editor; deleting needs admin.
What happens on each run
- Scada.Ink reads the cached value of every mapped tag (it does not poll the device).
- It converts each value to the column's type: Real (decimal), Integer or Text. A value that is not a number becomes empty (NULL); a tag with no value yet becomes NULL.
- It inserts one row into the target table with those columns.
- Local data table: the table's built-in
created_atrecords when the row was added (UTC). If the table also has a column namedtimestamp, that is filled with the time in ISO 8601 UTC (2026-09-15T14:00:00.012Z). - Remote database: if Timestamp column is set, that column is filled with the time the group ran.
- Local data table: the table's built-in
- It writes the On success or On failure handshake tag, if set (see Handshake below).
Values are read the moment the trigger fires, before anything is sent to the database. It always inserts; it never updates existing rows.
Prepare the target table
For a local target, create a Data Table with a column for every mapping.
- In Data Tables, create a table named
Hourly Press Log. - Add columns
die_temp(Real),part_count(Integer),job(Text).
Create a group
- Open Data Infrastructure → Transaction Groups and click New Transaction Group.
- Name:
Hourly press snapshot. - Write to: Local data table. Target Table: pick
Hourly Press Logfrom the list. - Trigger Type: Timer (interval), Interval (seconds)
3600. - Under Column Mapping (tag → table column) click Add Column for each value: pick Column name
die_temp, enter Device ID3and Tag pathHR100, type Real; and so on. - Leave Enabled — runs on its trigger ticked, or untick it to test first. Click Create.
- Click Execute now (the play icon). A message under the list says the row was written, or why it couldn't be.
Fields
| Field | What it does | Default | Notes |
|---|---|---|---|
| Name | Label | — | Required |
| Write to | Local data table or Remote database | Local | |
| Target Table | Local: a Data Table, picked from the list | — | Required; saving fails with Data table "…" does not exist if it's gone |
| Connection | Remote: a saved remote database connection | — | It must have data table writes turned on under its write permissions; the list marks those that don't |
| Table | Remote: the table's name on the server, optionally schema.table | — | Letters, digits, underscores ($/# allowed after the first character) |
| Timestamp column (optional) | Remote: a column filled with the time the group ran | blank | Leave blank if the table fills its own |
| Trigger Type | Timer (interval), Tag Change, Rising edge or Condition | Timer | Schedule-run groups show Schedule |
| Interval (seconds) | Timer period | 60 | 0 or blank means 60 |
| Trigger Tag | Tag Change, Rising edge and Condition (compare): the device and tag to watch, picked from the lists | — | Required |
| Condition | Tag compared with a value or Expression | Tag compared | See Triggers |
| Comparison / Value | Compare mode: >, >=, <, <=, ==, != and the value | >= | Numbers compare as numbers, anything else as text |
| Expression | Expression mode: an expression tag expression using {deviceId:tagPath} references | — | Must reference at least one tag |
| Column name | Local: picked from the table's columns. Remote: typed | — | Local saving fails with has no column named … otherwise; remote names are checked when the group runs |
| Handshake: On success / On failure | Tick to write a tag after each run: device, tag, Value to write | off; value 1 / 0 | See Handshake |
| Enabled — runs on its trigger | Whether the trigger runs the group | on | Execute now works either way |
| Device ID / Tag path | Source tag for this column | — | Local tags use the Local Tags device's id |
| Type | Real, Integer or Text | Real | Use Integer (0/1) or Text for booleans |
The list shows each group with its trigger, target (a remote one as connection: table) and Last run, with "queued in store & forward (database unreachable)" or "failed: …" and the reason when the last run didn't write; a switch to turn the group on or off; and icons for Execute now, Edit and Delete. Delete asks first ("Delete transaction group?"); rows already written stay in their table.
Triggers
- Timer (interval) runs every N seconds from the moment the group was loaded (API start or last save), not aligned to the clock. For rows exactly on the hour, use a schedule instead.
- Tag Change runs on every change of the watched tag — each count of a part counter, each flicker of a bit, after the tag's deadband.
- Rising edge runs once each time the tag goes from off to on: from 0,
falseor empty to a non-zero number ortrue. It doesn't run again while the tag stays on, only after it has gone off and on again. Use it for a PLC "part complete" or "batch done" bit. - Condition runs once each time a condition becomes true, and not again until it has been false. Tag compared with a value checks one tag (for example
Tank/Level >= 80) on each change of that tag. Expression is re-checked whenever any tag it references changes, and counts as true when it gives a non-zero number ortrue, for exampleif({1:Line/Running} == 1 && {1:Line/Count} >= 100, 1, 0). - For Rising edge and Condition, the first value read after the group starts (saving any group, or an appliance restart) only sets the starting point: a trigger already on at that moment does not run the group.
- Schedule: on the Schedules page create a schedule with the action Execute Transaction Group and a cron expression such as
0 * * * *. See Schedules.
Writing to a remote database
- Under Remote DBs, open the connection and turn on data table writes under its write permissions.
- Create the table on the server with the columns you'll map, plus one for the time if you want it, for example
CREATE TABLE production_log (id INT IDENTITY PRIMARY KEY, logged_at DATETIME2, part_count INT, die_temp FLOAT). - In the group, choose Write to → Remote database, the Connection, Table
production_logand Timestamp columnlogged_at, then type each Column name. - Click Execute now to prove the names: a wrong table or column shows the database's own error.
When a triggered run can't reach the database, the row is queued in store & forward and written when the connection is back; the list shows "queued in store & forward". Execute now writes directly instead, so a mistake shows at once rather than being queued. Table and column names are quoted with [ ] on SQL Server and backticks on MySQL; on PostgreSQL, Oracle and ODBC they are sent unquoted, so the database's usual case rules apply (PostgreSQL folds to lower case).
Handshake
A handshake tells the machine whether its data was stored, the way a PLC waits for an acknowledgement before it clears its buffer or moves on.
- On success writes Value to write (default
1) to the chosen tag after the row is inserted. A row queued in store & forward also counts as success: it is stored on the appliance and will be delivered. - On failure writes its value (default
0) when the run fails. - The tag can be a local tag, a simulator tag or a tag on a real device; a real device must have writes enabled in its settings, as for scripts. A handshake that can't be written is logged as a warning and doesn't change the run's result.
- Execute now writes the handshake too.
A common pattern: the PLC sets PartDone to 1, the group (Rising edge on PartDone) stores the row and writes 0 back to PartDone as its success handshake, ready for the next part.
Behavior to know
- Turn a group off with its switch to pause it; its settings are kept.
- Saving any group reloads all groups, which restarts every timer.
- When a run fails (missing table, missing column, database error), a notification Transaction Group Failed is sent once, and Transaction Group Recovered when it works again. The list shows the last error next to Last run.
- Execute now runs the group once, even when it is turned off, and shows whether the row was written or the error.
- Rows accumulate; add a scheduled script or named query to delete old rows if the table must not grow forever.
API
| Method | Path | Role | Purpose |
|---|---|---|---|
| GET | /api/data-infra/transaction-groups | viewer | List groups |
| GET | /api/data-infra/transaction-groups/:id | viewer | One group |
| POST | /api/data-infra/transaction-groups | editor | Create. Local: targetTable may be the table's display name or internal name; it and every columnName are checked. Remote: targetType: "remote", targetConnectionId, targetTable, optional timestampColumn. Triggers: triggerType timer, tag_change, rising_edge, condition (triggerConfig: { mode: "compare", deviceId, tagPath, operator, value } or { mode: "expression", expression }). handshake: { success?: { deviceId, tagPath, value }, failure?: { … } } |
| PUT | /api/data-infra/transaction-groups/:id | editor | Update (same fields) |
| DELETE | /api/data-infra/transaction-groups/:id | admin | Delete |
| POST | /api/data-infra/transaction-groups/:id/execute | editor | Run now; answers { table, columns, buffered }, or 400 with the reason |
Each group also returns lastResult (success, failed or buffered) and lastError.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Data table "…" does not exist | The table was deleted or renamed | Pick the table again in Target Table |
has no column named … | A Column name does not match the table | Correct the mapping |
| Columns are empty | Tag has no cached value, wrong Device ID/Tag path, or a non-numeric value in a Real/Integer column | Check the tag in Tags; use Text |
| Far too many rows | Tag Change on a tag that changes constantly | Use Rising edge, Condition, a timer or a schedule |
Name and target table are required | Missing field | Fill both |
Remote connection "…" does not allow data table writes | The connection's data table writes are off | Turn them on under the connection's write permissions in Remote DBs |
"…" is not a valid table name / column name | Spaces, quotes or other characters in a remote name | Use letters, digits and underscores, or rename on the server |
| Rising edge never fires | The tag was already on when the group started, or never goes back to 0 | Make the PLC reset the bit, or write 0 as the success handshake |
| Last run shows "queued in store & forward" | The remote database was unreachable | Rows are sent when it is back; see Store & Forward |
| A handshake tag doesn't change | Writes are not enabled on that device, or the tag isn't writable | Enable writes in the device's settings; see the API log for the warning |