Data Modeling
A functional dependency violation is a practical clue that an attribute may be stored at the wrong grain. If customer_id is supposed to determine customer_tier, but the same customer_id appears with multiple tiers in one analytical table, either the rule is false, the data is dirty, or customer_tier is not really a customer-grain attribute in that table.
What functional dependencies reveal in analytical tables
A functional dependency says that one set of columns determines another column. In plain English: if two rows have the same value for the determinant, they should also have the same value for the dependent attribute.
For example, if product_id determines product_name, then every row with product_id P7 should show the same product_name. If P7 appears as both Cloud Mug and Travel Mug, the table contains a counterexample to that dependency.
In operational database design, functional dependencies are often discussed in the context of normalization. In analytical modeling, they are just as useful as a diagnostic tool. They help answer a concrete question: does this attribute belong at the grain where it is currently stored?
The important point is that a dependency is not discovered by hope or naming convention. It is a business rule that can be challenged by data. A violation does not automatically prove the model is wrong, but it gives you a small, specific place to investigate.
Start with the table grain before checking dependencies
A dependency check only makes sense after you know the grain of the table. Grain means what one row represents. A row might be one order line, one order, one customer per day, one product per warehouse, or one subscription per billing period.
Analytical tables become confusing when attributes from several grains are flattened into one wide table without preserving their meaning. A line-level sales table may include order attributes, customer attributes, product attributes, shipment attributes, campaign attributes, and prices. Some of those can be repeated safely. Others are time-varying, many-to-many, or event-specific.
Before checking dependencies, write down the expected row meaning in one sentence. For example: one row per order line at the time the order was placed. That sentence controls which dependencies are reasonable.
- If the table is one row per order line, then order_line_id may determine quantity.
- If every order line belongs to one order, then order_id may determine order_date.
- If product names are stable in your business process, then product_id may determine product_name.
- If customer tiers change over time, customer_id may not determine customer_tier unless the table is explicitly storing the current tier only.
Do not ask whether a column is duplicated first. Ask what one row represents, then ask whether the column is stable at that row grain.
A hypothetical order-line table with counterexamples
Consider this hypothetical analytical table. Its intended grain is one row per order line. It has been made wide so analysts can build revenue, customer, and product reports from one place.
The table is convenient, but convenience hides grain problems. Several attributes are copied onto each line: customer_email, customer_tier, product_name, product_category, unit_price_at_order, order_date, and shipping_region.
Some repeated attributes are harmless. Others may be wrong for the claimed determinant. The goal is not to normalize everything immediately. The goal is to find the rows that disprove the dependency you thought was true.
| order_line_id | order_id | customer_id | customer_email | customer_tier | product_id | product_name | product_category | unit_price_at_order | quantity | order_date | shipping_region |
|---|---|---|---|---|---|---|---|---|---|---|---|
| OL1 | O1001 | C10 | [email protected] | Gold | P7 | Cloud Mug | Accessories | 14.00 | 2 | 2026-01-05 | West |
| OL2 | O1001 | C10 | [email protected] | Gold | P8 | Desk Notebook | Stationery | 9.00 | 1 | 2026-01-05 | West |
| OL3 | O1002 | C10 | [email protected] | Platinum | P7 | Cloud Mug | Accessories | 14.00 | 1 | 2026-02-10 | West |
| OL4 | O1003 | C11 | [email protected] | Silver | P7 | Cloud Mug | Drinkware | 13.00 | 3 | 2026-02-12 | East |
| OL5 | O1004 | C12 | [email protected] | Bronze | P9 | Travel Backpack | Bags | 60.00 | 1 | 2026-02-12 | East |
Read dependency violations as counterexamples, not as abstract errors
A dependency violation is easiest to understand as a counterexample. If you believe customer_id determines customer_tier, then a single customer_id with two different tiers is a counterexample. It proves the dependency does not hold in this table as currently populated.
That counterexample can have several explanations. The customer tier may be mutable. The table may be mixing current customer attributes with historical order facts. A source system may have inconsistent customer records. Or the business rule may have been stated too broadly.
This is why dependency checks are useful for locating wrong-grain attributes. They narrow the investigation from an entire table to a determinant, a dependent attribute, and the exact values that conflict.
A dependency violation is not automatically a bug. It is evidence that the assumed rule does not hold in the table without more context.
| Proposed dependency | Counterexample in the sample | What it may mean |
|---|---|---|
| customer_id determines customer_email | C10 appears with [email protected] and [email protected] | Email may be mutable, duplicated, or captured as an order-time snapshot rather than a stable customer attribute. |
| customer_id determines customer_tier | C10 appears as Gold and Platinum | Tier is likely time-varying, so customer_id alone is not enough unless the table stores only current tier. |
| product_id determines product_category | P7 appears as Accessories and Drinkware | Category may be time-varying, many-to-many, inconsistently mapped, or governed in multiple places. |
| product_id determines unit_price_at_order | P7 appears with 14.00 and 13.00 | This may not be a violation. Transaction price often belongs at order-line grain, not product grain. |
| order_id determines shipping_region | O1001 appears only with West | No violation is visible in this sample. If an order can split across regions, the determinant would need to change. |
Dependency checks to run on a wide analytical table
The basic check is simple: group rows by the determinant and count how many distinct dependent values appear. Any determinant value with more than one dependent value is a violation of the proposed dependency.
For a proposed rule such as customer_id determines customer_tier, group by customer_id and count distinct customer_tier. For product_id determines product_category, group by product_id and count distinct product_category. For order_id determines shipping_region, group by order_id and count distinct shipping_region.
Be deliberate about nulls. Sometimes null means unknown, not a real value. Sometimes null means the attribute does not apply. Decide whether null should count as a distinct dependent value for the rule you are testing.
Also avoid testing only single-column determinants. Many analytical facts are identified or described by combinations of columns. For this article, the key point is not how to choose every possible identifier. The point is to test whether an attribute is stable at the grain where you are storing it.
How violations locate attributes stored at the wrong grain
Once you find violations, classify the likely grain mismatch. This is where the check becomes modeling work rather than just data profiling.
- Entity attribute stored on an event table: A stable product name copied onto every order line may be acceptable for convenience, but the product table remains the natural home for the attribute.
- Mutable attribute treated as stable: Customer tier, lifecycle stage, account owner, and product category often change over time. The determinant may need an effective date, a snapshot date, or a history table.
- One-to-many relationship squeezed into one column: If a product can belong to multiple categories, product_id will not determine one product_category. The model may need a bridge table or a defined primary category rule.
- Event-specific attribute mistaken for entity attribute: Unit price at order time may vary by promotion, contract, region, or date. It should not be treated as if product_id alone determines it.
- Lower-grain process hidden in a higher-grain table: If one order can ship to multiple regions, shipping_region is not order-grain. It may be shipment-grain or order-line-shipment-grain.
The repair depends on the explanation. Do not move an attribute just because it repeats. Move or remodel it when the dependency violation shows that the current determinant is not sufficient to define it.
| Violation pattern | Likely grain issue | Typical modeling response |
|---|---|---|
| Same customer_id, multiple current-looking attributes | Mutable customer state mixed into event rows | Use a current customer table for current state, or a customer history table for time-aware analysis. |
| Same product_id, multiple categories | Category is changing, many-to-many, or inconsistently governed | Add product history, a category bridge, or a defined primary category rule. |
| Same order_id, multiple shipment values | Shipment is lower grain than order | Model shipments separately or use an order-shipment relationship. |
| Same entity id, multiple prices | Price is event-specific or effective-dated | Store transaction price on the event, or model price lists with effective periods. |
| Same determinant, values differ only by spelling or casing | Source quality or standardization issue | Clean and standardize values before treating it as a structural modeling problem. |
Separate data quality issues from modeling issues
A dependency violation does not automatically mean the model is wrong. It means the data contradicts a rule you expected to hold. The next step is to decide whether the rule, the data, or the model should change.
Ask four questions before remodeling:
- Is the dependency a real business rule? If the business allows a customer to have different tiers in different programs, then customer_id does not determine customer_tier.
- Is the attribute time-varying? If customer_tier changes, then customer_id plus an effective period may determine the tier, while customer_id alone does not.
- Is the attribute a snapshot? If the table stores the tier at order time, then the repeated value is a historical fact about the order, not the current customer profile.
- Is there source-system duplication? If two customer records were merged poorly, the right fix may be identity resolution or source cleanup, not a dimensional redesign.
Good modeling treats these explanations differently. A bad source value needs correction. A time-varying attribute needs history. A many-to-many relationship needs a relationship model. A snapshot fact needs a clear name and documentation.
A practical workflow for using dependency violations
Use this workflow when reviewing a wide table, a reporting mart, or a dashboard source that produces inconsistent numbers.
- Declare the table grain. Write one sentence describing what a row represents.
- List proposed dependencies. Use business language: product_id should determine product_name, order_id should determine order_date, customer_id should determine current_customer_segment.
- Run distinct-value checks. For each determinant and attribute, find determinant values with more than one dependent value.
- Inspect concrete counterexamples. Pull a few rows for each violation. Do not rely only on counts.
- Classify the cause. Decide whether the issue is source quality, time variation, many-to-many structure, event-specific meaning, or an incorrect rule.
- Change the model or the rule. Move stable entity attributes to entity tables, add history where time matters, create bridge tables for many-to-many relationships, or rename snapshot attributes so their meaning is explicit.
- Add a test for the dependency you expect to hold. If a dependency is part of the model contract, monitor it so the table does not silently drift.
If a dependency matters to reporting trust, turn it into a documented model expectation and a recurring data test.
Common failure modes in analytical tables
Dependency violations tend to show up in the same kinds of analytical modeling mistakes.
- Current attributes joined onto historical facts: Last year’s orders suddenly appear under today’s customer segment, making historical reporting shift over time.
- Dimension attributes copied without ownership: Product category appears in several marts, and each mart has a slightly different value for the same product.
- Implicit many-to-many relationships: A product, customer, account, or campaign can have multiple classifications, but the table allows only one.
- Status fields stored at the wrong process step: Order status, shipment status, payment status, and refund status are related but not interchangeable.
- Names used as identifiers: A name column appears stable until spelling, localization, rebranding, or source cleanup creates multiple values for the same entity.
These problems are not fixed by adding more dashboard filters. They are fixed by making the row grain, attribute grain, and dependency rules explicit.
What good looks like after the diagnosis
A cleaner analytical model does not require every table to be fully normalized. Analytics often benefits from denormalized tables that are easy to query. The difference is that the denormalization should be intentional.
After using dependency checks, a good model usually has clearer contracts:
- Stable product attributes have a single governed source.
- Historical facts keep the values that were true at event time when that is the analytical requirement.
- Current attributes are labeled as current, not confused with historical snapshots.
- Time-varying attributes have effective dates or snapshot dates.
- Many-to-many relationships are modeled as relationships, not hidden in a single overwritten column.
- Dashboard tables document which repeated attributes are safe conveniences and which are derived snapshots.
The outcome is not theoretical purity. The outcome is fewer metric disputes caused by attributes living at the wrong level of detail.
Key takeaways
- Functional dependencies are useful in analytics because they test whether an attribute is stable at the grain where it is stored.
- A violation is a counterexample: the same determinant has multiple dependent values.
- The same violation can indicate bad source data, a false business rule, a mutable attribute, a many-to-many relationship, or a wrong-grain model.
- Start with the table grain, then test the dependencies that should hold at that grain.
- Do not normalize blindly. Use dependency violations to decide which attributes need history, relationship tables, clearer naming, or data quality fixes.
Next step
Pick one wide reporting table and write its row grain in one sentence. Then choose five proposed dependencies, run distinct-value checks for each, and inspect the first few violations before changing the model.
- Read Composite Keys in Business Event Data: A practical guide to deciding when several columns together identify one business event.
- Read Candidate Keys and Alternate Keys: A practical guide to separating possible row identifiers from the one you choose as the primary key.
- Read Data Modeling Before Dashboards: Build Metrics People Can Trust: Use business definitions, entities, events, and trusted marts before investing in dashboard polish.