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>
This commit is contained in:
Joseph Doherty
2026-05-03 18:22:20 -04:00
commit 32f26272ae
411 changed files with 69973 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
# Overview — connecting and querying
## What's actually behind a Historian query
```
client (SSMS / app / aalog-equivalent)
│ T-SQL
Microsoft SQL Server ◄── normal SQL Server tables (Tag, AnalogTag, EngineeringUnit, etc.)
│ OLE DB
INSQL linked server ◄── this is the AVEVA Historian OLE DB Provider, name "INSQL"
│ HCAL
Retrieval subsystem ◄── reads .aaLGX history blocks on disk, returns rowsets
```
Configuration and event metadata live in real SQL Server tables. **Time-series ("history") data lives in binary history blocks on disk** and is presented through `INSQL` as if it were a SQL table. That's why every history query goes through the linked server.
## The extension tables
The "virtual" tables exposed by `INSQL`:
| Linked-server path | Local view | Purpose |
| --- | --- | --- |
| `INSQL.Runtime.dbo.History` | `History` | Tall format. One row per (tag, timestamp). The default for everything historical. |
| `INSQL.Runtime.dbo.Live` | `Live` | Most recent value per tag. Fast snapshot. |
| `INSQL.Runtime.dbo.WideHistory` | `WideHistory` | Wide format — one row per timestamp, one column per tag. **Must be queried via `OPENQUERY`**. Up to 1024 columns. |
| `INSQL.Runtime.dbo.AnalogSummaryHistory` | `AnalogSummaryHistory` | Pre-summarized analog data. |
| `INSQL.Runtime.dbo.StateSummaryHistory` | `StateSummaryHistory` | Pre-summarized state-tag data. |
| `INSQL.Runtime.dbo.HistoryBlock` | `HistoryBlock` | Metadata about the on-disk blocks (no `WHERE` required). |
| `INSQL.Runtime.dbo.Events` | `Events` | Alarm/event records. See [`06-alarms-events.md`](06-alarms-events.md). |
**Legacy backward-compatibility tables** (still supported, not preferred):
`AnalogHistory`, `DiscreteHistory`, `StringHistory`, `AnalogLive`, `DiscreteLive`, `StringLive`, plus `v_AlarmHistory`, `v_AlarmHistory2`, `v_EventHistory`, `v_AlarmEventHistoryInternal2`, `v_AlarmEventHistory2`. Use the unified `History` / `Live` / `Events` tables for new work.
## Linking `INSQL` to SQL Server
Done automatically by the Historian installer. If you ever need to redo it manually:
```sql
EXEC sp_addlinkedserver @server='INSQL', @srvproduct='', @provider='INSQL';
EXEC sp_serveroption 'INSQL', 'collation compatible', true;
EXEC sp_addlinkedsrvlogin 'INSQL', 'TRUE', NULL, NULL, NULL;
-- Alias (used when joining the legacy analog and discrete tables in one query):
EXEC sp_addlinkedserver @server='INSQLD', @srvproduct='', @provider='INSQL';
EXEC sp_serveroption 'INSQLD', 'collation compatible', true;
EXEC sp_addlinkedsrvlogin 'INSQLD', 'TRUE', NULL, NULL, NULL;
```
The `INSQL` OLE DB provider cannot run standalone — it must be hosted by SQL Server.
## Four ways to issue a SELECT
You'll see all four in the wild; each has a niche.
### 1. Four-part naming (preferred for `History`, `Live`, `Events`)
```sql
SELECT *
FROM INSQL.Runtime.dbo.History
WHERE TagName = 'SysTimeSec'
AND DateTime >= '2001-09-12 12:59:00'
AND DateTime <= '2001-09-12 13:00:00';
```
The four parts: `linked_server.catalog.schema.object_name`. For `INSQL` the catalog is **always `Runtime`** and the schema is **always `dbo`**.
### 2. Provider view (sugar over four-part)
Each extension table has a SQL Server view of the same name — `History`, `Live`, `Events`, etc.
```sql
SELECT *
FROM History
WHERE TagName = 'SysTimeSec'
AND DateTime >= '2001-09-12 12:59:00'
AND DateTime <= '2001-09-12 13:00:00';
```
Backward-compatibility views use `v_<name>`.
### 3. `OPENQUERY` (required for `WideHistory`, useful for pass-through)
```sql
SELECT * FROM OPENQUERY(INSQL, '
SELECT DateTime, SysTimeSec, ReactTemp
FROM WideHistory
WHERE DateTime >= "2001-09-12 12:59:00"
AND DateTime <= "2001-09-12 13:00:00"
');
```
The body is sent verbatim to `INSQL`. Quoting is **double quotes inside, single quote around the whole body.** Hard caps and quirks:
- 8000-character body limit.
- DateTime format must be `yyyy-mm-dd hh:mm:ss.fff`.
- `ORDER BY` and `GROUP BY` don't work *inside* the body (SQL Server doesn't see them) — apply them outside.
- Joins inside the body are unsupported. Place them outside.
- Variables (`@foo`) cannot appear inside the body.
### 4. `OPENROWSET` (one-off ad-hoc)
```sql
SELECT * FROM OPENROWSET('INSQL', '', '
SELECT DateTime, Quality, QualityDetail, Value
FROM History
WHERE TagName IN ("SysTimeSec")
AND DateTime >= "2001-09-12 12:59:00"
AND DateTime <= "2001-09-12 13:00:00"
');
```
Requires that ad-hoc distributed queries be enabled on the SQL Server (`sp_configure 'Ad Hoc Distributed Queries', 1`). Generally not recommended in production code.
## When to use which style
| Scenario | Style |
| --- | --- |
| Tall queries against `History`, `Live`, `Events` | **Four-part** or **view** |
| `WideHistory` (one column per tag) | **OPENQUERY** (mandatory) |
| Heavy filtering you want SQL Server to optimize | **Four-part** (the optimizer can see the predicates) |
| Forcing a predicate down to `INSQL` that the optimizer is dropping | **OPENQUERY** (literal pass-through) |
| Joining a SQL Server table against a Historian extension table | Four-part, with **`INNER REMOTE JOIN`** |
| One-off scripts on a server with ad-hoc DQ enabled | **OPENROWSET** |
## Date format gotcha
For four-part queries on **non-English SQL Server installations** (French, German, etc.), the default datetime format may be `yyyy-dd-mm hh:mm:ss.fff` instead of `yyyy-mm-dd ...`. Use `CONVERT(...)` explicitly when in doubt — output formatting also depends on the OS regional settings.
For `OPENQUERY`, always use `yyyy-mm-dd hh:mm:ss.fff` regardless of locale.
## "No `WHERE` clause" error
If you get:
```
Server: Msg 7320, Level 16, State 2, Line 1
Could not execute query against OLE DB provider 'INSQL'.
[OLE/DB provider returned message: InSQL did not receive a WHERE clause from SQL Server. ...]
```
…the SQL Server query optimizer truncated the `WHERE` clause as "superfluous." Workaround: add a redundant condition such as `AND wwRetrievalMode = 'delta'`. See [`02-syntax-limits.md`](02-syntax-limits.md#where-clause-anomalies).
## Next
- [`02-syntax-limits.md`](02-syntax-limits.md) — what works and what doesn't, plus the `wwXxx` extensions overview.
- [`03-retrieval-modes.md`](03-retrieval-modes.md) — pick a `wwRetrievalMode`.
- [`05-query-recipes.md`](05-query-recipes.md) — practical patterns.
+185
View File
@@ -0,0 +1,185 @@
# SQL syntax — what works, what doesn't
The `INSQL` OLE DB provider supports a subset of T-SQL. Knowing the cliffs in advance saves a lot of "mysteriously empty rowset" debugging.
## Quick capability matrix
| Syntax element | Four-part / view | `OPENQUERY` |
| --- | --- | --- |
| `ORDER BY` | ✅ | ❌ inside the body. Wrap and order outside. |
| `GROUP BY` | ✅ | ❌ |
| `TagName IN (...)` | ✅ | ✅ |
| `TagName LIKE '...'` | ✅ | ✅ |
| Date/time functions (`DateAdd`, `GETDATE`, …) | ✅ | ✅ |
| `MIN`, `MAX`, `AVG`, `SUM`, `STDEV` | All | `MIN`, `MAX`, `AVG`, `SUM` only — no `STDEV` |
| Sub-`SELECT` (one normal SQL Server table + one extension table) | ✅ with restrictions | ❌ |
| Sub-`SELECT` (two extension tables) | ❌ | ❌ |
| Variables (`@foo`) | ✅ outside `OPENQUERY` | ❌ inside |
## Time-domain extensions (the `wwXxx` columns)
The Historian exposes "virtual columns" on every extension table that double as input parameters. They control retrieval semantics:
```text
wwCycleCount wwEdgeDetection wwFilter wwInterpolationType
wwOption wwQualityRule wwResolution wwRetrievalMode
wwStateCalc wwTimeDeadband wwTimeStampRule wwTimeZone
wwValueDeadband wwValueSelector wwVersion
```
Reserved / deprecated: `wwParameters`, `wwMaxStates` (reserved), `wwRowCount` (deprecated — use `wwCycleCount`).
Set them in the `WHERE`:
```sql
SELECT DateTime, Value
FROM History
WHERE TagName = 'SysTimeSec'
AND DateTime >= '2001-12-02 10:00:00'
AND DateTime <= '2001-12-02 10:02:00'
AND wwResolution = 10
AND wwRetrievalMode = 'cyclic';
```
**Three rules that bite:**
1. The provider is stateless. **Every extension parameter must be set in every query** that needs it — there is no session memory.
2. **`IN` and `OR` cannot multiplex an extension.** `wwVersion IN ('original','latest')` and `wwRetrievalMode = 'Delta' OR wwVersion = 'latest'` both fail.
3. Values are **case-insensitive** even on a case-sensitive SQL Server collation.
Each individual extension is documented in [`04-retrieval-options.md`](04-retrieval-options.md).
## `LIKE` — only `TagName` and `Value`
`LIKE` only works on the `TagName` and `Value` columns (and `Value LIKE` only against a string tag).
```sql
SELECT TagName, Value
FROM History
WHERE TagName LIKE 'Sys%'
AND DateTime > '1999-05-24 14:30:00'
AND DateTime < '1999-05-24 14:32:00';
```
## `IN` against `AnalogTag` / `DiscreteTag` / `StringTag` with `LIKE`
If you push a `LIKE` *inside* an `IN`-driven sub-`SELECT` against the typed tag tables, the query fails **unless you select the `vValue` column**. An `INNER REMOTE JOIN` is more efficient anyway:
```sql
-- Clunky (and only works if vValue is in the projection):
SELECT DateTime, TagName, vValue, Quality, QualityDetail
FROM History
WHERE TagName IN (SELECT TagName FROM StringTag WHERE TagName LIKE 'SysString')
AND DateTime BETWEEN '2001-06-21 16:00:00.000' AND '2001-06-21 16:40:00.000'
AND wwRetrievalMode = 'Delta';
-- Preferred:
SELECT h.TagName, DateTime, Value
FROM SnapshotTag st
INNER REMOTE JOIN INSQL.Runtime.dbo.History h
ON st.TagName = h.TagName
WHERE EventTagName = 'SysStatusEvent'
AND DateTime = '2001-12-20 0:00';
```
## Joins
- **Inside `OPENQUERY`: not supported.** Both implicit (`FROM Tag t, Live v`) and explicit (`JOIN`) joins fail. Always pull the join out:
```sql
SELECT v.DateTime, v.TagName, v.Value, e.Unit
FROM OPENQUERY(INSQL, '
SELECT DateTime, TagName, Value FROM Live
WHERE TagName LIKE "%Date%"
') v
JOIN AnalogTag t ON v.TagName = t.TagName
JOIN EngineeringUnit e ON t.EUKey = e.EUKey
ORDER BY t.TagName;
```
- **Joining a SQL Server table against an extension table: use `INNER REMOTE JOIN`** with the SQL Server table on the left and the extension table on the right:
```text
<SQLServerTable> INNER REMOTE JOIN <HistorianExtensionTable>
```
This forces SQL Server to send the join predicate down to the OLE DB provider in one round trip rather than dragging the whole extension table back to the engine.
## `CONVERT` on `vValue` inside `OPENQUERY`
`CONVERT(...)` against the variant `vValue` column is **not supported inside the `OPENQUERY` body**. Filter outside:
```sql
SELECT * FROM OPENQUERY(INSQL, '
SELECT DateTime, Quality, OPCQuality, QualityDetail, Value, vValue, TagName
FROM History
WHERE TagName IN ("SysTimeMin", "SysPulse")
AND DateTime >= "2001-12-30 04:00:00.000"
AND DateTime <= "2001-12-30 09:00:00.000"
AND wwRetrievalMode = "Delta"
')
WHERE CONVERT(float, vValue) = 20.0;
```
## Sub-SELECTs against a SQL Server table + extension table
Permitted but inefficient — SQL Server fans the whole sub-query out badly. Always rewrite as `INNER REMOTE JOIN`. Two-extension-table sub-SELECTs are not supported at all.
## Optimizer dropping criteria
The SQL Server optimizer sometimes decides a `WHERE` clause is "redundant" and never sends it to `INSQL`, producing wrong-but-not-erroring results. Diagnose with the SSMS query plan; remediate by rewriting so the criteria flow through a more direct path, e.g. **put the small driver table on the left of `INNER REMOTE JOIN`** so its tagnames flow into the right side:
```sql
DECLARE @TagList TABLE (TagName nvarchar(256));
INSERT @TagList SELECT 'SysTimeSec' UNION SELECT 'SysPerfCPUTotal';
-- Wrong: TagName criteria gets optimized out
SELECT DateTime, h.vValue, h.TagName
FROM History h
INNER REMOTE JOIN @TagList l ON h.TagName = l.TagName
WHERE DateTime >= DATEADD(hour, -1, GETDATE()) AND DateTime < GETDATE()
AND wwRetrievalMode = 'AVG' AND wwCycleCount = 1;
-- Right: driver table on the left, criteria reach the OLE DB provider
SELECT DateTime, h.vValue, h.TagName
FROM @TagList l
INNER REMOTE JOIN History h ON h.TagName = l.TagName
WHERE DateTime >= DATEADD(hour, -1, GETDATE()) AND DateTime < GETDATE()
AND wwRetrievalMode = 'AVG' AND wwCycleCount = 1;
```
## Variant columns + functions
If you wrap a variant column (`vValue`) with a function like `ROUND()`, SQL Server emits a syntax error that **doesn't propagate back through OLE DB** — you get silently wrong / empty rows. Cast first or filter on the typed `Value` column.
## `StartDateTime` cannot drive query criteria
Many retrieval modes return a `StartDateTime` for the cycle. You can `SELECT` it, but you **cannot use it in `WHERE`** — the predicate is silently ignored. Use `DateTime` only.
## Comparisons and `NULL`
A predicate like `Value > 0` will error when the provider returns a `NULL`. Always pair it with `AND Value IS NOT NULL`.
## `WHERE` clause anomalies
If the SQL Server optimizer decides your `WHERE` is "useless" (typically a `LIKE '%'` against `TagName`), it removes the clause entirely and you get the OLE DB error from [`01-overview.md`](01-overview.md). Add another harmless predicate to keep the clause "needed":
```sql
SELECT DateTime, Value, QualityDetail
FROM History
WHERE TagName LIKE '%'
AND wwRetrievalMode = 'delta'; -- forces the WHERE to survive optimization
```
## Compatibility notes for the older `datetime` type
If your client uses the older SQL Server `datetime` type (instead of `datetime2`), expect rounding to ~3.3 ms vs. the 100 ns native `FILETIME` resolution.
## What `OPENQUERY` and Microsoft Query do *not* talk
Microsoft Query (the legacy Excel/Access tool) cannot parse `OPENQUERY` statements. Use one of the other connection paths from Excel.
## Next
- [`03-retrieval-modes.md`](03-retrieval-modes.md) — every `wwRetrievalMode` value.
- [`04-retrieval-options.md`](04-retrieval-options.md) — every other `wwXxx` parameter.
+183
View File
@@ -0,0 +1,183 @@
# Retrieval modes (`wwRetrievalMode`)
Every history query has a retrieval mode. The default is **`Cyclic`** for analog tables (and analog/state summary tables) and **`Delta`** for `History`, discrete, and string tables. Set explicitly with `AND wwRetrievalMode = '<Mode>'` (case-insensitive).
## At-a-glance picker
| Want… | Use |
| --- | --- |
| Evenly spaced points across a window, fast | `Cyclic` |
| Smooth curves (analog) over a window | `Interpolated` |
| Only the *changes* — no duplicates | `Delta` |
| Every stored point, including duplicates | `Full` |
| A compromise: cyclic perf + delta accuracy | `BestFit` |
| Time-weighted average per cycle | `Average` |
| Min / max value per cycle | `Minimum` / `Maximum` |
| Area under the curve per cycle | `Integral` |
| Slope (units / time) per cycle | `Slope` |
| Throughput / accumulator (handles wraps + resets) | `Counter` |
| "How long was the tag in state X" | `ValueState` |
| "How long between consecutive entries into state X" | `RoundTrip` |
| Forecast points using simple linear regression | `Predictive` (a `wwFilter` mode, not a `wwRetrievalMode`) |
| Retrieve bracketing values just outside the window | `BoundingValue` |
## Mode catalog
Each section below covers: how the mode picks rows, which `wwXxx` options apply to it, the typical query shape, and the gotchas.
### `Cyclic` — evenly spaced cycle boundaries
- One row per cycle boundary (last value at or before the boundary).
- Number of cycles set by `wwCycleCount` *or* `wwResolution`. Default 100 cycles when neither is set.
- Fast and cheap. May *miss* spikes/gaps that fall between boundaries — for that use `BestFit`.
- Default mode for analog and analog-summary tables.
- Applies to all tag types.
- Options: `wwCycleCount`, `wwResolution`, `wwVersion`, `wwTimeStampRule`, `wwQualityRule`.
```sql
SELECT 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'
AND wwCycleCount = 60;
```
NULLs and initial values are returned as-is — no special handling.
### `Delta` — changes only
- Returns only physically stored points where the value (or quality) actually changed.
- If no point sits exactly on the start time, the **last value before the start** is returned with timestamp shifted to the start time and `Quality = 133` (initial-value flag).
- A NULL after a non-NULL is always returned; consecutive NULLs are suppressed.
- Default for `History`, discrete, and string tables.
- Applies to all tag types.
- Options: `wwTimeDeadband`, `wwValueDeadband`, `wwVersion`, `wwQualityRule`.
```sql
SELECT TagName, DateTime, Value, QualityDetail
FROM History
WHERE TagName = 'A001'
AND DateTime >= '2009-09-12 00:20'
AND DateTime <= '2009-09-12 00:40'
AND wwRetrievalMode = 'Delta';
```
`>=` on the start time pulls the initial value; `>` skips it. Same logic for `<` / `<=` on the end.
### `Full` — every stored point
- Returns all stored rows including duplicates.
- Best fidelity to what was actually written by the I/O server.
- Heavy — long windows × many tags = giant rowsets.
- Initial-value handling matches `Delta`.
- Applies to all tag types.
- Options: `wwVersion`, `wwQualityRule`.
### `Interpolated` — `Cyclic`, but with interpolation between actual points
- Cycle boundaries identical to `Cyclic`. At each boundary, the value is **linearly interpolated** between the surrounding stored points (or stair-stepped, if that's the tag's configured interpolation type).
- Per-tag interpolation comes from the `InterpolationType` column on the `AnalogTag` table, with system parameters `InterpolationTypeInteger` / `InterpolationTypeReal` filling in defaults. **`AND wwInterpolationType = 'Linear'`** in the query overrides everything for that query.
- A NULL preceding a cycle boundary causes a NULL at the boundary (no interpolation across NULLs).
- Useful for trend smoothness and for mixing slow + fast tags.
- Only meaningful for analog tags — others fall back to stair-step (effectively `Cyclic`).
- Options: `wwCycleCount`, `wwResolution`, `wwVersion`, `wwInterpolationType`, `wwTimeStampRule`, `wwQualityRule`.
### `BestFit` — five points per cycle, picked from real data
For each cycle returns up to **five** points: first, last, min (with its real timestamp), max (with its real timestamp), and the first non-Good-quality "exception." A point that fills two roles (e.g. last == max) is returned once. Cycles with no data return nothing. Initial and final values at the query boundaries are interpolated.
Combines cyclic-style cost with delta-style fidelity. **One week of 5-second data is ~120,960 rows in `Delta`, ~300 rows in `BestFit`.** Use it for long-window trends.
- Only applied to analog and analog-summary tags. Other tags get plain `Delta`.
- Options: `wwCycleCount`, `wwResolution`, `wwVersion`, `wwInterpolationType`, `wwQualityRule`.
- Quality detail of points returned from a cycle that contains a gap or an incomplete final cycle gets the `4096` "partial cycle" bit OR'd into `QualityDetail`.
### `Average` — time-weighted average per cycle
- One row per tag per cycle.
- For tags with **linear interpolation**, midpoints between stored points are weighted by elapsed time.
- For tags with **stair-step interpolation**, the earlier of two values carries the full interval.
- Differs from SQL `AVG()`, which is unweighted; the Historian's time-weighted average is generally more accurate for sparse data.
- Gaps (NULL ranges) are excluded from the calculation; the output column `TimeGood` reports total good time per cycle.
- Initial / final values: with `wwTimeStampRule='END'` the initial value is the average over the cycle leading up to the query start; with `START`, the final value covers the cycle after the query end.
- Only applies to analog and analog-summary tags.
- Options: `wwCycleCount`, `wwResolution`, `wwVersion`, `wwInterpolationType`, `wwTimeStampRule`, `wwQualityRule`.
### `Minimum` / `Maximum` — per-cycle extremes
- Returns the minimum (or maximum) **stored** point inside each cycle, at its real timestamp.
- Cycles with no data return nothing. Cycles containing a NULL return NULL.
- If multiple points tie, the earliest is returned.
- `QualityDetail` of returned cycle-extreme points has the `4096` "partial cycle" bit set when the cycle is incomplete.
- Only applied to analog tags; others fall back to `Delta`.
- Options: `wwCycleCount`, `wwResolution`, `wwVersion`, `wwTimeStampRule`, `wwQualityRule`.
### `Integral` — area under the curve
- Per-cycle integral of the tag value × time. Useful for things like "how many gallons" given a flow-rate tag.
- The integration uses linear or stair-step depending on the tag's configured / overridden interpolation type.
- Only applied to analog tags.
- Options: `wwCycleCount`, `wwResolution`, `wwVersion`, `wwInterpolationType`, `wwTimeStampRule`, `wwQualityRule`.
### `Slope` — units per time per cycle
- Per-cycle slope (rate of change). Linear regression across the cycle's points.
- Only analog tags.
- Options: `wwCycleCount`, `wwResolution`, `wwVersion`, `wwTimeStampRule`, `wwQualityRule`.
### `Counter` — accumulators with wrap and reset support
- For tags that monotonically increase but periodically reset (totalizers, throughput counters, manual resets).
- Returns the accumulated count per cycle, accounting for resets and configurable deadbands so brief drops aren't read as resets.
- Supports manually-reset counters and counter deadbands.
- Options: `wwCycleCount`, `wwResolution`, `wwVersion`, `wwTimeStampRule`, `wwQualityRule`.
### `ValueState` — time-in-state
- Calculates how long the tag spent in each value-state during each cycle.
- The aggregation type comes from `wwStateCalc`: `Minimum`, `Maximum`, `Average`, `Total`, `Percent` (and `*Contained` variants).
- If you specify a single value in the query (`AND Value = 'Open'`), one row per cycle. If you don't, one row per (state, cycle) — table-friendly but trend-unfriendly.
- Works for integer, discrete, string, and state-summary tags. Other types return nothing.
- NULLs are themselves a state.
- Options: `wwCycleCount`, `wwResolution`, `wwVersion`, `wwStateCalc`, `wwTimeStampRule`, `wwQualityRule`.
### `RoundTrip` — time between consecutive entries to a state
- Like `ValueState` but measures the **time between successive leading edges** of the same state — useful for cycle-time analysis ("how long between successive 'Start' events").
- `wwStateCalc` accepts `MinContained`, `MaxContained`, `AvgContained`, `TotalContained`, `PercentContained`.
- Options: `wwCycleCount`, `wwResolution`, `wwVersion`, `wwStateCalc`, `wwTimeStampRule`, `wwQualityRule`, `wwEdgeDetection`.
- Edge detection is configured via `wwEdgeDetection` (`Leading`, `Trailing`, `Both`) — see [`04-retrieval-options.md`](04-retrieval-options.md#wwedgedetection).
### `Predictive` — simple linear regression forecast
- Available from Historian 2014 R2 P01 onward.
- Implemented as a `wwFilter` mode rather than a distinct `wwRetrievalMode`. Future Historian releases are expected to add other prediction algorithms.
- Set `AND wwFilter = '<predictive expression>'` against an analog tag.
### `BoundingValue` — bracket the window
- Available from Historian 17.3.100 onward.
- Returns the **two stored points immediately surrounding the query window**, in addition to whatever falls inside it.
- Use when a chart renderer or trend client needs a value to anchor the line at the query edges without interpolation.
## Mixing modes per query
You can't. One `wwRetrievalMode` per query. To compare modes, run them as separate queries and `UNION ALL`.
## Initial-value semantics, in one place
| Mode | Initial value |
| --- | --- |
| `Cyclic`, `Full`, `Delta` | Last value before the start, timestamped at the query start. `Quality=133` if shifted; if no prior value exists, NULL with `Quality=1`, `QualityDetail=65536`. |
| `Interpolated`, `BestFit` | Linearly interpolated between the surrounding points at the start time. |
| `Average`, `Min`, `Max`, `Integral`, `Slope` | Calculated over the cycle adjacent to the boundary defined by `wwTimeStampRule`. |
| `Counter`, `ValueState`, `RoundTrip` | Result of applying the algorithm to the cycle preceding the query start. |
`>=` on the start pulls an initial value; `>` does not. Same for `<=` / `<` on the end.
## Next
- [`04-retrieval-options.md`](04-retrieval-options.md) — `wwCycleCount`, `wwResolution`, `wwInterpolationType`, deadbands, quality, value selector, and more.
- [`05-query-recipes.md`](05-query-recipes.md) — worked examples that combine modes and options.
+193
View File
@@ -0,0 +1,193 @@
# Retrieval options (the rest of the `wwXxx` parameters)
`wwRetrievalMode` picks the algorithm; everything below tunes it. Set in the `WHERE` clause exactly like `wwRetrievalMode`. Each is case-insensitive. None can be supplied via `IN` or `OR`.
## Which option applies to which mode
(Selecting the most-asked questions; consult the source PDF table on pp. 89-90 for the full grid.)
| Option | Cyclic | Delta | Full | Interpolated | BestFit | Average | Min | Max | Integral | Slope | Counter | ValueState | RoundTrip |
| --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| `wwCycleCount` | ✅ | (limit) | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `wwResolution` | ✅ | | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `wwTimeDeadband` | | ✅ | | | | – | – | – | | | | | |
| `wwValueDeadband` | | ✅ | | | | – | – | – | | | | | |
| `wwVersion` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `wwInterpolationType` | | | | ✅ | ✅ | ✅ | – | – | ✅ | ✅ | | | |
| `wwTimeStampRule` | ✅ | | | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `wwQualityRule` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `wwStateCalc` | | | | | | – | – | – | | | | ✅ | ✅ |
| `wwFilter` (analog filter) | ✅ where applicable | | | ✅ | ✅ | ✅ | – | – | | | | | |
| `wwValueSelector` (summary tags) | ✅ | | | | | – | – | – | | | | | |
| `wwTimeZone` | ✅ everywhere |
| `wwEdgeDetection` | | | | | | – | – | – | | | | | ✅ |
## `wwCycleCount` — number of cycles
```sql
AND wwCycleCount = 60 -- give me 60 evenly-spaced cycles across the window
```
For "truly cyclic" modes (`Cyclic`, `Interpolated`, `Average`, `Integral`) you get one row per tag per cycle. Other modes use cycles for grouping but may emit zero, one, or many rows per cycle.
For `Delta` / `Full`, `wwCycleCount` acts as a **limit on the first N matching rows** instead of a cycle count.
`wwCycleCount` and `wwResolution` are mutually exclusive intents — set one. If neither is set, the default cycle count is **100**.
## `wwResolution` — cycle length in milliseconds
```sql
AND wwResolution = 60000 -- 60-second cycles
```
`number_of_cycles = window_length / wwResolution`. Use this when "every minute" matters more than "100 evenly-spaced points." See "Phantom cycles" in the source PDF (p. 95) for what happens at the boundary edges.
## `wwTimeDeadband` — suppress fast changes (Delta only)
Milliseconds. Within `wwTimeDeadband` of the most recently returned point, further changes are dropped. The deadband resets every time a point is emitted.
```sql
AND wwRetrievalMode = 'Delta' AND wwTimeDeadband = 10000 -- ≤1 sample / 10 s
```
## `wwValueDeadband` — suppress small changes (Delta only)
Percent of the tag's engineering-unit full-scale. Drops changes smaller than the deadband from the previously returned point. Quality changes always force a row out, regardless of deadband.
```sql
AND wwRetrievalMode = 'Delta' AND wwValueDeadband = 1.0 -- 1% of full scale
```
## `wwVersion` — original vs. latest stored value
```sql
AND wwVersion = 'Latest' -- default; honors any post-write corrections
AND wwVersion = 'Original' -- as captured at write time
```
Latest values that have been overwritten carry `QualityDetail = 202`. Applies to every retrieval mode.
## `wwInterpolationType`
```sql
AND wwInterpolationType = 'Linear' -- override per-query
AND wwInterpolationType = 'StairStep'
```
Resolution order: query-level `wwInterpolationType` > per-tag `AnalogTag.InterpolationType` > system parameters `InterpolationTypeInteger` / `InterpolationTypeReal`. The output column also reports the type that was actually applied per row (`LINEAR` or `STAIRSTEP`).
## `wwTimeStampRule` — which end of the cycle owns the timestamp
```sql
AND wwTimeStampRule = 'START' -- default; row stamped at cycle start
AND wwTimeStampRule = 'END' -- row stamped at cycle end
```
Affects the `DateTime` column in cyclic-flavored modes. Cycle-start time is also exposed via the read-only `StartDateTime` column for diagnostic purposes (cannot be used in `WHERE` — see [`02-syntax-limits.md`](02-syntax-limits.md)).
## `wwTimeZone` — return data in a different zone
Accepts any value from the `TimeZone.TimeZone` column. The `TimeZone` table is rebuilt at every server start from the OS's registered zones, so it picks up custom zones automatically. DST is corrected against the **server** OS; there is no client-side zone option.
```sql
AND wwTimeZone = 'Eastern Standard Time'
```
If unset, the server's local zone is used. For UTC-only output use `wwTimeZone = 'UTC'`.
## `wwQualityRule` — how to combine quality
When combining multiple stored points into one cycle output (any cyclic mode plus `Average`, `Min`, `Max`, etc.), this controls how individual qualities collapse into a single output quality:
- `Good` (default for most cyclic modes) — output is Good only if every contributing point was Good.
- `Extended` — propagate worst quality with extra detail bits.
- Other rule names per source PDF p. 111. Eight worked queries on pp. 113-118.
`Cyclic`, `Full`, `Delta`, `Min`, `Max`, `BestFit` do **not** combine qualities — `wwQualityRule` is silently ignored for those.
## `wwStateCalc` — `ValueState` / `RoundTrip` aggregation
For `ValueState`:
- `Minimum` — shortest time in each unique state.
- `Maximum` — longest time in each unique state.
- `Average` — average time in each unique state.
- `Total` — total time in each unique state.
- `Percent` — total time as a percentage of cycle length.
For `RoundTrip`, only the `*Contained` variants are valid: `MinContained`, `MaxContained`, `AvgContained`, `TotalContained`, `PercentContained`. The "contained" forms only count fully-contained intervals — partial intervals at cycle boundaries are excluded, which matters for slow-changing tags vs. long cycles. The non-contained calculations have arbitrary cycle-boundary effects that can produce non-intuitive results for slow tags over long cycles.
## `wwFilter` — analog value filtering
Three filter functions, plus the predictive variant.
- `SigmaLimit(<n>)` — drop outliers more than *n* standard deviations from the mean.
- `ToDiscrete(<threshold>, <op>)` — convert analog to discrete; e.g. `ToDiscrete(5.0, >)` returns 1 above 5.0, 0 below. Supported ops: `>`, `>=`, `<`, `<=`, `=`, `<>`.
- `SnapTo(<step>, <min>, <max>)` — quantize the value to the nearest `step`, clamped to `[min, max]`. Snapped values get the `0x2000` `QualityDetail` bit.
- Predictive variant — see source PDF p. 86; SLR forecast over the cycle.
```sql
AND wwFilter = 'SnapTo(0.01, 0, 1000)'
AND wwFilter = 'ToDiscrete(5.0, >)'
```
Bad filter syntax returns a syntax error and zero rows.
## `wwValueSelector` — analog summary value pickoff
When an analog summary tag has multiple summarized values stored for one period, this picks which one to surface:
| Setting | Meaning |
| --- | --- |
| `AUTO` (default) | Mode-dependent — see PDF p. 125-128. |
| `FIRST` | First summarized value in the period. |
| `LAST` | Last value in the period. |
| `MIN` | Minimum value in the period (timestamp = where the min occurred). |
| `MAX` | Maximum value in the period (timestamp = where the max occurred). |
| `AVG` | Time-weighted average. |
## `wwEdgeDetection` (RoundTrip + alarm/event subqueries)
Picks which edges count for `RoundTrip`:
```sql
AND wwEdgeDetection = 'Leading' -- transition into the state
AND wwEdgeDetection = 'Trailing' -- transition out of the state
AND wwEdgeDetection = 'Both' -- either
```
Also used in event-pattern queries against the `WideHistory` table — see PDF pp. 78-85 for analog and discrete edge-detection examples.
## `wwOption` — reserved bit-bag
Catch-all for one-off feature flags AVEVA exposes per release. Almost never set in user queries; consult the source PDF if a feature note tells you to.
## Reserved / deprecated
| Name | Status |
| --- | --- |
| `wwParameters`, `wwMaxStates` | Reserved. Don't use. |
| `wwRowCount` | Deprecated alias for `wwCycleCount`. Still works, but write new queries with `wwCycleCount`. |
## Combining options
Multiple `wwXxx` are fine as long as each appears in a single `AND` predicate (no `IN`, no `OR`). A representative dense query:
```sql
SELECT TagName, DateTime, Value, StateTime, StartDateTime
FROM History
WHERE TagName IN ('Reactor1Level')
AND DateTime >= DATEADD(hour, -8, GETDATE()) AND DateTime <= GETDATE()
AND wwRetrievalMode = 'RoundTrip'
AND wwStateCalc = 'AvgContained'
AND vValue = CONVERT(SQL_VARIANT, '1')
AND wwCycleCount = 1
AND wwTimeStampRule = 'Start'
AND wwQualityRule = 'Good'
AND wwFilter = 'ToDiscrete(5.0,>)'
AND wwVersion = 'Latest';
```
## Next
- [`05-query-recipes.md`](05-query-recipes.md) — when these options come together to answer specific questions.
+333
View File
@@ -0,0 +1,333 @@
# 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.
+214
View File
@@ -0,0 +1,214 @@
# Alarms and events SQL
The Historian's `Events` table holds records emitted by Application Server's alarm/event subsystem — set, acknowledged, cleared, plus user actions (writes, secured/verified writes). This is **not** the legacy "Classic Event subsystem" — that's a separate data path documented elsewhere in the source PDF.
## Time columns and `wwTimeZone`
Two ways `Events` expresses time:
- **`EventTimeUtc`** — UTC. Not affected by `wwTimeZone`.
- **`EventTime`** (and similar) — local time on the Historian server, projected through `wwTimeZone` if it's set.
Pick one and stick with it within a query, or you'll cross zones unexpectedly when DST flips.
## Schema essentials
The columns referenced in the recipes below — there are more, see PDF p. 175+:
| Column | Meaning |
| --- | --- |
| `EventTime`, `EventTimeUtc` | When the event occurred (local / UTC). |
| `ReceivedTime` | When the Historian received it. |
| `Type` | `Alarm.Set`, `Alarm.Acknowledge`, `Alarm.Clear`, `Alarm.Write`, `User.Write`, `User.Write.Secured`, `User.Write.Verified`, `Application.Write`, … |
| `Alarm_ID` | GUID identifying the alarm instance — same across `Set` / `Ack` / `Clear` rows. |
| `Alarm_State` | `UNACK_ALM`, `ACK_ALM`, `UNACK_RTN`, `ACK_RTN`. |
| `Alarm_DurationMs` | Total alarm life. |
| `Alarm_UnAckDurationMs` | Time spent unacknowledged. |
| `Severity` | 1 = Critical, 2 = Major, 3 = Minor, 4 = Informational. |
| `Priority` | 1-999, lower = higher priority. |
| `Source_Area`, `Source_Object`, `Source_ConditionVariable` | Origin of the alarm. |
| `User_Name` | Operator who performed an action (ack, write). |
| `IsAlarm` | `true` for alarm-related events, `false` for plain events. |
| `Namespace` | The event tag's namespace. |
Property values are **case-sensitive**`"TRUE"`, `"True"`, and `"true"` are distinct strings as stored.
## Recipe 1 — list every event in a window
```sql
SELECT *
FROM Events
WHERE EventTime BETWEEN '2015-10-25 00:00' AND '2015-10-26 00:00';
```
Use this as a sanity probe — if it's empty, your time-zone or window assumptions are off.
## Recipe 2 — average alarm rate per hour
```sql
DECLARE @StartTime varchar(60) = '2015-10-25 12:00:00';
DECLARE @EndTime varchar(60) = '2015-10-26 12:00:00';
DECLARE @AlarmRaise TABLE (
EventTime nvarchar(60),
ID nvarchar(50),
AlarmState nvarchar(20),
SourceArea nvarchar(20),
SourceObject nvarchar(20)
);
INSERT @AlarmRaise
SELECT EventTime, Alarm_ID, Alarm_State, Source_Area, Source_Object
FROM Events
WHERE EventTime > @StartTime AND EventTime < @EndTime
AND Alarm_State = 'UNACK_ALM';
DECLARE @AlarmCounts TABLE (ForDate date, OnHour int, CountPerHour int);
INSERT @AlarmCounts
SELECT CAST(EventTime AS date), DATEPART(hour, EventTime), COUNT(*)
FROM @AlarmRaise
GROUP BY CAST(EventTime AS date), DATEPART(hour, EventTime);
SELECT AVG(CAST(CountPerHour AS INT)) AS [Average Alarm Rate on Hourly Basis]
FROM @AlarmCounts;
```
Pattern: stage matching alarm rows into a table variable, group, average. Filter on `Alarm_State = 'UNACK_ALM'` to count only fresh alarms (not re-emissions).
## Recipe 3 — most frequent alarms per hour
```sql
DECLARE @StartTime varchar(60) = '2017-11-10 12:00:00';
DECLARE @EndTime varchar(60) = '2017-11-10 12:10:00';
DECLARE @AlarmRaise TABLE (
EventTime nvarchar(60),
ID nvarchar(50),
AlarmState nvarchar(20),
SourceArea nvarchar(20),
SourceObject nvarchar(20),
SourceConditionVariable nvarchar(40)
);
INSERT @AlarmRaise
SELECT EventTime, Alarm_ID, Alarm_State,
Source_Area, Source_Object, Source_ConditionVariable
FROM Events
WHERE EventTime > @StartTime AND EventTime < @EndTime
AND Alarm_State = 'UNACK_ALM';
SELECT CAST(EventTime AS date) AS ForDate,
DATEPART(hour, EventTime) AS OnHour,
COUNT(*) AS [Count per Hour],
SourceObject + SourceConditionVariable AS [Alarm Attribute]
FROM @AlarmRaise
GROUP BY CAST(EventTime AS date),
DATEPART(hour, EventTime),
SourceObject, SourceConditionVariable
ORDER BY ForDate ASC, OnHour, [Alarm Attribute];
```
## Recipe 4 — alarms by area / object
```sql
SELECT Source_Area AS [Source Area/Object], COUNT(*) AS [Total]
FROM Events
WHERE EventTime > '2015-10-25 12:00:00'
AND EventTime < '2015-10-26 12:00:00'
AND Alarm_State = 'UNACK_ALM'
GROUP BY Source_Area
UNION ALL
SELECT Source_Object, COUNT(*)
FROM Events
WHERE EventTime > '2015-10-25 12:00:00'
AND EventTime < '2015-10-26 12:00:00'
AND Alarm_State = 'UNACK_ALM'
GROUP BY Source_Object;
```
Useful for "where in the plant are alarms coming from."
## Recipe 5 — average time-to-acknowledge per hour and severity
```sql
DECLARE @start datetime2 = '2017-12-11';
DECLARE @end datetime2 = '2017-12-12';
SELECT DATEADD(hour, DATEDIFF(hour, 0, e.EventTime), 0) AS hour,
e.Severity,
AVG(Alarm_UnAckDurationMs) AS avg_unack,
COUNT(*) AS count
FROM Events e
WHERE e.EventTime < @end
AND e.EventTime >= @start
AND e.Severity <= 3 -- 1=Critical, 2=High, 3=Medium, (4=Low)
AND e.Type = 'Alarm.Acknowledged'
GROUP BY DATEADD(hour, DATEDIFF(hour, 0, e.EventTime), 0), Severity
ORDER BY hour, Severity;
```
`Alarm_UnAckDurationMs` is set on the `Alarm.Acknowledged` row; that's why you filter on `Type = 'Alarm.Acknowledged'`.
To break it down by user instead:
```sql
SELECT DATEADD(hour, DATEDIFF(hour, 0, e.EventTime), 0) AS hour,
e.User_Name,
AVG(Alarm_UnAckDurationMs) AS avg_unack,
COUNT(*) AS count
FROM Events e
WHERE e.EventTime BETWEEN @start AND @end
AND e.Type = 'Alarm.Acknowledged'
GROUP BY DATEADD(hour, DATEDIFF(hour, 0, e.EventTime), 0), e.User_Name;
```
## Recipe 6 — alarm life cycle (raise → ack → clear)
```sql
DECLARE @StartTime varchar(60) = '2017-12-12 12:00:00';
DECLARE @EndTime varchar(60) = '2017-12-12 12:02:00';
DECLARE @AlarmRaise TABLE (EventTime nvarchar(60), ID nvarchar(50), AlarmState nvarchar(20));
INSERT @AlarmRaise
SELECT EventTime, Alarm_ID, Alarm_State FROM Events
WHERE EventTime > @StartTime AND EventTime < @EndTime
AND Alarm_State = 'UNACK_ALM';
DECLARE @AlarmAck TABLE (EventTime nvarchar(60), ID nvarchar(50), UnAckDuration nvarchar(20));
INSERT @AlarmAck
SELECT EventTime, Alarm_ID, Alarm_UnAckDurationMs FROM Events
WHERE EventTime > @StartTime AND EventTime < @EndTime
AND Alarm_Acknowledged = 1;
DECLARE @AlarmClear TABLE (EventTime nvarchar(60), ID nvarchar(50), AlarmDuration nvarchar(20));
INSERT @AlarmClear
SELECT EventTime, Alarm_ID, Alarm_DurationMs FROM Events
WHERE EventTime > @StartTime AND EventTime < @EndTime
AND Type = 'Alarm.Clear';
SELECT 'Alarm Life - ' + s.ID AS Tag,
CASE WHEN a.EventTime > c.EventTime THEN 'Cleared Before Ack'
WHEN a.EventTime < c.EventTime THEN 'Acked Before Clear'
ELSE '-' END AS Comment,
s.EventTime AS AlarmRaised,
a.EventTime AS AlarmAcked,
c.EventTime AS AlarmClear,
a.UnAckDuration AS UnAckDuration,
c.AlarmDuration AS AlarmDuration
FROM (@AlarmRaise s INNER JOIN @AlarmClear c ON c.ID = s.ID)
LEFT JOIN @AlarmAck a ON a.ID = c.ID AND a.EventTime <> c.EventTime
ORDER BY AlarmRaised ASC;
```
The `LEFT JOIN` keeps unacknowledged-but-cleared alarms in the report. The `Cleared Before Ack` / `Acked Before Clear` heuristic is useful for diagnosing alarm-flood vs. proper-handling patterns.
## Notes
- **Alarm IDs are GUIDs and persist across the lifecycle.** Joining `Set` / `Ack` / `Clear` rows by `Alarm_ID` is the only safe way to reconstruct an alarm timeline.
- **Severity values are inverted relative to priority** — 1 is highest severity, 4 is lowest. Don't confuse with `Priority` (1 = highest priority, 999 = lowest).
- **`Alarm_State`** uses two-character suffixes: `_ALM` for active, `_RTN` for return-to-normal. Combine with `UNACK_` / `ACK_` for the four states.
- The PDF chapter dedicates pp. 175-181 to these patterns. For full property catalog see PDF pp. 202-207.
## Next
- [`07-rest-api.md`](07-rest-api.md) — same data over HTTP.
+172
View File
@@ -0,0 +1,172 @@
# Historian Data REST API
For when the consumer can't (or shouldn't) speak SQL — browsers, Excel, Power BI, Tableau, web frontends. Same data as the SQL extension tables, exposed as OData over HTTPS.
## Versions
Two API versions are supported by AVEVA Insight:
| Version | Recommended? | Notes |
| --- | --- | --- |
| `v2` | **Yes** | `TagFilter` (POST/GET) on `ProcessValues`, `AnalogSummary`, `StateSummary`. Single quotes are NOT used around `DateTime` in `$filter` (except for events). Summary entities use `StartDateTime` / `EndDateTime`; raw / process value entities use `DateTime`. |
| `v1` | Legacy | Still works. Uses `datetimeoffset'...'` quoting. Includes summary entities (`Summary`, `Daily`, `Hourly`, `Minutely`) that are v1-only. |
Pick **v2** for new code unless you need a v1-only summary entity.
## Authentication
Two flavors:
- **AVEVA Historian Insight (on-premises)** — Windows integrated security via Negotiate. The user must belong to `aaAdministrators`, `aaPowerUsers`, or `aaUsers`. Most modern browsers and Excel handle this transparently.
- **AVEVA Insight (cloud)** — OpenID Connect bearer-token, or Basic auth (`username` is treated as the API key). Some clients (older Excel) lack OIDC support; Basic remains as a fallback.
There's also a "solution-specific retrieval token" path: include the token in the URL and skip the `s/<solution_id>` segment.
## Endpoint shape
```
https://online.wonderware.com/s/<solution_id>/apis/historian/<api_version>/<resource>?<query_parameters>
```
- `<solution_id>` — your Insight solution. Omitted when using a solution-specific retrieval token.
- `<api_version>``v1` or `v2`.
- `<resource>` — one of the resources below.
- `<query_parameters>` — OData (`$filter`, `$top`, `$skip`, `$orderby`, `$select`, `$skiptoken`) or REST-style (`TagFilter=...&Resolution=...`).
In the URL: **`%20`** is space, **`%27`** is a single quote. The Chrome JSONView viewer also accepts `+` for space, which makes URLs more readable.
## OData operators
`eq`, `ne`, `gt`, `ge`, `lt`, `le`, `and`, `or`, `not`. Use parentheses for grouping. **A `$filter` may contain at most one time clause combined with one filter clause via `and`.** The filter clause itself may use any combination of operators internally.
If you need multiple values for the same field, use `or`:
```
((Priority eq 100 or Priority eq 200 or Priority eq 500) and (Severity le 2))
```
## `TagFilter` (REST-style)
Supported on `ProcessValues`, `AnalogSummary`, `StateSummary` (`v2`+). OData filter notation, with hard limits:
- Up to **20** `AND` clauses.
- Up to **20** `OR` clauses.
- Up to **2** UDF (user-defined function) clauses.
- **Cannot mix `AND` and `OR`** in the same `TagFilter`.
- Operators must be lowercase (`and`, not `And`).
- Most attribute searches are case-insensitive; case-sensitive ones are: `InterpolationType`, `MessageOn`, `MessageOff`.
Valid:
```
.../v2/ProcessValues?TagFilter=startswith(Source,'MVDS') and TagType eq 'string'
```
Invalid (mixed-case operator):
```
.../v2/ProcessValues?TagFilter=startswith(Source,'MVDS') And TagType eq 'string'
```
## Retrieval resources (the catalog)
| Resource | Verb(s) | Purpose |
| --- | --- | --- |
| `/ProcessValues` | GET | Raw value+time+quality (VTQ) records for one or more tags. |
| `/AnalogSummary` | GET | Aggregated analog statistics (min/max/avg/integral/etc.) over a window. |
| `/StateSummary` | GET | Time-in-state summaries for discrete/string/integer tags. |
| `/Events` | GET | Events and alarms (same content as the SQL `Events` table). |
| `/Tags` | GET / POST / DELETE | Tag metadata; v2 also returns extended properties. POST manages tags in bulk. |
| `/TagProperties` | GET | List of property names available on tags. |
| `/TagPropertyValues` | GET | Distinct values seen for a given tag property. |
| `/TagGroups` | GET | Groups of tags. |
| `/TagSuggest` | GET | Autocomplete-style tag-name suggestions. |
| `/TagSearch` | GET | Search tags by partial name / property. |
| `/TagExtendedProperties` | GET | Extended (custom) tag properties. |
| `/Summary` | GET | **v1-only.** Generic summary. |
| `/Daily`, `/Hourly`, `/Minutely` | GET | **v1-only.** Pre-bucketed summary periods. |
| `/SystemParameters` | GET | **On-premises only.** Server config. |
## `RetrievalMode` and `Resolution` (REST-side names)
Same modes as the SQL `wwRetrievalMode`. The REST query parameter is named `RetrievalMode`. Supported values from the v2 spec: `BestFit`, `Counter`, `Delta`, `Full`, `Interpolated`, `Maximum`, `Slope` (and `Cyclic`, the default for analog).
`Resolution` is in milliseconds, applies to `Raw`, `ProcessValues`, and the summary entities — picks the granularity of the returned data.
## `ProcessValues` — VTQ records
```http
GET /v2/ProcessValues?$filter=FQN+eq+'plant12.pump6'+and+DateTime+gt+2017-07-13T00:00:00Z
```
Required: `FQN` (`datasource.tagname`), and a time predicate (`DateTime` for v2). Optional: `OPCQuality`, `Value`, `Text` (string tags / discrete-tag message text). Returns 200 with `{ fqn, datetime, ... }`. 404 / 401 on missing FQN / unauthorized.
## `AnalogSummary` — windowed analog stats
```http
GET /v2/AnalogSummary
?$filter=FQN+eq+'Depot.Train09'
+and+StartDateTime+ge+2017-06-09T09:00:00-07:00
+and+EndDateTime+le+2017-06-09T10:00:00-07:00
&Resolution=3600000
```
Required: `FQN`, `StartDateTime`, `EndDateTime` (RFC 3339 / ISO 8601, UTC `Z` or explicit offset). Optional: `RetrievalMode` (`Cyclic`/`Full`, default `Cyclic`), `Resolution`, `SliceBy` (split summary by another tag's value at the same time, up to 10 SliceBy tags), `SliceByValue` (filter criterion for SliceBy), `OPCQuality`. Quality logic mirrors the SQL `wwQualityRule = 'Good'`: all-Good → Good, full gap → Bad, mixed → Doubtful (`64`).
## `StateSummary` — windowed time-in-state
Same shape as `AnalogSummary` but for state-tag data. Use `RetrievalMode=Cyclic` and a `Resolution` to bucket by time.
## `Events` — alarms and events
```http
GET /v2/Events?$filter=EventTime+gt+'2017-07-13T00:00:00'
```
Note the **single quotes around the datetime** for events specifically — events break the v2 "no quotes" rule because `EventTime` is treated as a string-style column.
Filterable optional parameters include `ID` (alarm GUID), `EventTime`, `Type` (`Alarm.Set` / `Alarm.Acknowledge` / `Alarm.Clear` / `Alarm.Write` / `User.Write` / `User.Write.Secured` / `User.Write.Verified` / `Application.Write`), `Priority` (1-999, lower = higher), `Namespace`, `Severity` (1=Critical, 2=Major, 3=Minor, 4=Informational), `EventTimeUTCOffsetMins`, `ReceivedTime`, `IsAlarm`. **Property values are case-sensitive.**
## Pagination — `$skiptoken`
Responses larger than ~5000 records are paginated. The response body includes a `nextLink` containing `$skiptoken=...` — follow it to fetch the next page. You don't need to construct `$skiptoken` yourself.
## Worked browser query
```
https://online.wonderware.com/s/ik97r5/apis/historian/v2/ProcessValues
?$filter=FQN+eq+'Baytown.tank_level'
+and+DateTime+gt+2017-07-13T00:00:00
&$top=200
```
Returns up to 200 raw VTQ records since the timestamp.
## Excel / Power BI hookups
The PDF chapter (pp. 232-240) walks through:
- **Excel** — `Data → Get Data → From Web`, paste the OData URL, sign in. Excel can refresh the connection on demand.
- **Postman** — pick GET, paste URL, set Auth (Basic for cloud / Negotiate for on-prem), Send.
- **Power BI** — `Get Data → OData feed`, paste the resource root (`.../v2/ProcessValues` or similar), authenticate, then build the report.
The shape of the data Power BI sees is identical to a JSON pull, so any aggregation you'd do in Power Query mirrors what `$filter` would have selected server-side.
## SSRS — Reporting Services extension
The **AVEVA Historian SQL Server Reporting Services Extension** lets SSRS report builders consume the same `INSQL` linked server queries documented in [`05-query-recipes.md`](05-query-recipes.md). PDF p. 241. Useful when the consumer is corporate reporting infrastructure rather than ad-hoc analysts.
## Errors
| Code | Meaning |
| --- | --- |
| 200 | OK with body. |
| 401 Unauthorized | Token expired / missing membership in the `aa*` Windows groups. |
| 404 Not Found | FQN doesn't exist, or solution / resource path wrong. |
| 5xx | Historian / Insight server-side issue. Retry with backoff. |
## Cross-references
- For the full property catalog of each resource, see PDF chapter "Browser-Friendly Data Retrieval" pp. 183-240.
- For SQL equivalents of every retrieval mode the REST API exposes, see [`03-retrieval-modes.md`](03-retrieval-modes.md).
- For options that map across (`Resolution``wwResolution`, `RetrievalMode``wwRetrievalMode`), see [`04-retrieval-options.md`](04-retrieval-options.md).
+51
View File
@@ -0,0 +1,51 @@
# histdb
LLM-oriented reference for **AVEVA Historian** SQL retrieval — the Microsoft SQL Server linked-server (`INSQL`), the extension tables (`History`, `Live`, `WideHistory`, `AnalogSummaryHistory`, `StateSummaryHistory`, `Events`), the `wwXxx` time-domain extensions, every retrieval mode, and the Historian Data REST API. Distilled from the official *AVEVA Historian Retrieval Guide* (December 2020 edition, 243 pages).
## What this folder is for
Read-only reference. There's no code here — these files exist so an LLM can answer "how do I get data X out of Historian" without paging through the official PDF. For Galaxy / object configuration the right tool is [`graccesscli`](../graccesscli/README.md); for the System Platform Galaxy Repository (a different SQL database) it's [`grdb`](../grdb/README.md). This folder is specifically the **time-series / event store**.
## Hard constraints / things to know up front
- **Historian SQL access goes through a linked server named `INSQL`** that wraps the AVEVA OLE DB provider over the binary history blocks. The OLE DB provider is **stateless** — every query must repeat its `wwXxx` extension parameters.
- **A `WHERE` clause is mandatory** on every extension table except `HistoryBlock`. Date ranges (`DateTime >= ... AND DateTime <= ...`) plus tag selection (`TagName = ...` / `TagName IN (...)` / `TagName LIKE ...`) is the minimum.
- **All times are UTC internally; the wire format is local time** (or whatever `wwTimeZone` says). Time math uses Win32 `FILETIME` (100 ns resolution).
- **Wide tables (`WideHistory`) require `OPENQUERY`** — the schema is built per query. Up to 1024 columns. Query bodies inside `OPENQUERY` are capped at 8000 characters.
- **`IN` and `OR` cannot multiplex a `wwXxx` extension.** `wwVersion IN ('original','latest')` and `wwRetrievalMode = 'Delta' OR wwVersion = 'latest'` both fail. One value per extension per query.
- **The full PDF is canonical** — these summaries reduce 243 pages to the essentials. When in doubt, check the source: <https://cdn.logic-control.com/docs/aveva/historian/HistorianRetrieval.pdf>.
## Layout
```text
histdb/
README.md this file
01-overview.md OLE DB provider, linked server, extension tables, four-part / OPENQUERY / OPENROWSET
02-syntax-limits.md Supported / unsupported SQL features, joins, sub-SELECTs, time-domain extensions overview
03-retrieval-modes.md Every wwRetrievalMode value (Cyclic, Delta, Full, Interpolated, BestFit, Average, Min, Max, Integral, Slope, Counter, ValueState, RoundTrip, Predictive, BoundingValue)
04-retrieval-options.md Every wwXxx parameter (CycleCount, Resolution, deadbands, Version, InterpolationType, TimeStampRule, TimeZone, QualityRule, StateCalc, Filter, ValueSelector)
05-query-recipes.md Practical SQL recipes (wide-table joins, INNER REMOTE JOIN, aggregate patterns, time-between-changes, gaps)
06-alarms-events.md Events table queries — listing events, alarm rates, time-to-clear, response-time analysis
07-rest-api.md Historian Data REST API — endpoints, $filter / OData syntax, auth, examples
```
## Resource index — by task
| Task | Go to |
| --- | --- |
| Connect to Historian via SQL Server, pick a query style (four-part / view / OPENQUERY / OPENROWSET) | [`01-overview.md`](01-overview.md) |
| What SQL syntax works and what doesn't (LIKE, IN, OR, joins, CONVERT, sub-SELECTs) | [`02-syntax-limits.md`](02-syntax-limits.md) |
| Pick the right retrieval mode for the question being asked | [`03-retrieval-modes.md`](03-retrieval-modes.md) |
| Tune what the mode returns (cycle count, resolution, deadbands, quality rule, value selector, time zone) | [`04-retrieval-options.md`](04-retrieval-options.md) |
| Worked SQL recipes for common questions (wide tables, aggregates, gaps, GROUP BY) | [`05-query-recipes.md`](05-query-recipes.md) |
| List events / count alarms / measure time-to-acknowledge | [`06-alarms-events.md`](06-alarms-events.md) |
| Pull data from a browser, Excel, Power BI, or any HTTP client (no SQL needed) | [`07-rest-api.md`](07-rest-api.md) |
| Original PDF — definitive source for any uncovered detail | <https://cdn.logic-control.com/docs/aveva/historian/HistorianRetrieval.pdf> |
## Source
Source PDF: *AVEVA Historian Retrieval Guide* (formerly Wonderware), publication date 3 December 2020, downloaded from `cdn.logic-control.com/docs/aveva/historian/HistorianRetrieval.pdf`. The guide describes Historian retrieval; companion volumes (administration, database reference, glossary) are *not* covered here — see references inside each deep doc.
## Maintenance
This folder follows the doctrine in [`../DOCS-GUIDE.md`](../DOCS-GUIDE.md). When AVEVA ships a new Retrieval Guide that adds modes, options, or REST endpoints, update the affected deep doc(s) and bump any new entries into the Resource index above. The root [`../CLAUDE.md`](../CLAUDE.md) holds one row pointing here — don't fan out per-file links from the root.