From qlik-toolkit
Star schema design, key resolution (natural/composite/hash/AutoNumber), synthetic key prevention, circular reference resolution, QVD layer architecture, multi-app patterns (single app, generator/consumer, extract-transform-model-UI, binary load), source architecture consumption (dimensional warehouse, OLTP, Data Vault 2.0, pre-joined views, flat files), grain alignment across multiple facts, bridge tables, link tables, ApplyMap vs dimension, HidePrefix/HideSuffix. Load when designing or reviewing a Qlik Sense data model, deciding between single-app and multi-app, choosing a key strategy, or diagnosing synthetic keys and circular references.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qlik-toolkit:qlik-data-modelingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Qlik Sense's associative engine links tables through identically-named fields — there is no explicit JOIN syntax at the model level. This makes **field naming a structural decision**: two tables sharing a field are automatically associated, and two tables sharing more than one field produce a synthetic key. Every model choice in this skill flows from that fact.
Qlik Sense's associative engine links tables through identically-named fields — there is no explicit JOIN syntax at the model level. This makes field naming a structural decision: two tables sharing a field are automatically associated, and two tables sharing more than one field produce a synthetic key. Every model choice in this skill flows from that fact.
This skill covers data-model structure only. For script mechanics (LOAD syntax, QVD optimization rules, incremental patterns, SET/LET) see qlik-load-script. For naming conventions see qlik-naming-conventions. For expressions see qlik-expressions.
Every pair of associated tables should share exactly one field name.
$Syn table, solid connector lines).Non-key fields must be made unique across the model, typically via entity-prefixed dot notation: [Product.Status], [Order.Status], [Customer.Region]. Key fields must match exactly across tables that should associate. See qlik-naming-conventions for the full convention.
These are often conflated. They are not the same thing.
| Synthetic Key | Circular Reference | |
|---|---|---|
| Cause | Two tables share >1 field name | Closed loop of single-field associations A↔B↔C↔A |
| Viewer signature | $Syn table with solid connector lines | Red dotted connector line on a loosely coupled table (help.qlik.com — Circular references) |
| Failure mode | Silent incorrect filtering; extra associations | Loosely coupled table does not propagate selections |
| Fix | Entity-prefix non-key fields, drop redundant shared fields, or use ApplyMap for lookups | Consolidate redundant key paths into one dimension, or introduce a link table; do not leave Qlik to pick a loose-coupling victim |
Common triggers for synthetic keys: unprefixed Status/Code/Type/Name; technical fields like load_datetime/source_system in multiple raw tables; wildcard LOADs from shared subroutines. Fix by entity-prefixing non-key fields or dropping technical fields.
QUALIFY is one tool for preventing synthetic keys on raw loads, but do not combine QUALIFY with a hand-maintained prefix convention — you will end up with Table.Entity.Field double-prefixed names and silent expression failures. Pick one discipline.
See references/anti-patterns.md (#1, #3, #4) for synthetic-key triggers, prevention mechanisms, the circular-reference comparison, and the QUALIFY failure modes.
A fact table surrounded by dimension tables, each linked by a single key.
[Order.Amount], [Sales.Quantity]), every row at the same grain.For one-to-many dimension attributes (product → multiple categories), use a bridge table rather than flattening into a delimited string. Use the aliased EXISTS pattern to add "No Entry" rows for parent entities that have no bridge entry, so they remain visible when the bridge is filtered.
Flattening via LEFT JOIN prefix is only appropriate for genuinely 1:1 relationships with a well-defined dedup rule. Default to bridge tables for many-to-many.
See references/star-schema-patterns.md for bridge-table construction, link tables, and ApplyMap patterns.
| Situation | Key type | Why |
|---|---|---|
| Single source, reliable unique ID | Natural key | Readable, debuggable, matches source |
| Multi-source, need composite uniqueness | Hash composite — `Hash128(a, ' | ', b)` |
| Storing surrogate in a QVD for incremental matching | Hash128 / Hash256 | Deterministic; AutoNumber is not |
| Final in-memory model load, very large string keys | AutoNumber | Memory savings; never in a stored QVD |
AutoNumber assigns integers in the order values are first encountered during this reload. Source-row reordering, new rows arriving between existing rows, or a different upstream table being loaded first will all produce different integer assignments on the next reload (help.qlik.com — AutoNumber). Never use it in the QVD layer or anywhere its output is compared across reloads — it will silently break incremental-load matching and cause duplicates to accumulate. Reserve it for the final model load that is not stored.
QVDs are Qlik's compressed column-store files. Layering them decouples extraction from consumption and enables incremental loads.
Three layers (large/complex projects):
| Layer | Purpose | Refresh |
|---|---|---|
| Raw | Extract source as-is; Raw_*.qvd | Incremental per source |
| Transform | Clean, prefix, apply business rules; Transform_*.qvd | Depends on Raw |
| Model | Star schema assembly, composite keys; Model_*.qvd | Depends on Transform |
Two layers (simpler projects): collapse Raw + Transform into a single Extract_*.qvd step, then Model.
An "optimized" QVD read is substantially faster than a standard (unpacked) read and an order of magnitude faster than re-querying the source. Preserve it with:
LOAD *, field subsetting, field aliasing (source AS [New.Name])LOAD DISTINCT, CONCATENATE loadEXISTS(field) / NOT EXISTS(field) — the standard incremental patternForced to standard read by: any function/expression on a field (Upper(name)), derived fields (a & '-' & b), WHERE clauses other than single-parameter EXISTS, two-parameter EXISTS(field, expr).
Full rules are in qlik-load-script → qvd-operations.md.
Single-app vs multi-app is a deployment decision. This section covers the structural mechanics of each pattern — how layers separate, what each app can customize, and what binary load does and does not copy. The WHEN-to-split signals (volume, refresh SLA, consumer count, team boundaries) live canonically in qlik-performance § 1 "Architecture-Level Decisions"; for the GB-anchored heuristics behind those signals see references/multi-app-architecture.md.
| Structural mechanic | Single App | Generator / Consumer | Extract → Transform → Model → UI | Binary Load |
|---|---|---|---|---|
| Independent layer refresh | no | yes | yes | no |
| Per-consumer model customization | n/a | yes | yes | no |
| Incremental load support | yes | yes | yes | no (full reload only) |
Binary Load copies the entire data model (and section access) from another app. Must be the first statement in the script. Syntax depends on platform:
binary [app_id]; (the most common cloud-native form) — or a file path to a .qvf/.qvw via a data connection to a file share — binary [lib://DataConnection/path/Generator.qvf];.binary [lib://Apps/Generator.qvf]; — .qvf via folder data connection.Binary load does not cascade reloads automatically, does not copy variables, sheets, visualizations, or master items. Consumer reloads must still be coordinated (event-based triggers / Qlik Automate in cloud; QMC task chains for client-managed).
See references/multi-app-architecture.md for full decision framework, reload coordination patterns, and common mistakes.
Different upstream systems require fundamentally different consumption patterns. Treating them identically produces incomplete or wrong models.
| Source type | What to do |
|---|---|
| Dimensional warehouse (Kimball) | Usually load the star schema nearly as-is; validate SCD handling |
| Normalized OLTP (3NF) | Denormalize — either push the join into SQL SELECT at extract, or use LEFT JOIN prefix in Qlik Transform |
| Data Vault 2.0 | Merge hub + current-version satellite into dimensions; for insert-only DV use LoadDate filter (canonical variant) or LoadDate+LoadEndDate (end-dated variant) |
| Pre-joined views | Validate grain before consuming — a "one row per customer" view often isn't; deduplicate if needed |
| Flat files / CSV | Set codepage, quote handling (msq), delimiter, validate headers; watch for file rotation |
| SaaS API exports (Salesforce, HubSpot, etc.) | Incremental on SystemModstamp over LastModifiedDate (indexed; captures system + user changes); handle soft-deletes via the deleted-record endpoint; for Salesforce expect Object History rows and multi-currency conversion fields |
Full patterns and worked examples are in references/source-consumption-patterns.md.
When two fact tables at different grains (daily + monthly, order + invoice) share the same dimensions, connecting them directly to the same dimensions produces:
Pick one fix:
[Fact.Type] discriminator; require expressions to filter on it. Simplest when both facts measure the same thing at different granularities.[Fact.Type]; each fact associates to the link, the link associates to the dims. Use when the two facts measure genuinely different things.references/anti-patterns.md — Canonical home for data-modeling failure modes: synthetic keys (causes, detection, three prevention mechanisms), AutoNumber in QVD layer, circular references vs synthetic keys, QUALIFY discipline (double-prefix, missing UNQUALIFY, persistent state), multiple shared fields, missing bridge tables, wide-format expansion, ignoring source architecture, data islands, over-modeling, missing "No Entry" rows, grain misalignment.references/star-schema-patterns.md — Bridge tables with aliased EXISTS, link table construction, ApplyMap lookups, normalized-over-wide, key-hiding mechanics (HidePrefix/HideSuffix application — naming convention itself in qlik-naming-conventions), SubField expansion, dimension vs fact classification.references/multi-app-architecture.md — Single-app, generator/consumer, four-layer split, binary load (both platforms), reload coordination (Cloud events/Automate, QMC task chaining), common multi-app mistakes.references/source-consumption-patterns.md — Full per-source consumption patterns including Data Vault hub/satellite merge, OLTP denormalization (SQL-side vs Qlik-side), dimensional warehouse ingest, pre-joined view grain validation, flat-file ingestion with codepage and quote handling.$Syn table with solid lines ≠ red dotted loose-coupling line. Synthetic keys and circular references are different problems with different fixes.AutoNumber only in the final in-memory model.EXISTS but broken by any function applied to a field.binary accepts an app ID or lib:// .qvf path on Cloud; client-managed requires the lib:// .qvf path. It does not cascade reloads.npx claudepluginhub pupfish-llc/claude-plugins --plugin qlik-toolkitGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.