Back to blog
PrimentraPrimentra
·July 28, 2026·11 min read

The supplier master data model: the tables, keys, and relationships most vendor masters get wrong

Home/Blog/The supplier master data model: the tables, keys, and relationships most vendor masters get wrong
VENDOR MASTER · ONE FLAT TABLEACME CorpACME Corp - ROTTERDAMACME Corp - NEW BANK 2024Acme Corporation B.V.four rows. one company.Supplier · 1 rowSITES ×3BANK ×2TAX ×2CONTACTS ×4the duplicates were never a data entry problem

Search your vendor master for ACME and you get four hits. One plain, one with a city appended, one with a year and the words NEW BANK, and one spelled slightly differently by whoever created it in 2019. The usual response is to call this a data quality problem and schedule a deduplication project. That project will fail, because somebody genuinely needs each of those four rows, and the person who merges them will spend the following month explaining to procurement why deliveries are going to the wrong address. The problem sits in the model, not in the typing.

ACME has three delivery addresses, changed banks two years ago, and trades under a second legal name in Belgium. Your vendor master gives it one row. So the people who needed the other facts did what anyone would do: they made more rows and put the difference in the name field. Every duplicate supplier conversation I have been in eventually arrives at the same place, which is that the table was never shaped like the thing it was describing.

What a supplier master data model actually is

A supplier master data model is the set of entities and relationships that describe a vendor: one record for the legal entity, plus separate child records for its sites, bank accounts, tax registrations, contacts, certificates, and category assignments. The vendor master in your ERP is a single table. The model is the shape underneath it.

That distinction sounds academic until you look at what the ERP table was designed for. It was designed to pay someone. It needs an account code, payment terms, a bank account, an address, and a tax number, which is exactly one of each, because an invoice only ever needs one of each. Procurement, compliance, and logistics arrived later with questions the table has no room to answer, and rather than change the table, everyone worked around it. Most of the attention since has gone to the governance side of supplier master data, which is fair enough, since that is where the visible pain is. My argument is that you cannot govern a shape that cannot hold the facts, so the model has to come first even though it is the less interesting half.

The one-table trap

Six things about a supplier are one-to-many: its sites, its bank accounts, its tax registrations, its contacts, its certificates, and its categories. A flat vendor master gives each of them one column. When reality needs two, the user has three options, and all three are bad.

They can overwrite the existing value, which quietly destroys history and is how a bank account change becomes untraceable. They can create a second supplier record and put the difference in the name, which is the four ACME rows. Or they can give up and keep it in a spreadsheet, which is where most lead times and certificate expiry dates actually live. None of those are things a person picks on purpose. In a table with one row per supplier there is no fourth option, so the user takes whichever of the three costs them least effort this afternoon, and none of the three costs them anything at all until much later.

The second option is the expensive one, because it breaks two things at once that nobody connects back to the cause. Duplicate detection stops working, since ACME Corp and ACME Corp - ROTTERDAM are genuinely different strings and a fuzzy match that catches them will also catch two unrelated companies. And spend analysis stops working, because the four rows split one supplier relationship into four purchase histories, so the vendor you spend the most with never appears at the top of the list. Teams then negotiate contracts against a number that is a quarter of the truth.

The core model: seven entities

Seven entities cover almost every supplier requirement I have seen in mid-market implementations. The test for whether something deserves its own table is not how important it is. It is whether a supplier can have two of them.

EntityCardinalityOwned byWhy it cannot live on the parent
Supplier1 per vendorMaster dataThe legal entity. The only table that gets exactly one row, and the anchor every other table points at.
SupplierSite1 to manyProcurementOrder-from, ship-from, and remit-to are three different addresses that happen to belong to one company. Flatten them and you get one vendor record per address.
SupplierBankAccount1 to manyAccounts payableA supplier can hold accounts in several currencies and countries, and old accounts must stay readable after a change. Effective-dated rows, never an overwrite.
SupplierTaxRegistration1 per countryFinanceA company trading in four EU countries has four VAT numbers. One tax field forces someone to pick a favourite and lose the rest.
SupplierContact1 to manyProcurementThe buyer, the AP contact, and the quality manager are three people. Storing one contact means the other two live in Outlook.
SupplierCertificate1 to manyComplianceISO certificates, insurance, and supplier declarations each carry their own expiry date. Expiry is the whole point, and it needs a row to live on.
SupplierCategorymany to manyProcurementSuppliers rarely sit in one commodity category. A single category column is how the biggest spend line ends up labelled Miscellaneous.

Notice the owner column. Four different functions own pieces of one supplier, which is the real reason this data fragments: each of them built the piece they needed in the system they controlled. The model does not fix that on its own, but it gives every function a place to put its fields without inventing another vendor record.

The SQL

The parent table stays small. Anything that can occur twice has already left, so what remains is the identity of the legal entity and its lifecycle. The persisted computed column exists to give the duplicate constraint something stable to bite on.

CREATE TABLE dbo.Supplier (
    SupplierId       INT IDENTITY(1,1) NOT NULL,
    SupplierNumber   VARCHAR(20)   NOT NULL,   -- business key, shown to users
    LegalName        NVARCHAR(200) NOT NULL,
    CountryCode      CHAR(2)       NOT NULL,   -- ISO 3166-1 alpha-2
    LifecycleStatus  VARCHAR(20)   NOT NULL,   -- draft|active|blocked|archived
    ParentSupplierId INT           NULL,       -- legal parent, not spend rollup
    OwnerUserId      INT           NOT NULL,
    CreatedAt        DATETIME2(0)  NOT NULL CONSTRAINT DF_Supplier_CreatedAt DEFAULT SYSUTCDATETIME(),
    NameKey AS LOWER(REPLACE(REPLACE(REPLACE(LegalName, ' ', ''), '.', ''), ',', '')) PERSISTED,
    CONSTRAINT PK_Supplier PRIMARY KEY CLUSTERED (SupplierId),
    CONSTRAINT UQ_Supplier_Number UNIQUE (SupplierNumber),
    CONSTRAINT FK_Supplier_Parent FOREIGN KEY (ParentSupplierId)
        REFERENCES dbo.Supplier (SupplierId)
);

-- Soft duplicate guard: same normalized name in the same country.
-- Scoped to non-archived rows so historic records do not block new ones.
CREATE UNIQUE INDEX UX_Supplier_NameCountry
    ON dbo.Supplier (NameKey, CountryCode)
    WHERE LifecycleStatus <> 'archived';

Sites carry a type, and the type is what makes the four ACME rows unnecessary. One supplier, three sites, each flagged for what it is allowed to do. The filtered unique index enforces exactly one default per type without forbidding the others.

CREATE TABLE dbo.SupplierSite (
    SupplierSiteId INT IDENTITY(1,1) NOT NULL,
    SupplierId     INT           NOT NULL,
    SiteType       VARCHAR(20)   NOT NULL,   -- order|ship_from|remit_to|registered
    SiteCode       VARCHAR(20)   NOT NULL,
    AddressLine1   NVARCHAR(200) NOT NULL,
    PostalCode     NVARCHAR(20)  NULL,
    City           NVARCHAR(100) NULL,
    CountryCode    CHAR(2)       NOT NULL,
    IsDefault      BIT           NOT NULL CONSTRAINT DF_SupplierSite_IsDefault DEFAULT 0,
    IsActive       BIT           NOT NULL CONSTRAINT DF_SupplierSite_IsActive DEFAULT 1,
    CONSTRAINT PK_SupplierSite PRIMARY KEY CLUSTERED (SupplierSiteId),
    CONSTRAINT UQ_SupplierSite_Code UNIQUE (SupplierId, SiteCode),
    CONSTRAINT FK_SupplierSite_Supplier FOREIGN KEY (SupplierId)
        REFERENCES dbo.Supplier (SupplierId)
);

-- Exactly one default site per type, per supplier.
CREATE UNIQUE INDEX UX_SupplierSite_Default
    ON dbo.SupplierSite (SupplierId, SiteType)
    WHERE IsDefault = 1 AND IsActive = 1;

Bank accounts are the one table where I would argue the design is not a preference. Make the rows effective-dated and never update an account number in place. A changed IBAN closes the old row and opens a new one, which means the question of what account we paid on 14 March has an answer, and the question of who changed it has one too.

CREATE TABLE dbo.SupplierBankAccount (
    SupplierBankAccountId INT IDENTITY(1,1) NOT NULL,
    SupplierId    INT          NOT NULL,
    Iban          VARCHAR(34)  NOT NULL,
    Bic           VARCHAR(11)  NULL,
    CurrencyCode  CHAR(3)      NOT NULL,   -- ISO 4217
    ValidFrom     DATE         NOT NULL,
    ValidTo       DATE         NULL,       -- NULL = currently valid
    ApprovedBy    INT          NULL,       -- NULL until second-person approval
    CONSTRAINT PK_SupplierBankAccount PRIMARY KEY CLUSTERED (SupplierBankAccountId),
    CONSTRAINT FK_SupplierBankAccount_Supplier FOREIGN KEY (SupplierId)
        REFERENCES dbo.Supplier (SupplierId),
    CONSTRAINT CK_SupplierBankAccount_Period CHECK (ValidTo IS NULL OR ValidTo > ValidFrom)
);

-- One open account per supplier per currency at any moment.
CREATE UNIQUE INDEX UX_SupplierBankAccount_Open
    ON dbo.SupplierBankAccount (SupplierId, CurrencyCode)
    WHERE ValidTo IS NULL;

Tax registrations follow the same shape and carry the constraint that does the most duplicate prevention of anything in the model. One tax number per country, unique across all suppliers, enforced at insert. A user creating a vendor that already exists finds out immediately, which is the only moment it is cheap to fix.

CREATE TABLE dbo.SupplierTaxRegistration (
    SupplierTaxRegistrationId INT IDENTITY(1,1) NOT NULL,
    SupplierId  INT          NOT NULL,
    CountryCode CHAR(2)      NOT NULL,
    TaxNumber   VARCHAR(30)  NOT NULL,
    IsPrimary   BIT          NOT NULL CONSTRAINT DF_SupplierTaxReg_IsPrimary DEFAULT 0,
    CONSTRAINT PK_SupplierTaxRegistration PRIMARY KEY CLUSTERED (SupplierTaxRegistrationId),
    CONSTRAINT FK_SupplierTaxReg_Supplier FOREIGN KEY (SupplierId)
        REFERENCES dbo.Supplier (SupplierId),
    -- The hard duplicate guard: nobody else may hold this number in this country.
    CONSTRAINT UQ_SupplierTaxReg_Number UNIQUE (CountryCode, TaxNumber)
);

Choosing the supplier identifier

Someone will propose making the VAT number the primary key. It looks clean: globally meaningful, externally verifiable, no meaningless integers. Do not do it. Tax numbers change when a company restructures, they are not unique across countries, some suppliers hold several, and sole traders in several jurisdictions have none at all. A primary key that can change or be missing pushes that problem into every foreign key that references it, and the identifier is the one decision you cannot reverse cheaply.

Surrogate integer as the key, tax number behind a unique constraint. You get the duplicate prevention without the fragility. The ERP vendor numbers, the procurement portal IDs, and whatever the logistics system calls the same company then live in a cross-reference table, one row per system per supplier. That table is not optional decoration. It is the thing that lets you answer whether vendor 4471 in the ERP and supplier ACM-0012 in the portal are the same company, and every integration you build afterwards depends on the answer.

Where item and supplier master data meet

Supplier item master data is the relationship between a vendor and something you buy from them: their part number for it, the lead time, the minimum order quantity, the price breaks, and whether they are the preferred source. It belongs to neither master. One item comes from four suppliers, one supplier delivers hundreds of items, and the facts that matter are true only for the pair.

This is the table most implementations skip, and skipping it has a signature. Lead times end up in a planner's spreadsheet, supplier part numbers get typed into purchase order free-text fields, and the answer to who else can supply this part is somebody's memory. Then the supplier has a fire and the answer matters within a day.

CREATE TABLE dbo.SupplierItem (
    SupplierItemId  INT IDENTITY(1,1) NOT NULL,
    SupplierId      INT           NOT NULL,
    ItemId          INT           NOT NULL,
    SupplierPartNo  NVARCHAR(50)  NOT NULL,   -- their number, not yours
    LeadTimeDays    SMALLINT      NULL,
    MinOrderQty     DECIMAL(18,4) NULL,
    OrderUomCode    VARCHAR(10)   NULL,       -- how they sell it, not how you stock it
    IsPreferred     BIT           NOT NULL CONSTRAINT DF_SupplierItem_Pref DEFAULT 0,
    CONSTRAINT PK_SupplierItem PRIMARY KEY CLUSTERED (SupplierItemId),
    CONSTRAINT UQ_SupplierItem_Pair UNIQUE (SupplierId, ItemId),
    CONSTRAINT FK_SupplierItem_Supplier FOREIGN KEY (SupplierId)
        REFERENCES dbo.Supplier (SupplierId)
);

-- One preferred source per item.
CREATE UNIQUE INDEX UX_SupplierItem_Preferred
    ON dbo.SupplierItem (ItemId) WHERE IsPreferred = 1;

One detail in there causes more trouble than the rest combined. OrderUomCode is the unit the supplier sells in, which is not always the unit you stock in, and the conversion between them belongs to the item rather than this table. Get that wrong and you order twenty-four times what you meant to. Also worth deciding early: whether payment terms sit on the supplier or can be overridden per item, because procurement will eventually ask for the override. This table has enough in it to deserve its own treatment, which it gets in supplier item master data: the manufacturer part number, sourcing rank, and what your ERP already calls the record.

The parent-child problem

The ParentSupplierId column in the parent table does one job: it records legal ownership, so you know that ACME Nederland B.V. is a subsidiary of ACME Group. That is a fact about company structure, it changes rarely, and a self-referencing column handles it fine.

What it cannot do is spend rollup, and this is where supplier models usually go wrong on the second attempt rather than the first. Procurement wants total spend with the ACME group, which follows legal ownership. Finance wants spend by paying entity, which follows the invoicing structure. Category management wants spend by commodity, which cuts across both. These are three different trees over the same suppliers, and the moment you try to serve all three from one parent column, one of them starts counting the same revenue twice. Keep the legal parent where it is, as a column on the supplier, and give every other tree its own named hierarchy with a membership table underneath it. You will end up with two or three of those, which sounds like more work than it is, since each one is a small table and nobody has to reconcile them against each other.

What to make mandatory at creation

Standardization documents tend to run to forty fields, and the length is why nobody follows them. Six fields, enforced at the moment of creation, prevent more damage than a forty-field guideline enforced by nobody.

Legal name, as registered

Not the trading name, not what the buyer calls them. The registered name is the only one that matches a tax record or a bank account.

Country of registration

Everything else is scoped by it: tax number format, VAT validation, sanctions screening, payment rails.

Tax registration number

Mandatory for companies, with a documented exception path for sole traders who genuinely have none. This single field kills most duplicates at creation.

Lifecycle status

Draft, active, blocked, archived. Not a boolean. A blocked supplier and an archived supplier need different behaviour from the payment run.

At least one site, typed

A supplier with no address is a record nobody can transact against. Force one site with an explicit type at creation.

Owner

A named person, not a department. Every field above decays, and decay without an owner is nobody’s job.

Everything else can be filled in later by the function that cares about it. Certificates, categories, and extra contacts are all things a supplier can trade without on day one, and blocking creation until compliance has uploaded an ISO certificate is how people learn to create suppliers in the spreadsheet instead.

Building this without writing the DDL

The SQL above is the model expressed as tables, which is the right form if you are extending a database you already own. In a master data platform you express the same thing as entities and attributes instead, and the seven tables become seven entities in one Supplier model, with the child entities referencing the parent through a domain attribute. Country, currency, and commodity category stop being free-text columns and become their own reference entities, which is what makes them selectable rather than typeable.

In Primentra that model takes an afternoon to build, and two things come with it that are awkward to write by hand. Bank accounts can require approval before a change goes live, so a new IBAN is a proposed change until a second person reviews it against the old value. And every field change is recorded with who, when, and what it was before, which is the history the effective-dated design above is trying to reproduce manually. If you are coming off Microsoft MDS, this is also the point where the model matters most: a flat vendor entity migrated as-is carries the four ACME rows into the new system, and the migration is the cheapest moment you will ever have to split them.

Common questions

What is a supplier master data model?

The set of entities and relationships that describe a vendor: one record for the legal entity, plus separate child records for sites, bank accounts, tax registrations, contacts, certificates, and categories. The vendor master in your ERP is a single table. The model is the shape underneath it.

What tables does it need?

Seven cover almost every case: Supplier, SupplierSite, SupplierBankAccount, SupplierTaxRegistration, SupplierContact, SupplierCertificate, and SupplierCategory. Each exists because its relationship to the supplier is one-to-many, and a flat table cannot express that without encoding it in the name.

Should the tax number be the primary key?

No. Use a surrogate integer key and put the tax number behind a unique constraint scoped to country. Tax numbers change on restructuring, are not unique across countries, and some suppliers have none. It is your best duplicate-prevention constraint, not your identifier.

What is supplier item master data?

The relationship between a supplier and an item you buy from them: supplier part number, lead time, minimum order quantity, price breaks, preferred-source flag. It belongs to neither master, because one item comes from several vendors and one vendor supplies hundreds of items. It needs its own table keyed on both.

How is this different from a vendor master?

The vendor master is an ERP table holding what finance needs to pay someone. The model is the entity design across every function that touches the vendor, including procurement, compliance, and logistics. Most companies have the first. Far fewer have the second.

Build the model without writing the migration scripts

Primentra runs on your own SQL Server and models suppliers as entities and attributes, so the seven tables above become a Supplier model you can change without a schema migration. Approvals and a field-level audit trail come with it. It deploys in a day and costs €7,500 per year flat, and the 60-day trial is long enough to load your real vendor master and find out how many ACME rows you have.

Start free trial →Try the demo →

More from the blog

Supplier master data management: what it includes, what breaks without it, and where to start9 min readSupplier item master data: the record that belongs to neither master10 min readThe product master data model: three levels your item master probably collapsed into one11 min read

Ready to migrate from Microsoft MDS?

Join the waitlist and be the first to try Primentra. All features included.

Download Free TrialTry DemoCompare MDM tools
Supplier Master Data Model: Tables & SQL | Primentra