fazalehaq.comSaved
A1
INSIGHTS
ANO.BPOSTCTOPICDDATEEREAD
← INSIGHTS

INSIGHTS!A03

FP&A · SQL · WORKING CAPITAL · DATA QUALITY · POWER BI · CONTROLS · 2026-08-29 · 10 MIN

Duplicate Customer IDs Are Quietly Inflating DSO

A duplicate customer ID on the one side of a SQL join can quietly overstate days sales outstanding. This article walks through the failure pattern, a worked example, and the pre-refresh control that catches the inflation before the finance pack goes out.

Start With the Key, Not the KPI

A working capital dashboard rarely fails because the days sales outstanding formula is wrong. It fails because the tables feeding the formula break a basic grain rule: each customer ID must appear once before the join. In an order-to-cash environment, customer master data often arrives from billing, collections, ERP, and CRM extracts. If one of those extracts contains duplicate customer IDs, every downstream SQL join multiplies rows quietly. By the time the dashboard refreshes, the damage is already baked into the numerator.

Days sales outstanding measures the average number of days a company takes to collect an account receivable after a sale. The standard calculation divides open accounts receivable by credit sales and multiplies by the number of days. In a working capital dashboard, open AR is not a static cell; it is the sum of rows returned by a query. A duplicate key changes the row count, which changes the sum, which changes DSO—without any change in actual collections.

Join Fan-Out Turns One Invoice Into Many

Duplicate customer IDs create many-to-many cardinality where a one-to-many relationship should exist. Dimension tables should carry a unique ID column on the one side of a relationship. When duplicate values sit in the customer lookup, a relationship becomes many-to-many, and the query engine can multiply output rows depending on how the join is written. In SQL, the same issue appears as fan-out: one open AR invoice joins to three customer rows and becomes three rows in the result set.

When a lookup table holds two rows for the same key, the duplication comes from the join, not from any later aggregation step. The standard control is to deduplicate the lookup table to the correct grain or rank rows per customer after the join using a window function. Both controls return the data to a one-to-one resolution before the dashboard calculation consumes it.

Because the DSO numerator is the open AR balance, this is not an abstract database problem. The joined AR sum is exactly what the working capital dashboard divides by credit sales. If the join adds rows, the dashboard reports an AR balance that exists only in the join output. The financial statement pack then inherits that error.

Concrete Example: One Invoice, Three Customer Rows

Consider a duplicate customer ID scenario in an order-to-cash dataset. The AR open items table contains one $45,000 invoice for customer ID 10442. The customer master has three rows for 10442 because of inconsistent address lines and site codes. A report joins the two tables on customer_id. The result shows three $45,000 rows for the same invoice, so the AR subtotal for that customer becomes $135,000 instead of $45,000.

Scaling that error across a mid-size AR dataset changes the DSO story. Suppose true open AR is $6.0 million and annual credit sales are $48 million. The correct DSO is about 45.6 days. If duplicate fan-out adds $240,000 to the AR numerator, DSO becomes about 47.4 days. That shift looks like a collections problem on the dashboard when the real cause sits in the query.

The analyst finds the cause by reversing the flow: isolate open AR items, group by customer ID, count rows before the join, and compare each row count to one. Any count above one explains the gap. The fix is not to adjust the AR balance manually; it is to resolve the key.

Where Duplicate Customer Records Come From

Duplicate customer records are a recognized master data problem. They emerge when separate systems store the same customer with different naming, address, or site codes. Without a unique identifier, analytics cannot reliably match an open AR invoice to one customer row.

Deduplication rules are designed to remove duplicate records so each customer is represented by a single row per table. The rule runs against every row and selects a winner row based on merge preferences. That step should happen before tables are unified or joined to DSO calculations.

A Validation Order That Stops the Inflation

The control layer follows a fixed order: extract the open AR ledger, verify that customer ID is unique in every lookup table, resolve duplicates, join, and then reconcile the joined AR total to the extracted open AR total. This order matters because a post-join reconciliation can detect the fan-out, but it cannot repair the underlying grain issue.

In Power BI, the same principle applies through relationships. The customer dimension should sit on the one side of a one-to-many relationship with a unique ID column. In SQL, the workflow means deduplicating the customer lookup before the join or applying a window function that returns one row per customer. In Excel, a COUNTIF-style check on customer ID in the lookup table can catch repeated values before they feed the DSO model.

Data quality rules provide a durable control. Unique-value rules confirm that a column has no repeated values. Duplicate-row rules check whether the same combination of values appears more than once. These checks are not just data governance; they are part of the financial control environment when they protect the DSO calculation.

Limitation: Not Every Duplicate Is an Error

A realistic limitation is that deduplication can over-correct. Some duplicate-looking customer rows represent separate business roles: a sold-to entity, a bill-to entity, or a legal subsidiary under the same customer ID. If the analyst collapses those rows without understanding the credit structure, the DSO calculation may lose legitimate AR or misclassify a collection risk.

The test is whether the duplicate rows carry different credit exposure or different payment terms. If they do, the right fix is not to delete rows but to create a distinct key that preserves the business dimension while keeping the relationship one-to-many. Human judgment is still required when the data dictionary is unclear.

The safe workflow is to deduplicate before the DSO join only after confirming the duplicate rows do not represent separate order-to-cash roles. Skipping that review can produce a clean-looking metric that is also incorrect.

Monday Morning Takeaway

The durable rule is simple: the key must be clean before the metric is trusted. Run a row count per customer ID in the AR lookup before every DSO refresh. If any customer key counts above one, stop the join and resolve the grain issue. That single check prevents the dashboard from reporting a DSO that exists only in the join output.

When the next working capital pack goes out, the analyst can answer the question before it is asked: the formula is correct, the join is clean, and the AR total ties to the source ledger.

Sources

  1. Credit statistics FAQ - Finance | Dynamics 365
  2. row duplication after 10 min exploded fact table after join - Microsoft Q&A
  3. Many-to-many relationship guidance - Power BI
  4. Microsoft Purview and Profisee Master Data Management
  5. Data unification - duplicates (Dynamics 365 Customer Insights)
  6. Create Data Quality Rules in Unified Catalog
  7. FINANCIAL RATIO LIST - CFA Institute
  8. Risk and Cash Flow Management – Export Development Canada

Practical checklist

  • Run a pre-join row count per customer ID in the customer lookup table and flag any count above one.
  • Resolve duplicate customer rows to a single master before joining to open AR.
  • Keep the customer ID column on the one side of any Power BI relationship.
  • Compare joined open AR total to the extracted AR ledger before calculating DSO.
  • Apply unique-value and duplicate-row data quality rules to customer ID fields.
  • Document any duplicate rows that represent sold-to, bill-to, or subsidiary roles before deduplicating.