Files
wwtools/histdb/05-query-recipes.md
T
Joseph Doherty 32f26272ae Initial commit: Wonderware / System Platform tools and reference
Five tools under one repo, all docs organized per DOCS-GUIDE.md:

- aalogcli: .NET 4.8 / x86 CliFx CLI for reading System Platform binary
  logs (*.aaLGX) for LLM debugging, built on aaOpenSource/aaLog. Commands:
  last, tail, range, unread, fields. Stable JSON envelope under --llm-json.
  Build template under lib/build/ for rebuilding aaLogReader.dll.

- aot: ArchestrA Object Toolkit 2014 v4.0 reference material. Dev guide
  (Markdown converted from CHM), API reference for the ArchestrA.Toolkit
  namespace, and the Monitor / Watchdog VS sample solutions.

- graccesscli: .NET 4.8 / x86 CliFx CLI that automates Galaxy
  configuration via the ArchestrA GRAccess COM interop. Includes session
  daemon, IPC protocol, and llm-json envelope contract.

- grdb: SQL/DDL exploration of the Galaxy Repository database. DDL
  captures, reusable queries, hierarchy / contained-name <-> tag-name
  translation notes.

- histdb: LLM-oriented reference for AVEVA Historian retrieval. INSQL
  linked-server, extension tables, every wwXxx time-domain extension,
  every retrieval mode, alarm/event SQL recipes, REST API. Distilled
  from the 243-page Historian Retrieval Guide.

Root contains:
- CLAUDE.md: thin index pointing into each tool's README.
- DOCS-GUIDE.md: doctrine for organizing docs for LLM consumption.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 18:22:20 -04:00

334 lines
11 KiB
Markdown

# Query recipes
Worked SQL patterns for the questions that come up most often. Each recipe links back to the deeper concept doc when more context is needed.
## Querying the `History` table — vanilla
```sql
SELECT DateTime, Sec = DATEPART(ss, DateTime), TagName, Value
FROM History
WHERE TagName = 'ReactLevel'
AND DateTime >= '2001-03-13 13:15:00'
AND DateTime <= '2001-03-13 14:15:00'
AND wwRetrievalMode = 'Cyclic'; -- explicit, even though Cyclic is the analog default
```
Without `wwCycleCount` / `wwResolution`, default is **100 rows**. See [`03-retrieval-modes.md`](03-retrieval-modes.md#cyclic--evenly-spaced-cycle-boundaries).
## Latest snapshot per tag — `Live`
```sql
SELECT TagName, Value
FROM Live
WHERE TagName = 'ReactLevel';
```
`Live` is fed by streamed updates only. Non-streamed paths bypass it (CSV import, store-and-forward, post-shutdown writes, "Latest" revision data) — query `History` if `Live` shows stale results.
## Wide tables — `WideHistory` via `OPENQUERY`
```sql
SELECT * FROM OPENQUERY(INSQL, '
SELECT DateTime, ReactLevel, ReactTemp
FROM WideHistory
WHERE DateTime >= "2001-03-02 06:20:00"
AND DateTime <= "2001-03-02 06:30:00"
AND wwRetrievalMode = "Cyclic"
AND wwResolution = 60000
');
```
Column types follow tag types. Up to 1024 columns. **Variables can't appear inside the body**; if you need parameterization, build the body string in T-SQL and `EXEC` it.
## Wide tables in `Delta` mode
`Delta` over a wide table is well-defined for a **single tag**. With multiple tags, what's a "delta" — the `OR` of all tags changed? — is ambiguous. Stick to `Cyclic` / `Interpolated` for multi-tag wide queries unless the source PDF section "Querying Wide Tables in Delta Retrieval Mode" (p. 131) covers your exact case.
## Joining a SQL Server table to an extension table — `INNER REMOTE JOIN`
```sql
-- "Give me history for every string-tag with MaxLength = 64"
SELECT DateTime, T.TagName, vValue, Quality, QualityDetail
FROM StringTag T
INNER REMOTE JOIN History H ON T.TagName = H.TagName
WHERE T.MaxLength = 64
AND DateTime >= '2002-03-10 12:00:00.000'
AND DateTime <= '2002-03-10 16:40:00.000'
AND wwRetrievalMode = 'Delta';
```
Pattern: **driver SQL Server table on the left, extension table on the right.** Without `INNER REMOTE JOIN`, SQL Server pulls the entire extension table back into the engine before joining — order of magnitude slower.
## Aggregates inside `OPENQUERY`
`MIN`, `MAX`, `AVG`, `SUM` are supported inside the body; **`STDEV` is not**.
```sql
SELECT * FROM OPENQUERY(INSQL, '
SELECT
"Minimum" = MIN(ReactLevel),
"Maximum" = MAX(ReactLevel),
"Average" = AVG(ReactLevel),
"Sum" = SUM(ReactLevel)
FROM WideHistory
WHERE DateTime > "2001-02-28 18:55:00"
AND DateTime < "2001-02-28 19:00:00"
AND wwRetrievalMode = "Cyclic"
');
```
For `STDEV` or other unsupported aggregates, run the inner `OPENQUERY` to fetch raw rows and apply the aggregate outside.
## `COUNT(*)` quirks
`COUNT(*)` works directly in four-part queries:
```sql
SELECT COUNT(*)
FROM History
WHERE TagName = 'SysTimeSec'
AND DateTime >= '2001-12-20 00:00'
AND DateTime <= '2001-12-20 00:05'
AND wwRetrievalMode = 'delta'
AND Value >= 30;
```
Inside `OPENQUERY`, you cannot apply arithmetic to `COUNT(*)`. Push the count into the inner query and do math outside:
```sql
SELECT COUNT(*), COUNT(*)/2 FROM OPENQUERY(INSQL, '
SELECT DateTime, vValue, Quality, QualityDetail
FROM History
WHERE TagName IN ("SysTimeSec")
AND DateTime >= "2002-04-16 03:00:00.000"
AND DateTime <= "2002-04-16 06:00:00.000"
AND wwRetrievalMode = "Delta"
');
```
## Arithmetic across columns
In `OPENQUERY`, math operators **need spaces around them**:
```sql
SELECT * FROM OPENQUERY(INSQL, '
SELECT DateTime, ReactLevel, Prodlevel, Sum = ReactLevel + Prodlevel
FROM WideHistory
WHERE DateTime > "2001-02-28 18:55:00"
AND DateTime < "2001-02-28 19:00:00"
AND wwRetrievalMode = "Cyclic"
');
```
`ReactLevel+Prodlevel` (no spaces) parses as a column-name suffix and breaks. `ReactLevel + Prodlevel` works.
## `GROUP BY` outside, raw rows inside
`GROUP BY` doesn't work inside `OPENQUERY`. Wrap and group outside:
```sql
SELECT TagName,
AvgValue = AVG(CAST(Value AS FLOAT))
FROM OPENQUERY(INSQL, '
SELECT TagName, Value FROM History
WHERE TagName IN ("SysTimeSec","SysTimeMin")
AND DateTime >= "2024-01-01 00:00"
AND DateTime <= "2024-01-01 01:00"
AND wwRetrievalMode = "Delta"
') x
GROUP BY TagName;
```
## Variant `vValue` filtering
Always cast — see [`02-syntax-limits.md`](02-syntax-limits.md#convert-on-vvalue-inside-openquery):
```sql
SELECT DateTime, vValue
FROM OPENQUERY(INSQL, '
SELECT DateTime, vValue
FROM History
WHERE TagName IN ("SomeFloatTag")
AND DateTime >= "2024-01-01 00:00"
AND DateTime <= "2024-01-01 01:00"
AND wwRetrievalMode = "Delta"
') x
WHERE CONVERT(float, vValue) = 20.0;
```
## Time-zone-aware queries
`wwTimeZone` re-projects timestamps into the named zone. SQL Server still does the **datetime math in server local time** before passing the predicates down — so `DATEADD(...)` works in server local, then results come back in the requested zone.
```sql
SELECT DateTime, TagName, Value
FROM History
WHERE TagName IN ('SysTimeHour','SysTimeMin','SysTimeSec')
AND DateTime > DATEADD(mi, -30, GETDATE())
AND wwTimeZone = 'Eastern Daylight Time';
```
## Time between value changes (`wwResolution` on output)
In `Cyclic`, `Delta`, `Full`, the `wwResolution` virtual column on output reports milliseconds since the previous row for the same tag. `-1` means the gap exceeded ~25 days (`> 2,147,000,000` ms). Doesn't apply to `AnalogSummaryHistory` / `StateSummaryHistory`.
```sql
SELECT DateTime, Value, wwResolution
FROM History
WHERE TagName = 'PumpRunning'
AND DateTime >= '2024-01-01 00:00'
AND DateTime < '2024-01-02 00:00'
AND wwRetrievalMode = 'Delta';
```
Total time the value matched a condition — sum the resolutions:
```sql
SELECT SUM(wwResolution) AS TotalMs
FROM OPENQUERY(INSQL, '
SELECT DateTime, Total = Pump1 + Pump2, wwResolution
FROM WideHistory
WHERE DateTime >= "2012-03-08 16:00"
AND DateTime < "2012-03-08 17:00"
AND wwRetrievalMode = "DELTA"
') x
WHERE Total = 0;
```
## `SELECT INTO` from history
Materializing a slice into a normal SQL Server table is supported via `OPENQUERY`:
```sql
DROP TABLE IF EXISTS dbo.MyTable;
SELECT DateTime,
Sec = DATEPART(ss, DateTime),
mS = DATEPART(ms, DateTime),
ReactTemp, ReactLevel
INTO dbo.MyTable
FROM OPENQUERY(INSQL, '
SELECT DateTime, ReactTemp, ReactLevel
FROM WideHistory
WHERE DateTime >= "2001-03-02 06:00"
AND DateTime <= "2001-03-02 07:00"
AND wwRetrievalMode = "Cyclic"
AND wwResolution = 60000
');
```
## `SliceBy` (analog summary tables)
`AnalogSummaryHistory` supports a `SliceBy` column that buckets results by the value of *another* tag at the same time. Useful for "summarize flow when a valve was in each state":
```sql
SELECT SliceByValue, TagName,
StartDateTime = MIN(StartDateTime),
EndDateTime = MAX(EndDateTime),
TotalTime = SUM(wwResolution),
Maximum = MAX(Maximum),
Total = SUM(Integral),
Average = SUM(Average*wwResolution)/SUM(wwResolution),
AvgOfAvg = AVG(Average)
FROM AnalogSummaryHistory
WHERE TagName = 'M31.FlowIn'
AND SliceBy = 'M31.ValveIn'
AND EndDateTime >= '2018-11-27 00:00'
AND StartDateTime <= '2018-11-28 00:00'
GROUP BY TagName, SliceByValue;
```
## Annotations
Free-text notes attached to a tag at a moment. Insert, then query:
```sql
DECLARE @UserKey INT;
SELECT @UserKey = UserKey FROM UserDetail WHERE UserName = 'wwAdmin';
INSERT INTO Annotation (TagName, UserKey, DateTime, Content)
VALUES ('ReactLevel', @UserKey, GETDATE(), 'The pump is off');
SELECT DateTime, TagName, Content
FROM Annotation
WHERE TagName = 'ReactLevel'
AND DateTime > '2024-02-27'
AND DateTime <= GETDATE();
```
## Bitwise extraction from a packed integer tag
When multiple discrete signals are packed into one PLC register and that register is mapped to a single Historian tag, use bitwise SQL:
```sql
SELECT
CONVERT(BIT, CAST(Value AS INT) & 1) AS Bit0,
CONVERT(BIT, CAST(Value AS INT) & 2) AS Bit1,
CONVERT(BIT, CAST(Value AS INT) & 4) AS Bit2,
CONVERT(BIT, CAST(Value AS INT) & 8) AS Bit3
FROM dbo.History
WHERE TagName = 'PackedDigitals'
AND DateTime >= '2024-01-01 00:00'
AND DateTime < '2024-01-01 01:00'
AND wwRetrievalMode = 'Delta';
```
`CAST(... AS INT)` caps the addressable bits at 32. Add `2^(bit-1)` for each subsequent bit.
## Comparison operators with `Delta`
The boundary semantics matter. With `>=` on the start, an initial value is synthesized at the start time. With `>`, no initial value is returned. Same for `<` / `<=` on the end. See PDF pp. 149-153 for the full grid.
```sql
-- "Anything strictly within the window, no boundary synthesis"
WHERE DateTime > '2024-01-01 00:00'
AND DateTime < '2024-01-02 00:00'
AND wwRetrievalMode = 'Delta';
```
## Retrieval across a data gap
When a tag has a gap (I/O server disconnect, undeployed object), retrieval inserts well-defined boundary points:
| Boundary | `Value` | `Quality` | `QualityDetail` |
| --- | --- | --- | --- |
| Last point before gap (closing point) | last good value | varies | varies |
| First point after gap (initial of next block) | snapshot | 0 | 96 (0x60 = 150 decimal in some prints) |
| Cyclic boundary inside the gap | `NULL` | 256 (0x100) | 0 |
Quality / value-deadband behavior across gaps: a value deadband is **not** applied across a NULL — every value immediately after a NULL is returned. A time deadband still suppresses values that fall within its window even across a gap.
## Mixing `wwCycleCount`, `wwResolution`, and `wwRetrievalMode`
Set behavior table (PDF p. 142):
| Mode | `wwResolution` | `wwCycleCount` | Result |
| --- | --- | --- | --- |
| `Cyclic` | N | 0 / unset | Apply N-ms resolution to all stored data in window. |
| `Cyclic` | 0 / unset | 0 | Server returns **100,000** rows per tag. |
| `Cyclic` | 0 / unset | N | N evenly-spaced rows. |
| `Cyclic` | N | (any — ignored) | N-ms resolution wins; cycle count ignored. |
| `Cyclic` | (none) | (none / negative) | **100** rows per tag (default). |
| `Delta` / `Full` | (any — ignored) | N | First N matching rows. |
| `Delta` / `Full` | (any — ignored) | (none) | All matching rows in the window. |
## Bounding-value behavior with stored proc / cursors
Any normal SQL Server stored procedure or server-side cursor can wrap an `INSQL` query — four-part, `OPENQUERY`, `OPENROWSET`, parameterized queries, views — to encapsulate complex retrieval. Useful for parameterizing what `OPENQUERY` itself can't.
## Aggregate same data four ways
The four documented ways to summarize:
1. SQL Server `AVG(Value)` over `Cyclic` rows — discrete sample average.
2. `wwRetrievalMode = 'Average'` — time-weighted, the usual right answer for analog process data.
3. `AnalogSummaryHistory` after configuring summary replication — pre-computed, cheapest at query time.
4. Event-subsystem summary into the `SummaryData` table — driven by the Event subsystem (which uses simple `AVG`).
Pick replication when the query rate is high and the summary is stable. Pick the `Average` mode when ad-hoc.
## Next
- [`06-alarms-events.md`](06-alarms-events.md) — alarm/event-specific queries.
- [`07-rest-api.md`](07-rest-api.md) — same data, no SQL needed.