Alternate keys in Dataverse (incl. composite keys for data imports)
Praxisis D365 Wiki · Public reference · Client-specific details redacted
Source: Claude.ai chat (original: enabling-alternate-key-fields-on-tables__257e414f.md)
Added: 2026-10-02
Summary
- Alternate keys are defined at the table level, not on columns — a key record references one or more columns. Max 5 keys per table, up to 16 columns per key, total index size < 900 bytes.
- Key creation is async — a system job builds the backing SQL unique index. If duplicates exist in the data, the key lands in Failed state and must be cleaned + recreated.
- Supported column types: Single Line of Text, Whole Number, Decimal, Date/Time, Choice, Lookup (non-polymorphic), Unique Identifier. Unsupported: Multiline Text, Multi-select Choice, Currency, Floating Point, Customer/polymorphic Lookups, File/Image, and any Calculated / Rollup / Formula column.
Key takeaways
- Composite keys: for "match on email AND org code" style imports, create one key record containing both columns — not two separate keys. Column order doesn't affect matching, only the index.
- Natural-key URL syntax:
GET /api/data/v9.2/contacts(emailaddress1='x@y.z',customer_legacyorgcode='CODE-042'). Same syntax works forPATCH→ upsert-by-natural-key without a pre-lookup. - KingswaySoft integration: on Dataverse Destination, set
Action = Upsertand choose the alternate key as matching criteria — eliminates the legacy-ID → GUID lookup component + staging round-trip. The component reads keys from metadata; refresh metadata cache if a new key doesn't appear. - Nulls break upsert idempotency (critical gotcha):
NULL = NULLis UNKNOWN in SQL three-valued logic. A row with null in a key column is physically indexed but unreachable by key lookup → every rerun creates a new duplicate because the "not found" branch of Upsert fires. The load reports success and only duplicates are the symptom. Fix: route null-bearing rows down a second pipeline that keys on a different column (e.g. a legacy ID), or filter them out entirely. Avoid "synthetic placeholder" values that pollute data and risk real transmission. - Empty strings → null: Dataverse normalises
''on text columns to null on write in most paths; same unreachable case. KingswaySoft has a setting for "blanks as null vs omitted" — the two Upsert behaviours differ. - Case-insensitive matching:
ABCandabccollide. Trailing whitespace is significant in Web API URLs but often not in SQL —TRIM()in staging. - Keys can't be edited after creation — delete and recreate.
Learnings / how to apply
- "Alternate key design + null handling" is a mandatory section in any Praxisis data-migration playbook — this exact trap costs weeks when caught in UAT.
- Pre-flight dedupe query:
GROUP BY <key columns> HAVING COUNT(*) > 1as a mandatory step before enabling a key. - Bulk upserts via alternate key still fire plugins and can mass-trigger live journeys — add CIJ-side guards before any archive run.
- Record "KingswaySoft config: match-by-alternate-key" as the preferred D365 import pattern in the Sustain Operating Model (removes an entire staging layer).
Bonus (related in same chat): Campaign + Marketing List in Customer Insights
- Neither is part of the real-time journey engine. Both are legacy CE tables, kept because Sales still uses them. Real-time journeys live on
msdynmkt_journeyand have no relationship to the campaign table. - Campaign = planning + cost-tracking container; its only execution is Quick Campaign (bulk-generates activity records; a Sales workflow, not a marketing send).
- Marketing List = static or dynamic membership of accounts / contacts / leads (one type per list, never mixed). Still earns a place as a manual curation surface for non-technical staff (board members, specific cohorts) that would be awkward to express as a real-time segment.
- CASL/consent trap: Quick Campaign checks the classic
donotbulkemail/donotsendmmflags; real-time journeys check contact point consent (msdynmkt_contactpointconsent*). A staff member firing a quick campaign off a marketing list transacts on a consent model that the real-time implementation doesn't govern — plausible route to a non-compliant send with no audit trail. Mitigation: restrictprvCreateonlistto a small group OR remove Marketing Lists + Quick Campaign from the CIJ app sitemap entirely. - Outbound marketing retired mid-2025 → the surviving route for lists in journeys is
listmembertraversal in a real-time segment.
Need this applied to your own tenant?
Praxisis Sustain Self-Serve builds a private AI Wiki trained on your documents and tickets, with free Tier-2 support when the AI cannot answer. Book a 30-min overview.