Files
wwtools/histdb/02-syntax-limits.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

186 lines
7.5 KiB
Markdown

# 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.