A composite key (also known as a compound key) is a primary key composed of two or more columns that together uniquely identify each row in a table. Unlike a simple primary key which uses a single column, composite keys leverage multiple columns to establish uniqueness.
Composite keys are particularly useful when:
- No Single Natural Key Exists: When no single column can uniquely identify a record
- Natural Relationships: When multiple attributes naturally combine to form a unique identifier
- Junction Tables: In many-to-many relationships where the combination of foreign keys forms the primary key
- Historical Data: When tracking changes over time (e.g., employee + date combination)
- Hierarchical Data: When representing parent-child relationships with multiple levels
CREATE TABLE TableName (
Column1 DataType NOT NULL,
Column2 DataType NOT NULL,
Column3 DataType,
CONSTRAINT PK_TableName PRIMARY KEY (Column1, Column2)
);ALTER TABLE TableName
ADD CONSTRAINT PK_TableName PRIMARY KEY (Column1, Column2);The file T-Sql/id-ego-superego-composite-key.tsql demonstrates composite keys using Freudian psychology concepts where id, ego, and superego together form a unique identifier for psychological states.
This creative example illustrates several key concepts:
-
Multiple Component Identity: Just as Freud's psyche model requires all three components (id, ego, superego) to describe a complete psychological state, the composite key requires all three columns to uniquely identify a record.
-
Natural Relationship: The strength or level of each component (represented by integer values) combines to create a unique psychological profile.
-
Referential Integrity: Child tables (PsychologicalConflict, BehavioralOutcome, PsycheMetrics) reference the parent table using all three components of the composite key.
CREATE TABLE PsychologicalState (
id INT NOT NULL, -- Primitive desires
ego INT NOT NULL, -- Realistic mediator
superego INT NOT NULL, -- Moral standards
stateName VARCHAR(100),
description VARCHAR(500),
conflictLevel DECIMAL(3,2),
resolutionStrategy VARCHAR(200),
CONSTRAINT PK_PsychologicalState PRIMARY KEY (id, ego, superego)
);Child tables must reference all components:
CREATE TABLE PsychologicalConflict (
conflictId INT PRIMARY KEY IDENTITY(1,1),
id INT NOT NULL,
ego INT NOT NULL,
superego INT NOT NULL,
conflictType VARCHAR(50),
-- Foreign key must include all composite key columns
CONSTRAINT FK_Conflict_State FOREIGN KEY (id, ego, superego)
REFERENCES PsychologicalState(id, ego, superego)
);- Reflects real-world relationships more accurately
- No need for artificial surrogate keys
- Self-documenting (meaningful column names)
- Enforces uniqueness across multiple dimensions
- Prevents duplicate combinations
- Maintains referential integrity across related tables
- Can improve query performance when filtering on key columns
- Supports efficient joins on multiple columns
- Makes the unique identifier meaningful
- Easier to understand data relationships
- More complex to write JOIN statements
- Foreign key definitions are longer
- More columns to manage in relationships
- Larger index size (multiple columns)
- Potentially slower than single-column keys
- More data to transfer in joins
- Updating any part of the key affects all referencing tables
- Requires cascading updates if key values change
- More difficult to maintain if business rules change
- More complex in ORM frameworks
- Requires multiple parameters for lookups
- Can complicate API design
-- Good: Status codes and dates rarely change
CREATE TABLE OrderStatus (
orderId INT NOT NULL,
statusDate DATE NOT NULL,
status VARCHAR(20),
PRIMARY KEY (orderId, statusDate)
);
-- Avoid: Email addresses can change
CREATE TABLE UserProfile (
email VARCHAR(100) NOT NULL,
profileDate DATE NOT NULL,
-- Not ideal as composite key
PRIMARY KEY (email, profileDate)
);-- Good: 2-3 columns
PRIMARY KEY (customerId, orderDate)
-- Potentially problematic: Too many columns
PRIMARY KEY (region, country, state, city, zipCode)
-- Consider a surrogate key instead-- Option: Composite key + surrogate key for easier referencing
CREATE TABLE OrderItem (
orderItemId INT IDENTITY(1,1) PRIMARY KEY, -- Surrogate
orderId INT NOT NULL,
productId INT NOT NULL,
quantity INT,
CONSTRAINT UK_OrderItem UNIQUE (orderId, productId) -- Natural key
);Always document why the specific columns form a composite key:
-- The combination of studentId and courseId uniquely identifies
-- a student's enrollment in a specific course
CREATE TABLE Enrollment (
studentId INT NOT NULL,
courseId INT NOT NULL,
enrollmentDate DATE,
grade VARCHAR(2),
PRIMARY KEY (studentId, courseId)
);-- Student-Course enrollment
CREATE TABLE StudentCourse (
studentId INT NOT NULL,
courseId INT NOT NULL,
semester VARCHAR(20),
grade DECIMAL(3,2),
PRIMARY KEY (studentId, courseId),
FOREIGN KEY (studentId) REFERENCES Students(studentId),
FOREIGN KEY (courseId) REFERENCES Courses(courseId)
);-- Stock prices over time
CREATE TABLE StockPrice (
symbol VARCHAR(10) NOT NULL,
priceDate DATETIME NOT NULL,
openPrice DECIMAL(10,2),
closePrice DECIMAL(10,2),
volume BIGINT,
PRIMARY KEY (symbol, priceDate)
);-- Organization hierarchy
CREATE TABLE OrganizationUnit (
companyId INT NOT NULL,
departmentId INT NOT NULL,
unitId INT NOT NULL,
unitName VARCHAR(100),
PRIMARY KEY (companyId, departmentId, unitId)
);-- Product versions
CREATE TABLE ProductVersion (
productId INT NOT NULL,
versionNumber INT NOT NULL,
releaseDate DATE,
features VARCHAR(1000),
PRIMARY KEY (productId, versionNumber)
);SELECT *
FROM PsychologicalState
WHERE id = 1 AND ego = 2 AND superego = 1;SELECT ps.stateName, pc.conflictType
FROM PsychologicalState ps
JOIN PsychologicalConflict pc
ON ps.id = pc.id
AND ps.ego = pc.ego
AND ps.superego = pc.superego;-- Find all states where id = 2 (partial key)
SELECT *
FROM PsychologicalState
WHERE id = 2;| Aspect | Composite Key | Surrogate Key |
|---|---|---|
| Meaning | Business-meaningful columns | System-generated, no business meaning |
| Stability | Can change if business rules change | Stable, never changes |
| Complexity | More complex joins and foreign keys | Simpler references |
| Performance | Larger indexes, more join overhead | Smaller indexes, faster joins |
| Clarity | Self-documenting | Requires additional context |
| Best For | Natural relationships, junction tables | Arbitrary records, frequently updated keys |
Composite keys are a powerful database design tool when used appropriately. They provide natural representation of complex relationships and enforce data integrity across multiple dimensions. However, they come with added complexity and should be chosen carefully based on:
- Business requirements
- Data stability
- Query patterns
- Performance needs
- Maintenance considerations
The id-ego-superego example in this repository demonstrates how composite keys can elegantly model complex, multi-dimensional concepts while maintaining referential integrity throughout the database schema.
- Primary Keys: Unique identifiers for table rows
- Foreign Keys: References to primary keys in other tables
- Candidate Keys: Potential primary keys
- Natural Keys: Keys derived from real-world attributes
- Surrogate Keys: Artificial keys with no business meaning
- Unique Constraints: Ensure uniqueness without being primary keys