Vocabulary¶
Every annotation any active extension permits, generated from the profiles themselves. If an annotation is not listed here, rule V001 rejects it.
Annotations on a table¶
| Annotation | Range | Meaning |
|---|---|---|
varda:fact_type |
FactType |
The temporal shape of a fact table. Facts only. |
varda:grain_statement |
string |
What exactly one row of this table represents, as a sentence. Required on facts, alongside varda:grain. Conventionally phrased "one row per ...". Checked for being a sentence and nothing further: varda:grain carries the half of the claim a validator can test, and this carries the half it cannot. The sentence is not redundant with the columns. It states why those columns, and what a row means to somebody reading the model rather than querying it. A fact table whose grain nobody can state in a sentence is one whose double counting nobody can rule out, and the discipline of writing it is most of the value. |
varda:grain |
string, list |
The columns at which rows of this table are unique — the set that together identifies exactly one row. Required on facts. This is the grain proper: in every formal treatment of dimensional modeling the grain is the column set that identifies a row. Every entry must name a column of this table, and each must be a foreign key or a degenerate dimension: those are the columns that locate a row in the model's dimensional space. A measure cannot be part of a grain, because a grain is what a row is, not what it records. Stating the grain as columns is what makes it checkable — a validator can test the claim, and a generator can emit it as a uniqueness constraint the database enforces. |
varda:hierarchies |
Hierarchy, list |
Named drill paths over this dimension's columns. Dimensions only. Each hierarchy lists its levels from least to most granular — [country, region, city], not the reverse. A column may appear in several hierarchies, which is how a date dimension carries both a calendar path and a week path: weeks do not nest inside months, so they are separate hierarchies rather than one chain. A level names what a reader sees when they drill, so it must be a column somebody can read. A surrogate key or a foreign key is a fine identity and an unreadable label — nobody drills into 4718 — so neither may name a level. Where the coarser levels are their own tables, write foreign_key.column to reach the readable column through the key: country_key.country_name rather than country_key. Both halves are checked — the near column must be a foreign key of this table, and the far one a column of the dimension it points at. A level names a member of the level; whether it also identifies one is a separate question. month_name holds "January", which every year has, and city_name holds "Springfield", which several states have. Both describe the path correctly and neither can be grouped by alone. Prefer a column that identifies where one exists naturally — year_month in a date dimension — and expect anything grouping by a level that does not to carry the coarser levels with it. Reaching through a foreign key avoids the question, because the key has already picked one row out. That each level determines the one above it — that every city is in exactly one region — is asserted here and not checked, because it is a claim about data rather than about the schema. What is checked is that the levels are real, distinct, and the kind of column a level can be. Only fixed-depth paths where each level rolls up into exactly one parent. A hierarchy whose branches differ in depth, whose levels can be skipped, or where a child has several parents is a bridge table. |
varda:physical_name |
string |
The table name to emit, when it differs from the class name. Without it, generators derive a physical name from the class name. |
varda:role |
TableRole required |
What part this table plays. Required on every table. |
varda:scd |
SlowlyChangingType |
How this dimension responds to source changes. Dimensions only. Declared on the table rather than per column, because a mixed dimension — some attributes type 1, others type 2 — is a genuinely harder object that this core deliberately does not model. Split it into two dimensions, or handle it in an extension. |
Annotations on a column¶
| Annotation | Range | Meaning |
|---|---|---|
varda:additivity |
Additivity |
How this measure may be aggregated. Required on every measure. |
varda:max_length |
integer |
The widest value this column holds, in characters. Emitted as the length of the SQL type: VARCHAR(80). String columns only — string, uri, uriorcurie, ncname. A length on a date parameterizes nothing. Optional, and there is no default. A column declaring none emits a bare VARCHAR, which is unbounded in PostgreSQL and DuckDB and means VARCHAR(1) in SQL Server. Varda states the width the model gave it and invents nothing, because a default width is a number nobody chose appearing in a schema everybody trusts. |
varda:physical_name |
string |
The column name to emit, when it differs from the slot name. |
varda:precision |
integer |
How many significant digits this column keeps, counting both sides of the decimal point. Emitted as the first parameter of the SQL type: the 18 in NUMERIC(18, 2). Decimal columns only. A float has no exact precision to declare — that is what makes it a float. Optional. A bare NUMERIC is exact and unconstrained in PostgreSQL and DECIMAL(18, 3) in DuckDB, where a unit price of 0.123456 is stored as 0.123 and no error is raised. V707 asks every decimal measure to say what it keeps. |
varda:references |
string |
The name of the class this foreign key points at. Required on every column whose role is FOREIGN_KEY, and checked: it must name a class that exists and that is a dimension or a bridge. The table holding the key may be any of the three. |
varda:role |
ColumnRole required |
What part this column plays. Required on every column. |
varda:scale |
integer |
How many of this column's digits fall after the decimal point. Emitted as the second parameter of the SQL type: the 2 in NUMERIC(18, 2). Decimal columns only, and needs a varda:precision beside it — there is no NUMERIC(, 2) to emit. May be zero, which is a whole number stored as a decimal and not a mistake. |
varda:semi_additive_over |
string |
The foreign key across which this measure may NOT be summed — almost always the date. Required when additivity is SEMI_ADDITIVE, and checked against the table's actual foreign keys, because naming a dimension the fact does not have is a constraint that silently never applies. |
Structured values¶
The shapes an annotation's value takes when its range is not a scalar.
Hierarchy¶
One named drill path over a dimension's columns. Not an annotation class: it carries no varda:applies_to, because it is the range of varda:hierarchies rather than something a model annotates directly.
| Key | Range | Meaning |
|---|---|---|
name |
string required |
What this path is called, unique within the table. Names it rather than numbering it, because a dimension with a calendar path and a fiscal path needs the reader to know which is which. |
description |
string |
What this path is for, when the name alone does not carry it. A dimension with several paths is where this earns its place: merchandise and finance name two ways of rolling the same products up, and which one a reader wants is not decidable from the names. |
levels |
Level, list required |
The levels, from least to most granular. At least two: one level is not a hierarchy, it is a column. Each is written as a column name — city_name, or country_key.country_name to reach through a foreign key — or as a mapping carrying column and key when what names the level is not what tells its members apart. |
Level¶
One step of a drill path. Written as a bare column name wherever that column identifies the level's members, which is the common case. The mapping form exists for the case it does not. Declared as a class so the shape is documented and machine-readable, not because LinkML enforces it: annotation values are opaque to LinkML, so a level naming a field this class does not have is caught by Varda's own rules rather than by the schema.
| Key | Range | Meaning |
|---|---|---|
column |
string required |
The column that names this level to a reader. Either a column of the table the hierarchy is declared on, or foreign_key.column to reach one through a foreign key. |
key |
string |
The column that tells one member of this level from another under the same parent, when that is not the column naming it. A level showing product_name where two products share a name is identified by sku. Rarely needed. It defaults to the foreign key for a level reached through one and to the naming column otherwise, and a level's full identity is that key preceded by the key of every coarser level — city_name holds "Springfield" for cities in three states, and country, state and city together hold one of them. That path is what the hierarchy already says, so it is never written out. Asserted and not checked. Varda checks the column exists and is the kind that can identify something; whether members are actually distinct is a claim about data. |
Enumerations¶
These are closed. An extension may not add a value to one — see Extending.
TableRole¶
What part a class plays in the dimensional model. Every class carrying Varda annotations must declare exactly one.
| Value | Meaning |
|---|---|
FACT |
A measurement event table. Has a declared grain, foreign keys to dimensions, and zero or more measures. Never the target of another table's foreign key. |
DIMENSION |
Descriptive context. Has one surrogate key, one or more natural key columns, and attributes that are almost always textual or categorical. Referenced by facts. |
BRIDGE |
Resolves a many-to-many between a fact and a dimension, or a multi-valued dimension attribute. Carries an allocation factor when the many-to-many is weighted. |
ColumnRole¶
What part a slot plays inside its table. Every slot of a class carrying Varda annotations must declare exactly one. This is the most valuable annotation in the profile: it is what turns a flat list of columns into a structure that generators and validators can reason about.
| Value | Meaning |
|---|---|
SURROGATE_KEY |
The meaningless integer or hash primary key of a dimension. Exactly one per dimension. |
NATURAL_KEY |
A column carrying the business identity of a dimension row. Together the natural key columns — plus, for a type-2 dimension, the validity window — must be unique. |
FOREIGN_KEY |
A reference to the surrogate key of a dimension or a bridge. Must declare varda:references. Legal on any table. A fact referencing its dimensions is the common case, and a dimension referencing another is a snowflake — which is why a hierarchy level may be written country_key.country_name, to reach a readable column through the key. What a foreign key may not do is point at a fact: that join is fact-to-fact, the grain of the result is neither table's, and no aggregate over it is safe. |
MEASURE |
A numeric quantity that is aggregated. Must declare varda:additivity. Lives on a fact or a bridge, never a dimension. |
ATTRIBUTE |
Descriptive context, grouped and filtered on but not summed. The default role for a dimension column. |
DEGENERATE_DIMENSION |
An identifier that lives on the fact with no dimension table of its own — an order number, a ticket reference. Fact tables only. |
VERSION_START |
The instant from which this version of the row is the one in force. Dimensions with varda:scd: TYPE_2 only. Named for the version rather than for validity on purpose. In practice this column usually records when the warehouse observed the change rather than when the change was true in the business — dbt's snapshots and Data Vault's LOAD_DATE both work this way, whatever they are called. Varda does not distinguish the two, because separating business validity from load time is bitemporality and that is out of scope here. The period is closed at the start and open at the end, following SQL:2011: a row is in force from this instant up to but not including VERSION_END. That convention is what stops consecutive versions overlapping at their boundary. Ontology term: time:hasBeginning |
VERSION_END |
The instant at which this version stopped being the one in force, exclusive. Dimensions with varda:scd: TYPE_2 only. Optional, and genuinely so. A great many type-2 dimensions store only the start and derive the end from the next version's start; Data Vault virtualizes it as a view over an insert-only satellite. Requiring it would reject correct designs, so nothing does. Ontology term: time:hasEnd |
IS_CURRENT |
Flags the version in force now. Dimensions with varda:scd: TYPE_2 only. Derivable from the period, and materialized anyway almost everywhere, because the alternative is a null test or a far-future sentinel in every query that wants today's row. |
VERSION_NUMBER |
A monotonic counter distinguishing versions of one natural key, for dimensions that version without timestamps at all. The current version is the highest. Dimensions with varda:scd: TYPE_2 only. |
Additivity¶
How a measure may be aggregated across dimensions. Required on every measure, because the wrong answer here is the single most expensive class of error a dimensional model produces: silently summing something that must not be summed yields a number that looks fine and is wrong.
| Value | Meaning |
|---|---|
ADDITIVE |
May be summed across every dimension of its fact. Sales amount, quantity, cost. |
SEMI_ADDITIVE |
May be summed across some dimensions but not others — almost always not across time. Account balance, inventory level, headcount. Must declare varda:semi_additive_over. |
NON_ADDITIVE |
May not be summed across any dimension. Ratios, percentages, unit prices, temperatures. Aggregate the components and recompute. |
SlowlyChangingType¶
How a dimension responds when a source attribute changes. Kimball's numbering, restricted to the three types that are common enough to generate for.
| Value | Meaning |
|---|---|
TYPE_0 |
Retain original. The value is written once at first load and never updated, whatever the source says afterwards. |
TYPE_1 |
Overwrite. No history is kept; the row shows the current value. |
TYPE_2 |
Add a row. History is preserved by versioning: a new row per change, bounded by a validity window and marked with a current flag. |
FactType¶
The temporal shape of a fact table, which determines how it is loaded and what its grain sentence must say.
| Value | Meaning |
|---|---|
TRANSACTION |
One row per event, at the moment the event occurs. Insert-only. |
PERIODIC_SNAPSHOT |
One row per entity per regular interval, whether or not anything happened. Insert-only, predictable size. |
ACCUMULATING_SNAPSHOT |
One row per tracked item, updated in place as it passes milestones. The only fact shape that is routinely updated after insert. |
FACTLESS |
One row per event or condition, carrying no measure — coverage, eligibility, attendance. Counted rather than summed. |