← Назад к вопросам
Приведи пример использования составного первичного ключа в БД
2.2 Middle🔥 171 комментариев
#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Составной первичный ключ в БД
Композитный PK (Primary Key) — это ключ, состоящий из нескольких столбцов. Используется когда уникальность требует комбинации полей.
Пример 1: Junction Table (Many-to-Many)
Сценарий: Студент может записаться на несколько курсов, курс может иметь много студентов.
CREATE TABLE students (
id UUID PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE courses (
id UUID PRIMARY KEY,
title VARCHAR(100) NOT NULL
);
CREATE TABLE student_courses (
student_id UUID NOT NULL,
course_id UUID NOT NULL,
enrolled_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (student_id, course_id), -- Составной ключ
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (course_id) REFERENCES courses(id)
);
-- Невозможно записать одного студента на один курс дважды
INSERT INTO student_courses (student_id, course_id) VALUES ('abc', 'course1');
INSERT INTO student_courses (student_id, course_id) VALUES ('abc', 'course1'); -- ОШИБКА!
Node.js TypeORM:
@Entity('students')
export class Student {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@ManyToMany(() => Course)
@JoinTable({
name: 'student_courses',
joinColumn: { name: 'student_id' },
inverseJoinColumn: { name: 'course_id' },
})
courses: Course[];
}
@Entity('courses')
export class Course {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
title: string;
}
Пример 2: Order Items
Сценарий: Один заказ может содержать одну единицу товара максимум (нет дублей).
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE order_items (
order_id UUID NOT NULL,
product_id UUID NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (order_id, product_id), -- Составной ключ
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
);
-- Один заказ может иметь только один товар определённого типа
INSERT INTO order_items VALUES ('ord1', 'prod1', 2, 50.00);
INSERT INTO order_items VALUES ('ord1', 'prod1', 3, 50.00); -- ОШИБКА: дубликат!
Node.js Запрос:
async function getOrderWithItems(orderId: string) {
const order = await db.query(`
SELECT o.id, o.created_at,
json_agg(json_build_object(
'product_id', oi.product_id,
'quantity', oi.quantity,
'price', oi.price
)) as items
FROM orders o
LEFT JOIN order_items oi ON o.id = oi.order_id
WHERE o.id = $1
GROUP BY o.id
`, [orderId]);
return order.rows[0];
}
Пример 3: User Permissions
Сценарий: Пользователь может иметь роль на определённом уровне (проект, команда), но только одну.
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(100) UNIQUE NOT NULL
);
CREATE TABLE teams (
id UUID PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE team_members (
user_id UUID NOT NULL,
team_id UUID NOT NULL,
role VARCHAR(50) NOT NULL CHECK (role IN ('admin', 'member', 'viewer')),
joined_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (user_id, team_id), -- Составной ключ
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (team_id) REFERENCES teams(id)
);
-- Пользователь может быть в команде только один раз
INSERT INTO team_members VALUES ('user1', 'team1', 'admin');
INSERT INTO team_members VALUES ('user1', 'team1', 'member'); -- ОШИБКА!
Пример 4: Product Variants (SKU)
Сценарий: Один товар может иметь варианты (размеры, цвета), каждый вариант уникален.
CREATE TABLE products (
id UUID PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE product_variants (
product_id UUID NOT NULL,
size VARCHAR(10) NOT NULL,
color VARCHAR(50) NOT NULL,
sku VARCHAR(50) UNIQUE NOT NULL, -- SKU должен быть уникален
stock INT DEFAULT 0,
price DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (product_id, size, color), -- Составной ключ (3 столбца)
FOREIGN KEY (product_id) REFERENCES products(id)
);
-- Один товар не может иметь одну комбинацию (размер + цвет) дважды
INSERT INTO product_variants VALUES ('prod1', 'M', 'red', 'SKU-001', 10, 29.99);
INSERT INTO product_variants VALUES ('prod1', 'M', 'red', 'SKU-002', 5, 29.99); -- ОШИБКА!
Node.js запрос:
async function getProductVariants(productId: string) {
return db.query(`
SELECT product_id, size, color, sku, stock, price
FROM product_variants
WHERE product_id = $1
ORDER BY size, color
`, [productId]);
}
Пример 5: Sharded Data by Time and User
Сценарий: В высоконагруженной системе логируем события пользователя по дням (таблица очень большая).
-- Таблица для каждого дня (шардирование по времени + пользователю)
CREATE TABLE user_events_2024_01_01 (
user_id UUID NOT NULL,
event_id UUID NOT NULL,
timestamp TIMESTAMP NOT NULL,
event_type VARCHAR(50) NOT NULL,
data JSONB,
PRIMARY KEY (user_id, event_id), -- Составной ключ
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Для каждого дня новая таблица
-- user_events_2024_01_02
-- user_events_2024_01_03
-- etc.
Когда использовать
✅ Используй составной PK когда:
- Many-to-many отношения
- Уникальность требует комбинации полей
- Хочешь явно заявить об этом в структуре БД
❌ Не используй когда:
- Есть естественный UUID/ID (используй его)
- Нужна гибкость изменения уникальных полей
- Слишком много столбцов в ключе (максимум 3-4)
Индексирование составного ключа
-- Автоматически создается индекс на PRIMARY KEY
-- Но часто нужны дополнительные для поиска
CREATE INDEX idx_student_courses_course
ON student_courses(course_id); -- Поиск по курсу
CREATE INDEX idx_order_items_product
ON order_items(product_id, order_id); -- Поиск товаров