Fabric Eventhouse / KQL Database
KQL management and query patterns specific to Fabric Eventhouse. Assumes familiarity with KQL — focus is on Fabric-specific behavior and az-rest-driven workflows.
Connection
- Cluster URI: each KQL Database has its own
queryServiceUri,https://<cluster>.kusto.fabric.microsoft.com. Discover withGET /v1/workspaces/{wsId}/kqlDatabases(returnsqueryServiceUri+databaseNameper item). - Token audience:
https://kusto.kusto.windows.net/.defaultfor all direct access. A wrong audience surfaces as a 401 "Request is invalid and cannot be processed", not an auth error. - Query endpoint:
POST {clusterUri}/v1/rest/query, body{"db":"<dbName>", "csl":"<KQL>"}. - KQL
|breaks shell escaping — write the JSON body to a temp file and pass--body @<file>, never inline.
Worked az rest invocation and the REST item-definition envelope:
references/programmatic-access.md.
Schema Discovery
.show tables
.show table T schema as json // column names + types
.show table T details // row count, extent count, size
.show functions
.show materialized-views
.show database principals // who has what role
Schema Evolution
.create-merge tableis the safe / idempotent form — adds missing columns, never drops existing. Prefer over.create tablefor repeatable deployments..alter-merge table T (NewCol: string)— add column..rename column T.OldName to NewName— rename..drop column T.OldCol— irreversible; fails if column is used in materialized view or function (drop dependents first)..drop table T ifexists— guarded drop.- Atomic blue-green swap via
.rename tables A=B, B=C, C=A(single command, atomic).
With OneLake availability ON the surface narrows: adding and deleting columns work (April 2026+), but altering a column type, renaming a table, applying RLS, and deleting/truncating/purging data do not — toggle availability off, change, toggle back on. Full matrix: references/onelake-availability.md.
Ingestion
| Command | Use for |
|---|---|
.ingest inline into table T <| |
Small data / testing |
.set-or-append T <| |
Append KQL query results |
.set-or-replace T <| |
Replace table contents from a query |
.ingest into table T (h'abfss://...;impersonate') |
Blob / ADLS / OneLake files — the ;impersonate suffix is required |
Streaming ingestion is per-table and must be enabled first:
.alter table Events policy streamingingestion enable.
Schema-associated Eventstream destinations auto-create one table per schema named
{CloudEventType}_{CloudEventSchemaVersion}(e.g.Orders_v1) — don't create these by hand. Seefabric-eventstream.
Full command syntax and CSV/JSON mapping definitions: references/ingestion.md.
Policies
| Policy | Purpose | Notes |
|---|---|---|
| Retention | Soft-delete period + recoverability | JSON body with SoftDeletePeriod and Recoverability |
| Caching | Hot (SSD) vs cold tier window | .alter table T policy caching hot = 30d |
| Partitioning | Hash key for extent pruning | Hash on column with XxHash64, MaxPartitionCount |
| Merge | Background extent merge thresholds | RowCountUpperBoundForMerge, MaxExtentsToMerge |
| Streaming ingestion | Enable streaming endpoint | .alter table T policy streamingingestion enable |
Database-level policies (.alter database MyDB policy ...) act as defaults; table-level overrides them. Check effective policy with .show table T policy retention.
Materialized Views
.create materialized-view with (backfill=true) EventCounts on table Events {
Events | summarize Count = count(), LastSeen = max(Timestamp) by EventType
}
// Lifecycle
.show materialized-view EventCounts statistics
.disable materialized-view EventCounts
.enable materialized-view EventCounts
.drop materialized-view EventCounts
// Query a materialized view
materialized_view("EventCounts") | where EventType == "Login"
Stored Functions and Update Policies
.create-or-alter function takes docstring, folder, and default parameter
values — see references/REFERENCE.md. The Fabric-specific
piece is wiring one as an update policy, an automatic transform applied on
ingestion into the source table:
.create-or-alter function ParseRawEvents() {
RawEvents
| extend Parsed = parse_json(RawData)
| project
Timestamp = todatetime(Parsed.timestamp),
UserId = tostring(Parsed.userId)
}
.alter table ParsedEvents policy update
@'[{"IsEnabled":true,"Source":"RawEvents","Query":"ParseRawEvents()","IsTransactional":true}]'
IsTransactional: true makes the source-row insert and the transform atomic — failed transform aborts both.
External Tables (OneLake / ADLS)
.create external table ExternalSales (
OrderDate: datetime, ProductId: string, Quantity: int, Amount: real
) kind=storage
dataformat=parquet
( h'abfss://workspace@onelake.dfs.fabric.microsoft.com/lakehouse.Lakehouse/Tables/sales;impersonate' )
external_table("ExternalSales") | where OrderDate > ago(30d) | summarize sum(Amount) by ProductId
Permission Model
| Role | Query | Ingest | Admin |
|---|---|---|---|
viewer |
✅ | ❌ | ❌ |
user |
✅ | ❌ | ❌ |
ingestor |
❌ | ✅ | ❌ |
admin |
✅ | ✅ | ✅ |
.add database MyDB viewers ('aaduser=user@contoso.com')
.add database MyDB admins ('aaduser=admin@contoso.com')
.add table T admins ('aaduser=tableadmin@contoso.com')
Layered with Fabric workspace roles (Admin/Member/Contributor/Viewer). restrict access and security functions provide RLS.
Query Patterns
| Pattern | Why |
|---|---|
Always filter by time first — where Timestamp > ago(...) |
Enables extent pruning |
Use has over contains |
has uses term index (fast); contains is substring scan (slow) |
project early to drop unused columns |
Reduces memory |
summarize with bin(Timestamp, 1h) |
Efficient time bucketing |
take 100 for exploration |
Avoids full scans |
materialize() reused subexpressions |
Caches intermediate result |
Avoid * in project |
Explicit column list survives schema changes |
String matching speed
| Operator | Indexed | Case-Sensitive | Speed |
|---|---|---|---|
== |
✅ | Yes | Fastest |
=~ |
❌ partial | No | Medium |
has |
✅ | No | Fast |
has_cs |
✅ | Yes | Fast |
startswith |
partial | No | Medium |
contains |
❌ | No | Slow |
matches regex |
❌ | Yes | Slowest |
Monitoring
.show queries // currently running
.show commands | where StartedOn > ago(1h)
.show journal // management ops history
.show ingestion failures | where FailedOn > ago(24h)
.show table T extents // per-table extent count + size
.show database datastats // DB size, extent count, row count
Cross-Database Queries
database("OtherDB").OtherTable | take 10
Graph semantics
KQL can do graph analysis inside the KQL engine — make-graph for a
transient graph, graph() over a persistent snapshot, then graph-match /
graph-shortest-paths. This is not the standalone Fabric GraphModel
item, which is a separate workload using GQL / ISO-39075 (see fabric-graph).
Same word, different engine.
Operators, snapshot management and limits, and the openCypher/GQL-over-KQL preview: references/graph-operators.md.
Gotchas
| Issue | Cause | Fix |
|---|---|---|
Request is invalid and cannot be processed (401) |
Wrong token audience | Use https://kusto.kusto.windows.net/.default |
dynamic column shows as string |
Stored as string, not dynamic | Wrap with parse_json(col) or todynamic() |
Forbidden (403) on management commands |
Insufficient role | Need admin or ingestor database role |
| OneLake / ADLS ingest auth fails | Missing ;impersonate on URI |
Append ;impersonate to the storage URI |
| Materialized view stuck at 0% | No new source data or backfill pending | .show materialized-view MV statistics |
.drop column fails |
Column referenced by materialized view or function | Drop dependents first |
| Streaming ingestion errors | Streaming policy not enabled | .alter table T policy streamingingestion enable |
| External table returns no data | Path / format / schema mismatch | Verify abfss:// path, dataformat=, and column types match source |
| Retention deleting data too soon | Table-level policy overrides DB default | .show table T policy retention |
dcount() returns approximate value |
HyperLogLog by design | dcount(col, 4) for higher accuracy (costly), or T | distinct col | count for exact |
Remote MCP server (preview)
A hosted HTTP MCP server per KQL database gives schema discovery, NL→KQL, and
query execution. Claude Code cannot connect to it — the
api.fabric.microsoft.com/v1/mcp/* endpoints require OAuth Dynamic Client
Registration it doesn't support, so it appears in config and silently fails to
connect. From Claude Code use the local fabric-rti-mcp
(uvx microsoft-fabric-rti-mcp); the hosted server's working home is
.vscode/mcp.json for VS Code Copilot. URL pattern, auth requirements, and the
config block: references/remote-mcp.md.
Reference
- Microsoft Learn: Eventhouse overview
- Full MS Learn link bundle (KQL management commands / ingestion / mappings / materialized views / update policies / monitoring / REST query API): references/REFERENCE.md
See also
- fabric-auth skill —
kusto.kusto.windows.net/.defaultaudience details - fabric-cli skill —
fabCLI for Eventhouse item creation, principals, exports - fabric-rest-api skill —
kqlDatabaseslisting endpoint and pagination - fabric-graph skill — the other graph workload: the standalone Fabric GraphModel item (GQL / ISO-39075 over OneLake). Different engine; the KQL graph operators above do NOT apply there.