# Querying Tag History with a Limit

**URL:** <https://forum.inductiveautomation.com/t/querying-tag-history-with-a-limit/114367>\
**Category:** Module Development\
**Created:** [March 6, 2026, 7:09pm UTC](https://forum.inductiveautomation.com/t/querying-tag-history-with-a-limit/114367 "2026-03-06T19:09:07Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![mpderbec](https://avatars.discourse-cdn.com/v4/letter/m/bbe5ce/32.png) [@mpderbec](https://forum.inductiveautomation.com/u/mpderbec)\
**Post date:** [March 6, 2026, 7:09pm UTC](https://forum.inductiveautomation.com/t/querying-tag-history-with-a-limit/114367/1 "2026-03-06T19:09:07Z")

</div>

I need to query for _raw data_ in tag history, with no aggregations or interpolation. I found that setting returnSize to zero is key to retrieving something that is closest to the raw data.

But I also need to limit the amount of data that is returned. My user may query for years of data and I need to handle it in chunks _after_ the queryHistory() call (I can’t chunk it “inline” during the handling of my custom StreamingDatasetWriter.writeRow() due to how our system works).

Here’s what the code looks like:

```python
        BasicTagHistoryQueryParams queryParams = BasicTagHistoryQueryParams.newBuilder()
                .paths(tagPathList)
                .startDate(startDate)
                .endDate(endDate)
                .queryFlags(Flags.of(
                        TagHistoryQueryFlags.BOUNDING_VALUES_YES,
                        TagHistoryQueryFlags.NO_INTERPOLATION,
                        TagHistoryQueryFlags.NO_PREPROCESSED_DATA
                ))
                .returnSize(0)
                .returnFormat(ReturnFormat.Tall)
                .build();

        // Use our custom writer that converts rows directly to Samples as they arrive
        SampleDataset dataset = new SampleDataset(this.tagQualityCodeFormat);

        tagHistoryManager.queryHistory(queryParams, dataset);

```

SampleDataset is my custom StreamingDatasetWriter that handles writeRow().

So, my question is: **Is there a way to limit how many rows are returned _without_ defeating the requirement to return raw data? Maybe I can throw an exception from my writeRow() when I’ve reached the limit of what I can accept?**

---

<div class="post-metadata">

**Author:** ![mpderbec](https://avatars.discourse-cdn.com/v4/letter/m/bbe5ce/32.png) [@mpderbec](https://forum.inductiveautomation.com/u/mpderbec)\
**Post date:** [March 6, 2026, 8:04pm UTC](https://forum.inductiveautomation.com/t/querying-tag-history-with-a-limit/114367/2 "2026-03-06T20:04:16Z")

</div>

The exception-throwing _seems_ to work well. Would appreciate someone from Inductive Automation telling me if it’s a bad idea. Here’s what my StreamingDataSet class looks like:

```python
    private static class SampleDataset implements StreamingDatasetWriter {
        @Getter private final List<Sample> samples = new ArrayList<>();
        private final int maxSamples;

        public SampleDataset(TagQualityCodeFormatEnum qualityCodeFormat, int maxSamples) {
            this.maxSamples = maxSamples;
        }

        @Override
        public void initialize(String[] columnNames, Class<?>[] columnTypes, boolean fixedSize, int numRows) {
            ...
        }

        @Override
        public void write(Object[] row, QualityCode[] qualityCodes) {
            // Check if we've reached the limit before processing this row
            if (this.samples.size() >= this.maxSamples) {
                throw new SampleLimitReachedException(
                        String.format("Sample limit of %d reached, short-circuiting data retrieval", this.maxSamples));
            }

            ... turn row into sample ...

            this.samples.add(sample);
        }

        @Override
        public void finish() {
            // Data conversion happens in write(), nothing to do here
        }

        @Override
        public void finishWithError(Exception e) {
            if (e instanceof SampleLimitReachedException) {
                // Ignore SampleLimitReachedException - this is expected when we hit the limit
                return;
            }

            // Log other errors
            LOG.error("Query finished with error", e);
        }
    }

```

---

<div class="post-metadata">

**Author:** ![mpderbec](https://avatars.discourse-cdn.com/v4/letter/m/bbe5ce/32.png) [@mpderbec](https://forum.inductiveautomation.com/u/mpderbec)\
**Post date:** [March 6, 2026, 11:51pm UTC](https://forum.inductiveautomation.com/t/querying-tag-history-with-a-limit/114367/3 "2026-03-06T23:51:22Z")

</div>

The downside is that the exception gets logged, which is some unfortunate noise:

```python
jvm 1 | E [t.h.q.ResultWriter] [15:49:10.696]: Error executing historical tag read.
jvm 1 | com.seeq.ignition.v83.gateway.link.IgnitionGatewayConnection$SampleLimitReachedException: Sample limit of 5 reached, short-circuiting data retrieval
jvm 1 | at com.seeq.ignition.v83.gateway.link.IgnitionGatewayConnection$SampleDataset.write(IgnitionGatewayConnection.java:1460)
jvm 1 | at com.inductiveautomation.historian.gateway.query.writing.TallHistoryWriter.writeRow(TallHistoryWriter.java:118)
jvm 1 | at com.inductiveautomation.historian.gateway.query.writing.TallHistoryWriter.commitRows(TallHistoryWriter.java:71)
jvm 1 | at com.inductiveautomation.historian.gateway.query.writing.HistoryWriter.readData(HistoryWriter.java:322)
jvm 1 | at com.inductiveautomation.historian.gateway.query.writing.HistoryWriter.execute(HistoryWriter.java:227)
jvm 1 | at com.inductiveautomation.historian.gateway.HistorianManagerImpl.queryHistory(HistorianManagerImpl.java:1070)
jvm 1 | at com.inductiveautomation.ignition.gateway.sqltags.history.TagHistoryManagerBridge.queryHistory(TagHistoryManagerBridge.java:99)
jvm 1 | at com.seeq.ignition.v83.gateway.link.IgnitionGatewayConnection.getSamplesViaTagHistoryManager(IgnitionGatewayConnection.java:1537)
jvm 1 | at com.seeq.ignition.v83.gateway.link.IgnitionGatewayConnection.getSamples(IgnitionGatewayConnection.java:1605)
jvm 1 | at com.seeq.link.sdk.DatasourceConnectionV2Host.signalRequest(DatasourceConnectionV2Host.java:1777)
jvm 1 | at com.seeq.link.sdk.BaseDatasourceConnection.processMessage(BaseDatasourceConnection.java:492)
jvm 1 | at com.seeq.link.sdk.BaseDatasourceConnection.lambda$processMessage$2(BaseDatasourceConnection.java:425)
jvm 1 | at com.seeq.link.sdk.BaseDatasourceConnection.lambda$processMessage$0(BaseDatasourceConnection.java:417)
jvm 1 | at com.seeq.link.sdk.utilities.DefaultConcurrentRequestsHandler.lambda$runWhenPermitted$0(DefaultConcurrentRequestsHandler.java:71)
jvm 1 | at com.seeq.link.sdk.utilities.ThreadCollection$Worker.run(ThreadCollection.java:313)
jvm 1 | at com.seeq.link.sdk.utilities.ThreadCollection.lambda$spawn$2(ThreadCollection.java:170)
jvm 1 | at java.base/java.lang.Thread.run(Unknown Source)

```
