Duplicate records don't happen by accident — they happen when your data model doesn't have structural guardrails. Learn how to use Dataverse alternate keys and duplicate detection rules to enforce data quality at the database level, not just the UI layer.

Picture this: your organization has spent six months migrating customer data from a legacy CRM into Dataverse. The migration completes, your users start working, and within three weeks you have 847 duplicate accounts. Some customers appear twice because they were entered manually while the migration was still in flight. Others exist because your integration pulls from a REST API that uses an external ID your Dataverse records don't expose as a key. Your data quality team is now spending every Monday morning running deduplication scripts instead of doing anything useful.
This scenario is not hypothetical. It plays out in almost every mid-scale Dataverse implementation that doesn't treat data quality as a first-class architectural concern. The tools to prevent it exist natively in Dataverse — alternate keys, duplicate detection rules, and the supporting configuration that ties them together — but they require deliberate design. You don't accidentally end up with clean data. You engineer it.
By the end of this lesson, you'll understand how alternate keys work at both the surface and the storage level, how to design duplicate detection rules that catch real-world duplicates without generating false positives, how to surface data quality enforcement inside model-driven apps, and how to handle the edge cases that trip up even experienced Dataverse architects. These aren't features you click through once and forget — they're structural decisions that shape the reliability of everything built on top of your data model.
What you'll learn:
You should be comfortable working directly in the Dataverse maker portal and understand how tables, columns, and relationships are structured. If you need a refresher on how Dataverse organizes data at a fundamental level, the lesson on Dataverse Fundamentals: Tables, Columns, and Rows Explained for Power Apps Makers covers the building blocks you'll need. You should also have a working understanding of relationships and data model design from Designing a Dataverse Data Model: Relationships, Lookups, and Choice Columns, because alternate keys often interact directly with lookup columns and foreign key behavior. Experience with model-driven apps is helpful but not strictly required for the Dataverse-side content.
Every Dataverse record has a system-generated GUID — the primary key — that Dataverse manages internally. You can't choose it, you can't predict it, and in most enterprise scenarios, it means nothing to the systems you're integrating with. Your ERP system knows a customer by their SAP account number. Your e-commerce platform knows an order by its platform order ID. Your finance team's SQL Server knows a product by its internal SKU.
An alternate key solves the translation problem. It declares that one or more columns on a table, when combined, uniquely identify a record — and it enforces that uniqueness at the database level, not just at the application layer. This is not just a Dataverse concept; under the hood, Dataverse runs on Azure SQL Database, and creating an alternate key creates a real SQL unique constraint (and a corresponding unique index) on the underlying table. That means the enforcement is hard — no amount of clever application logic can bypass it.
This distinction matters enormously. A lot of Dataverse makers confuse alternate keys with "unique field validation" implemented via business rules or custom JavaScript. Those approaches check for uniqueness at save time by querying existing records and refusing to save if a duplicate exists. They look the same to a casual user but have critical gaps: they're not atomic, they're bypassable via API, and they fail under concurrent inserts. An alternate key is enforced transactionally at the database level. There is no race condition.
Key insight
Alternate keys enforce uniqueness through a SQL UNIQUE constraint in Azure SQL. This means they're enforced for every write path — Power Apps forms, Power Automate flows, direct API calls, plugin code, and bulk import. There is no back door. If two records would violate the constraint, one of them fails with a DuplicateKeyException, period.
The second major function of alternate keys is enabling Upsert operations. When you send data to Dataverse via the Web API or a connector and you're not sure whether a record already exists, you'd normally have to query first, then decide to create or update. With an alternate key defined, you can send a single Upsert request referencing the alternate key value, and Dataverse will create the record if it doesn't exist or update it if it does — atomically, in a single request. This is the correct pattern for any integration that pushes data from an external system.
Creating an alternate key is straightforward in the maker portal: navigate to your table in make.powerapps.com, open the Keys section, and define a new key by selecting one or more columns. But the design decisions you make before you click that button determine whether your key is actually useful.
Dataverse supports alternate keys on these column types:
Notably absent: multi-line text (memo), calculated columns, rollup columns, currency columns, and file/image columns. If your natural business key lives in a memo field (maybe someone stored it there as free text in a legacy system), you'll need to introduce a dedicated text column to hold a cleaned-up version of that value before you can create an alternate key.
Alternate keys can span multiple columns, creating a composite key. The classic use case is a record that's uniquely identified by the combination of a code and a parent entity. Consider a Product Variant table where no single column uniquely identifies a row — you need both Product (a lookup to a Product table) and Variant Code (a text column). A composite alternate key on those two columns enforces that combination.
The practical limit on composite key columns is five columns, though you'll rarely need more than three. Be careful about including columns that change frequently in your composite key — Dataverse has to maintain the unique index, and if users regularly update key columns, you're generating index maintenance overhead on every write.
Warning
Changing the value of an alternate key column on an existing record is allowed, but it means any external system holding a reference to the old key value can no longer use it to find that record. If you're using alternate keys for integration, treat key column values as immutable after initial creation, or build explicit key-rotation logic into your integration layer.
When you create an alternate key in the maker portal, you give it a display name and Dataverse generates a schema name. That schema name appears in API calls, plugin registrations, and error messages. Keep it human-readable. If your key is the combination of a customer's external account number and their region code, name it something like new_CustomerAccountRegionKey rather than accepting an auto-generated name you'll struggle to recognize later.
When you create an alternate key on a table that already has data, Dataverse needs to build the underlying SQL unique index. For tables with millions of rows, this can take several minutes. The key status will show as "Pending" until the index build completes. During this time, the key is not enforced — records that would violate it can still be written. This is not a bug; it's how SQL index creation works. Once the status transitions to "Active," enforcement kicks in.
The index creation can fail if there are existing violations in the data. Dataverse will report the key status as "Failed" and give you an error. Before creating an alternate key on a live table, run a query to verify uniqueness:
// Using Web API to check for duplicates before creating alternate key
// Replace 'new_externalaccountnumber' with your actual column name
GET [organization URI]/api/data/v9.2/accounts
?$select=name,new_externalaccountnumber
&$filter=new_externalaccountnumber ne null
&$orderby=new_externalaccountnumber asc
A more targeted approach using FetchXML to find violations before key creation:
<fetch>
<entity name="account">
<attribute name="name" />
<attribute name="new_externalaccountnumber" />
<filter>
<condition attribute="new_externalaccountnumber" operator="not-null" />
</filter>
<order attribute="new_externalaccountnumber" ascending="true" />
</entity>
</fetch>
You'd then look for repeated values in the new_externalaccountnumber column in the result set. Before key creation, clean those duplicates — either by removing the duplicate records, by nulling out the key column on the records you want to de-prioritize, or by merging records using Dataverse's built-in merge capability.
Once your alternate key is active, the Upsert pattern becomes available to every integration touching that table. Here's what it looks like against the Dataverse Web API:
PATCH [organization URI]/api/data/v9.2/accounts(new_externalaccountnumber='SAP-10042')
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
Prefer: return=representation
{
"name": "Contoso Manufacturing",
"emailaddress1": "contact@contoso.com",
"telephone1": "+1-555-0100",
"new_externalaccountnumber": "SAP-10042"
}
The key part is the URL format: instead of the record's GUID, you provide the alternate key column name and value in parentheses after the entity set name. Dataverse will look up the record by that key. If it finds one, it issues an UPDATE. If it doesn't, it issues a CREATE. The HTTP method is always PATCH, and the response will be 204 No Content (update) or 201 Created (create) unless you've requested the representation back via the Prefer header.
For composite keys, the URL format expands to include all key columns:
PATCH [organization URI]/api/data/v9.2/new_productvariants(new_productid='{PRODUCT-GUID}',new_variantcode='RED-XL')
Content-Type: application/json
Tip
In Power Automate, you can use the Dataverse connector's "Add a new row" or "Update a row" actions with alternate key lookup. But for true Upsert behavior (create-or-update in a single step), use the HTTP connector with the PATCH pattern above, or wrap the logic in a Dataverse custom API. The standard connector actions don't natively expose Upsert semantics as cleanly as the raw API does.
When a write operation violates a unique constraint, the Dataverse API returns HTTP 412 with an error code of 0x80040237 and a message containing DuplicateKeyException. Your integration needs to handle this explicitly — it's a different exception type than a validation error or a permission error, and it needs different remediation logic (typically: query for the existing record, decide which version wins, and potentially trigger a merge or update instead of a create).
In a Power Automate flow, this means adding a "Configure run after" condition on a branch that catches DuplicateKeyException specifically, then routing to a sub-flow that handles the conflict resolution. Don't just retry on this error — retrying a write that violates a unique constraint will keep failing. The right response is to handle the conflict.
Alternate keys solve the uniqueness enforcement problem for fields where you have a clean, machine-generated identifier. But real-world data quality problems are messier. Your users will enter "Contoso Manufacturing" in one record and "Contoso Manufacturing, Inc." in another. They'll enter the same contact twice with slightly different email addresses. They'll create a new account without realizing the company already exists under a slightly different name.
This is the problem duplicate detection rules are designed for. Rather than hard enforcement, duplicate detection applies fuzzy matching logic and warns users when a record being created or updated closely resembles one that already exists. It's about catching human error that alternate keys can't address because there's no clean, deterministic key to enforce uniqueness against.
Key insight
Alternate keys and duplicate detection solve different problems and should be used together, not instead of each other. Alternate keys handle integration scenarios with clean identifiers. Duplicate detection handles human data entry scenarios where similarity (not exact match) is the meaningful signal.
A duplicate detection rule targets a specific entity and defines one or more matching conditions. Each condition specifies:
The conditions are AND-ed together — a record is flagged as a potential duplicate only if all conditions match. This is how you avoid false positives: a rule that matches only on company name will generate a huge false positive rate for common names. A rule that matches on company name AND phone number AND city is far more precise.
Duplicate detection rules also let you set case sensitivity on text matching (default is case-insensitive) and field-level matching criteria independently. You can require an exact match on email but only require "same first 6 characters" on the postal code.
The hardest part of duplicate detection is not the configuration — it's the rule design. A poorly designed rule either misses real duplicates (too lenient) or generates so many false positives that users start ignoring the warnings and clicking through them (too strict in the wrong way).
Let's design duplicate detection for an Account table in a B2B scenario. Our business logic says a duplicate account is one where:
Here's why this combination works: two legitimately different companies rarely share both a phone number and a similar name. A slightly misspelled version of an existing account that was entered by a different user will still have the same phone number. This catches real duplicates without flagging "Contoso" and "Contoso Logistics" as duplicates (they have different phone numbers).
To create this rule: in the classic interface, navigate to Settings > Data Management > Duplicate Detection Rules, then select New. Set the Base Record Type to Account, set Status to Published after you've configured the conditions. Add your first condition: Base Record Field = Account Name, Criteria = Same First Characters (N) = 10, Matched Record Field = Account Name. Add second condition: Base Record Field = Main Phone, Criteria = Exact Match, Matched Record Field = Main Phone.
Warning
The "Sounds Like" operator uses Soundex encoding, which was designed for English. It will produce poor results for names in other languages, and it can create unexpected false positives for common English word patterns (e.g., "Smith" and "Smithe" sound alike but may be different companies). Use "Sounds Like" cautiously, and only after testing against a representative sample of your actual data.
For contacts, the best approach is usually matching on email address (exact match) as the primary condition. Email addresses are nearly unique in practice, and an exact match on email is a very strong signal that two records represent the same person. A good secondary rule adds a condition on last name (same first characters) combined with company (exact match lookup value).
Avoid building a single rule that tries to catch all duplicate types. Instead, create multiple rules that each target a specific duplicate pattern. Dataverse evaluates all published rules against the entity type and flags a record if any rule matches — you don't have to pick one.
By default, a duplicate detection rule checks the record being saved against all existing records of the same entity type. This is the right behavior, but it can create issues for very large tables. If your Contact table has 5 million rows and you're running real-time detection on every save, you're executing a query against 5 million rows on every user interaction.
Dataverse optimizes this with indexed lookups when your matching conditions include exact-match criteria on indexed columns (like email address, which is indexed on the standard Contact table). Make sure at least one condition in each rule uses exact matching on an indexed column — this gives Dataverse the ability to use an index seek rather than a full table scan.
Duplicate detection rules are only effective if the system-level duplicate detection setting is enabled. In the classic interface: Settings > Administration > System Settings > Data Management tab, enable "Duplicate Detection" at the system level. This is separate from the individual rules being published — you can have published rules but detection disabled, and nothing happens.
With the system setting enabled and rules published, Dataverse performs real-time duplicate checking when users save records in model-driven apps. When a potential duplicate is detected, the user sees a dialog showing the matching records. They can then:
This last option is powerful and underused. When users can directly navigate to the potential duplicate from the dialog, they can make an informed decision rather than guessing. Make sure your published views on the entity show the columns that help users distinguish similar records — for accounts, that means city, state, phone, and primary contact, not just the account name.
By default, users with Create/Update permissions on a table can override duplicate detection warnings. You can tighten this through security roles: in the security role editor, find the "Ignore Duplicate Warning" privilege under the Core Records section. Remove this privilege from roles where you want detection to be mandatory — those users will be blocked from saving a duplicate, not just warned.
Tip
Don't remove the override privilege organization-wide without a well-defined exception process. Legitimate "near-duplicate" records exist — two subsidiaries of the same parent company will often match on several criteria. Removing all override capability turns what should be a guardrail into a hard wall that generates helpdesk tickets.
This connects directly to how you think about Dataverse security roles — the dvp_DuplicateDetection privilege is a distinct capability that deserves its own role design consideration, separate from general read/write access.
Real-time detection catches new duplicates as they're entered, but it doesn't retroactively clean existing data. For that, you run Duplicate Detection Jobs. From Settings > Data Management > Duplicate Detection Jobs, you can create a job that runs a specific rule against all existing records and generates a Duplicate Detection report.
The job produces a list of potential duplicate pairs. You review the list and can merge, delete, or ignore each pair. The job can be scheduled to run automatically on a recurring basis — weekly is a reasonable cadence for most production systems.
One critical detail about merge: when you merge two Dataverse records, you pick a "master" record to retain and a "child" to merge in. All relationships pointing to the child record are redirected to the master. The child is then deactivated (not deleted — this is important for audit trail reasons). The merge operation is available natively in model-driven apps for certain out-of-the-box tables (Account, Contact, Lead) but requires custom code for custom tables.
The native duplicate detection rules handle a wide range of scenarios, but sometimes your business logic is too specific or too complex for the rule-based system. Common examples:
In these cases, you write a plugin registered on the Create and Update messages, Pre-Operation stage (or Pre-Validation for early exit before any other processing). Inside the plugin, you query for potential duplicates using a FetchXML or QueryExpression, and if you find a match, you throw a InvalidPluginExecutionException with a message that surfaces to the user.
// Plugin: PreCreate on Account to enforce custom duplicate logic
public class AccountDuplicateCheckPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
var serviceFactory = (IOrganizationServiceFactory)
serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = serviceFactory.CreateOrganizationService(context.UserId);
var tracingService = (ITracingService)
serviceProvider.GetService(typeof(ITracingService));
// Only run on Create of Account
if (context.MessageName != "Create" ||
context.PrimaryEntityName != "account") return;
var newAccount = context.InputParameters["Target"] as Entity;
if (newAccount == null) return;
// Get the incoming external account number
var externalNum = newAccount.GetAttributeValue<string>("new_externalaccountnumber");
if (string.IsNullOrEmpty(externalNum)) return;
// Check for existing record with same external number
var query = new QueryExpression("account")
{
ColumnSet = new ColumnSet("name", "new_externalaccountnumber"),
Criteria = new FilterExpression(LogicalOperator.And)
};
query.Criteria.AddCondition(
"new_externalaccountnumber", ConditionOperator.Equal, externalNum);
query.Criteria.AddCondition(
"statecode", ConditionOperator.Equal, 0); // Active records only
var results = service.RetrieveMultiple(query);
if (results.Entities.Count > 0)
{
var existingName = results.Entities[0]
.GetAttributeValue<string>("name");
throw new InvalidPluginExecutionException(
$"A duplicate account exists with external number '{externalNum}': " +
$"'{existingName}'. Please update the existing record instead of " +
$"creating a new one.");
}
}
}
Warning
Plugin-based duplicate checking runs in the same transaction as the save operation, so it adds latency to every Create or Update. Keep your queries as targeted as possible — use the specific columns involved in the check in your ColumnSet, filter on indexed columns, and never do broad wildcard searches inside a synchronous plugin. If your logic is complex, consider moving it to an async plugin and flagging the record for review rather than blocking the save.
Data quality enforcement that's invisible to users generates frustration and workarounds. The best model-driven app implementations surface quality signals proactively, in context, so users understand what's expected and why.
A well-designed view can serve as a data quality dashboard. In your model-driven app views, you can create filtered views that highlight records needing attention — accounts missing a phone number, contacts without an email, leads with duplicate detection flags, and so on. These views are navigable from the site map and give data stewards a working queue rather than a spreadsheet.
For example, a "Accounts Missing Key Fields" view with filters: new_externalaccountnumber = null OR telephone1 = null. Users responsible for data quality can work through this view daily. Pair it with a formula column that shows a data completeness score (a percentage of required fields that are populated), and you have a lightweight data quality scorecard built entirely within Dataverse.
On model-driven forms, you can use business rules to show warnings when a record's data quality is low. A business rule that checks whether key fields are populated and sets the notification text on a field is visible in real-time as the user fills out the form. This is different from the duplicate detection dialog — it's about completeness rather than uniqueness, and it gives feedback before the user even tries to save.
Consider also adding a "Data Quality" section to your forms for tables where quality is critical. This section might include:
This gives your data quality program a home inside the app itself rather than in a separate spreadsheet or report.
When data quality problems are most common during initial record creation, a business process flow can enforce completeness progressively. Rather than exposing all fields at once and hoping users fill them correctly, a BPF walks users through stages — basic info, contact details, classification, verification — with each stage requiring specific fields before advancement. Records that haven't completed the full flow are easy to identify and route to cleanup queues.
Tip
Combine a business process flow with duplicate detection rules so that detection runs at Stage 1 entry (when the name is first entered) rather than only at final save. You can trigger this in a Power Automate flow listening to the "Process Stage Changed" message, or by using the BPF's stage transition events to perform an early check. Catching duplicates at Stage 1 is far less disruptive than catching them after a user has entered detailed contact information.
In event-driven architectures using Azure Service Bus or Event Grid (via Dataverse service endpoints), alternate key values provide stable identifiers that downstream consumers can use without needing to understand Dataverse GUIDs. When you publish a record-changed event, including the alternate key column values in the payload means receiving systems can correlate the event to their own records without a separate GUID mapping table.
This is a particularly important pattern when integrating Dataverse with data warehouses. Your warehouse uses a surrogate key internally, but it also needs the Dataverse GUID and the business alternate key (like the SAP account number) to resolve records. Design your event payload to include all three.
Dataverse Dataflows (the Power Query-based ETL tool in Power Apps) support alternate keys for upsert operations during load. When configuring a dataflow destination targeting a Dataverse table, you can specify an alternate key as the match key for upsert behavior. This means your dataflow can run incrementally — sending all records from the source and letting Dataverse handle the create-vs-update decision — without you needing to implement that logic in Power Query.
This is the right pattern for daily sync scenarios from external databases or file-based sources. The dataflow handles the transformation; the alternate key handles the merge logic.
When moving solutions between environments (development, test, production), records referenced by GUID in configuration data often need manual updating because GUIDs differ between environments. Alternate keys eliminate this problem for records that have a stable business key. If your "Regions" table has an alternate key on the region code, and your configuration data references regions by code rather than GUID, the same configuration works across all environments without modification.
This exercise gives you practice with the full data quality stack: alternate key creation, upsert via the API, and duplicate detection rule design.
Scenario: You're building a Dataverse model for a product catalog. The Product table needs to accept daily updates from a pricing service that identifies products by their SKU. You also need to prevent users from accidentally creating duplicate products with similar names.
Step 1: Prepare the Table
In make.powerapps.com, open your development environment and find or create a custom table called Product (schema name: new_product). Add the following columns:
SKU — Single Line of Text, Max Length 50, Required, schema name: new_skuProduct Name — Single Line of Text, Max Length 200, Required, schema name: new_productnameList Price — Currency, RequiredCategory — Choice column with values: Electronics, Apparel, Home Goods, OtherStep 2: Create the Alternate Key
Navigate to the table's Keys section. Create a new key: Display Name = "SKU Key", select the SKU column. Save. Wait for the key status to show "Active" before proceeding (this is usually fast on an empty table, but check the status — don't assume).
Step 3: Test Upsert Behavior
Open a tool that can make HTTP requests — Postman, Insomnia, or the Power Platform Web API tester. Authenticate using your environment's OAuth token. Send the following PATCH request (replace [org-uri] with your environment URL):
PATCH [org-uri]/api/data/v9.2/new_products(new_sku='ELEC-001')
Content-Type: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
Prefer: return=representation
{
"new_sku": "ELEC-001",
"new_productname": "Wireless Noise-Cancelling Headphones",
"new_listprice": 299.99,
"transactioncurrencyid@odata.bind": "/transactioncurrencies(CURRENCY-GUID)"
}
You'll need a valid currency GUID from your environment — query /api/data/v9.2/transactioncurrencies?$select=isocurrencycode,transactioncurrencyid to find it.
Send the request once — you should get 201 Created. Send it again with a different price — you should get 204 No Content (update). Verify in make.powerapps.com that the product record exists and the price reflects the second value.
Step 4: Attempt a Key Violation
Try creating a second record via the UI or API with the same SKU but a different name. The API should return 412 with a DuplicateKeyException. Note the error code (0x80040237) for your exception handling logic.
Step 5: Create a Duplicate Detection Rule
Navigate to Settings > Data Management > Duplicate Detection Rules (in the classic Settings menu). Create a new rule:
Publish the rule, then enable duplicate detection in System Settings > Data Management if you haven't already.
Step 6: Test Detection
In your model-driven app (or create a quick view from make.powerapps.com), create a Product with Name "Wireless Bluetooth Speaker" and a unique SKU. Then try to create another product with Name "Wireless Bluetooth Earbuds" — it starts with the same 8 characters ("Wireless"). The duplicate detection dialog should fire. Practice using the dialog to navigate to the matching record.
Reflection questions:
This almost always means existing data violates the uniqueness constraint. Query the table for duplicate values in the key column before attempting recreation. Fix the duplicates first — there's no way to force the key to activate over violations.
Check the following in order: (1) Is duplicate detection enabled in System Settings? (2) Is the rule published (not just saved)? (3) Does the rule apply to the table you're testing? (4) Are you in a context where detection runs — detection doesn't run in Quick Create forms by default, only full record forms. (5) Does your security role include the "Duplicate Detection" privilege?
The alternate key column value in the URL must exactly match an existing record's value, including case (the matching is case-insensitive for text columns by default in Dataverse, but leading/trailing whitespace matters). If your source system sends " SAP-10042" with a leading space and Dataverse stores "SAP-10042", you'll get a new record. Normalize your input data before upsert operations.
If your detection job runs but never completes, the matching logic is too broad. Rules with fuzzy text matching (Contains, Sounds Like) across millions of rows don't use indexes efficiently and can time out. Add an exact-match condition on an indexed column (like email or phone) to narrow the search space before the fuzzy match is applied.
This is a rule calibration problem. Examine the rules that triggered the warning — look at which condition caused the match. If it's a condition with very loose matching (Same First 3 Characters on company name, for example), tighten the N value or add a second condition that further differentiates records. Track override frequency in audit logs to identify which rules are generating the most ignored warnings — those are your false positives.
Note
You can query the DuplicateRecord table in Dataverse to see historical duplicate detection results, including which records were flagged and whether the warning was overridden. This data is invaluable for tuning your rules over time. Use FetchXML against duplicaterecord and join to duplicaterule to understand which rules are firing most frequently.
When including a Lookup column in a composite alternate key, you're actually keying on the GUID of the referenced record. If the same logical value (e.g., "same customer") appears under different GUIDs in different environments, your composite key won't behave consistently across environments. This is a known issue with using lookup columns in alternate keys for solutions that must work across multiple environments with different reference data.
Dataverse alternate keys and duplicate detection are the two pillars of proactive data quality in any serious Dataverse implementation. They solve different problems — alternate keys handle the precision case (enforce that a machine-generated ID is unique, enable upsert), while duplicate detection handles the human case (catch similar records created by mistake). Using them together, with thoughtful rule design and appropriate security configuration, gives you a data layer that resists corruption at both the API integration level and the user interface level.
The architectural lesson underneath all of this is that data quality cannot be retrofitted. The cost of cleaning duplicates after they've propagated through related tables, reports, and downstream integrations grows exponentially with time. Every alternate key you define and every duplicate detection rule you publish on day one of a project is worth approximately fifty hours of cleanup work later.
From here, the natural next area to explore is how to layer validation logic on top of the structural quality controls you've built. Business Rules in Dataverse: Validation and Field Logic Without Code covers how to add field-level validation that complements your duplicate rules and alternate key enforcement — together, they create a complete data quality enforcement stack. For implementing calculated completeness scores and aging metrics that feed your data quality views, Formula Columns and Rollup Columns in Dataverse: Calculated Data Without Code will show you how to build those indicators without writing code. And if you're thinking about how to surface quality metrics to users with different access levels, Dataverse Security: Business Units, Security Roles, and Teams will help you ensure that the right people are seeing — and acting on — the right data quality information.