How do Ignition client tags refresh with Polling Mode off?

Daniel Price7 min read
HMI / SCADAOther ManufacturerTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Where does the dataset request travel, and where does it stop?

A client tag lives inside each running client session. When its Expression Type is SQL Query, the client builds the query text, hands it to the gateway, and the gateway runs it through its database connection. The result set comes back along the same path and lands in the tag as a DataSet. Every hop carries a request that starts in the client. The table never sends anything on its own, so no hop carries a change notification from the database back to the client.

Hop What it does What makes it send
Client tag (DataSet, SQL Query) Builds the query string, including any {...} tag substitutions The poll timer, or a change in the evaluated query text
Client to gateway Carries the query request and the result set Only a request from the client
Gateway database connection Runs the SQL against the database Only a request from the gateway
Database table Returns rows Nothing. It answers queries and does not push changes.

With Polling Mode set to Off, the poll timer is gone. The only thing left that makes the tag re-query is a change in its own evaluated query text. Nothing can detect a change in the table unless something reads the table. You have two ways to deal with that:

  • Have an operator action force the re-read.
  • Poll something small and use it to gate the large query.

How do I build the dataset tag so something external can re-fire it?

Put a value you control into the query text. When that value changes, the text changes and the tag re-executes. A Boolean works well because toggling it always produces a change.

  1. Create a client tag named trigger with Data Type Boolean.
  2. Create a second client tag with Data Type DataSet.
  3. On the DataSet tag, set Expression Type to SQL Query and Polling Mode to Off.
  4. Enter the query with the trigger substituted on both sides of an always-true comparison:
SELECT * FROM table WHERE {[~]trigger}={[~]trigger}

[~] refers to the client tag provider from inside a client tag expression. When trigger is false, the gateway receives SELECT * FROM Table WHERE 0=0. When it is true, the gateway receives SELECT * FROM Table WHERE 1=1. Both predicates are always true, so the returned rows are identical. The query string is different, though, and a new query string is what makes the tag execute again.

Check before moving on:

  1. Run the SQL on its own against the same database connection and confirm it returns the expected rows.
  2. Open the client and confirm the DataSet tag populates on startup.
  3. Edit a row in the table and confirm the tag does not change. That confirms polling is really off.

How does a button toggle the trigger?

Put this script on the button's actionPerformed event. It reads the current trigger state and writes the inverse:

value = system.tag.read('[Client]trigger').value
system.tag.writeToTag('[Client]trigger', not value)

Use not value rather than writing a constant. Writing True every time changes the query text only on the first click. After that the text is identical, and the tag has no reason to re-execute.

Check:

  1. Put a label bound to trigger next to the button and click the button. The label should flip on every click.
  2. Change a row in the table, then click the button again. The table or component bound to the DataSet tag should show the new row.

If the label flips but the data does not change, the break is on the query side. Go back to the previous section's checks.

Can a script write the dataset directly instead?

Yes. Drop the SQL Query expression and let the button run the query and write the result.

  1. Create a client tag with Data Type DataSet and no query expression. In this example it is named Scripting tag.
  2. Put this script on the button's actionPerformed event:
data = system.db.runQuery("SELECT * FROM table")
system.tag.write("[Client]Scripting tag", system.dataset.toDataSet(data))

system.db.runQuery sends the query through the gateway and returns a PyDataSet. system.dataset.toDataSet converts that result into the DataSet type the tag holds. With this method there is no trigger tag and no dummy predicate. The data is exactly as current as the last click.

Aspect Trigger binding Script write
Where the query lives In the tag expression In the button script
Extra tags trigger (Boolean) None
Other refresh sources Anything that changes trigger can refresh the tag Every refresh source needs its own copy of the script
Initial value at client start Query runs automatically Empty until the first click, unless a startup script also runs the query

Check: click the button and confirm the tag's row count matches a direct SELECT against the table. A spelling mistake in the tag path, including the space in Scripting tag, makes the write fail. The error appears in the client's console or error popup.

What breaks after commissioning?

Symptom Cause Fix
One operator refreshes, but another client still shows old data Each client session has its own copy of every client tag and runs its own query Each client refreshes itself. For one shared copy, move the dataset to a gateway-scoped tag.
The screen freezes while the scripted button runs The event script runs the query on the UI thread and blocks until it returns Keep the query small, or run it on a background thread (system.util.invokeAsynchronous) and write the tag when it completes
Only the first click refreshes The button writes a constant instead of toggling Use the not value toggle
The tag is empty or shows a quality error The SQL is invalid for the database dialect, or the connection is faulted Test the SQL with the Database Query Browser and check the connection status on the gateway
Data is stale because nobody clicks A manual trigger cannot detect changes in the table Poll a small change marker, as described below

For an automatic refresh without re-reading the whole table:

  1. Create a polled client tag that runs a single-value query. Good choices are a row count or the maximum of a last-modified column. The column name is specific to your schema.
  2. Reference that tag in the dataset query in place of trigger. If the value is a string or timestamp, quote it on both sides, for example WHERE '{[~]marker}'='{[~]marker}'.
  3. Keep the dataset tag's Polling Mode at Off.

With this arrangement, the full query runs only when the marker value changes. This is still polling, but each poll moves one value instead of the full result set.

How do I prove the refresh works end to end?

  1. Open two clients against the same gateway.
  2. Record the row count of the DataSet tag in both clients.
  3. Insert or update a row directly in the database table.
  4. Confirm neither client changes. This proves polling is off and that nothing else is refreshing the tag.
  5. Click the refresh button in client A. Client A shows the new data within one query round-trip. Client B stays stale, which confirms the refresh is scoped to one client.
  6. If you use the trigger method, click the button again with no database change. Client A re-queries and shows identical rows, which confirms the toggle fires the query on every click.
  7. If you use the change-marker method, change a row, wait one marker poll interval, and confirm both clients update without any clicks.

FAQ

Can I make an Ignition SQL Query client tag update automatically with polling off?

Not by itself. The database does not push changes, so something must read it. Poll a small change-marker tag, such as a row count or a max timestamp, and reference that tag in the dataset query so the large query runs only when the marker changes.

Does the {[~]trigger}={[~]trigger} clause change the query result?

No. It evaluates to 0=0 or 1=1, which are both always true. Its only purpose is to change the query text, and that change forces the tag to re-execute.

Does refreshing a client tag in one client update the other open clients?

No. Every client session holds its own copy of each client tag and runs its own query. Each client needs its own trigger, or you move the data to a gateway-scoped tag.

Can I use system.db.runQuery instead of a SQL Query client tag?

Yes. Create a DataSet client tag with no expression. Have the button run system.db.runQuery, then write the result with system.tag.write after converting it using system.dataset.toDataSet.

Back to blog