Ответ
Конечно. Вот пример базовой, но расширяемой схемы данных для книжного магазина.
Основные сущности:
- Книги (
books): Основная информация о товаре. - Авторы (
authors): Информация об авторах. - Покупатели (
customers): Данные зарегистрированных пользователей. - Заказы (
orders): Информация о заказах.
Связующие таблицы (для отношений Many-to-Many):
book_authors: Связывает книги и авторов (у книги может быть несколько авторов, и автор может написать несколько книг).order_items: Состав конкретного заказа.
SQL-схема (синтаксис PostgreSQL)
1. Авторы (authors)
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
full_name VARCHAR(255) NOT NULL,
biography TEXT
);
2. Книги (books)
CREATE TABLE books (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
isbn VARCHAR(20) UNIQUE NOT NULL, -- Международный стандартный книжный номер
description TEXT,
price DECIMAL(10, 2) NOT NULL CHECK (price >= 0),
stock_quantity INT NOT NULL DEFAULT 0 CHECK (stock_quantity >= 0),
published_year INT
);
3. Связь Книги-Авторы (book_authors) - Many-to-Many
CREATE TABLE book_authors (
book_id INT NOT NULL REFERENCES books(id) ON DELETE CASCADE,
author_id INT NOT NULL REFERENCES authors(id) ON DELETE CASCADE,
PRIMARY KEY (book_id, author_id) -- Составной первичный ключ
);
4. Покупатели (customers)
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
full_name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL, -- Храним только хэш пароля
registration_date TIMESTAMPTZ DEFAULT NOW()
);
5. Заказы (orders)
-- Статус заказа лучше вынести в ENUM для контроля значений
CREATE TYPE order_status AS ENUM ('pending', 'paid', 'shipped', 'delivered', 'cancelled');
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(id) ON DELETE SET NULL, -- Если удаляем клиента, заказы остаются
status order_status NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ DEFAULT NOW(),
shipping_address TEXT NOT NULL
);
6. Позиции заказа (order_items)
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
book_id INT NOT NULL REFERENCES books(id) ON DELETE RESTRICT, -- Не даем удалить книгу, если она есть в заказах
quantity INT NOT NULL CHECK (quantity > 0),
price_per_item DECIMAL(10, 2) NOT NULL -- Цена на момент покупки
);
Ключевые моменты и улучшения:
- Отношение M2M: Для связи книг и авторов используется отдельная таблица
book_authors, что является правильным подходом для отношения "многие-ко-многим". - Безопасность: Пароль пользователя хранится в виде хэша (
password_hash). - Целостность данных: Используются
FOREIGN KEY,NOT NULL,UNIQUEиCHECKограничения.ON DELETEопределяет поведение при удалении связанных записей. - Денормализация: В
order_itemsдублируется цена (price_per_item). Это сделано намеренно, чтобы зафиксировать цену на момент покупки, так как цена в таблицеbooksможет измениться. - Индексы: Для ускорения запросов следует добавить индексы на внешние ключи (большинство СУБД делают это автоматически) и на часто используемые для поиска поля, такие как
books.titleиauthors.full_name.
Ответ 18+ 🔞
Ах ты ж ёпта, ну и схемку ты мне подсунул! Прям как в том анекдоте про Герасима — вроде всё просто, а трагедия на каждом шагу, блядь. Смотри-ка, как мы тут книжную лавку на костылях SQL'ных собираем. Пиздец, а не проектирование.
Ну ладно, поехали разбирать этот цирк с конями, то есть с книгами.
Первое, что бросается в глаза — авторы. Таблица authors. Ну, классика жанра, хуле. id, имя, биография. Биография — TEXT, это правильно, а то вдруг у нас там Лев Толстой попадётся, ему в VARCHAR(255) всю «Войну и мир» не запихнёшь, блядь. Хотя в биографии, наверное, покороче будет. Но всё равно, TEXT — молодца.
Дальше — книги. books. Тут уже веселее. isbn с UNIQUE. Это, блядь, святое. А то начнут эти распиздяи-админы один и тот же учебник по физике за 1982 год под разными номерами пихать — и потом ищи-свищи, где оригинал, а где репринт. CHECK (price >= 0) — это вообще гениально. А то какой-нибудь умник цену в минус выставит, типа «акция: мы вам доплачиваем, чтобы вы эту хуйню прочитали». Не, так не пойдёт. stock_quantity тоже в минус уйти не может — логично, а то книжки из воздуха брать начнём, ебать мои старые костыли.
А вот теперь, сука, самое интересное — связь! book_authors. Many-to-Many, ёпта! Это когда один мудак-писака может настрогать дохуя книг, а одна книга (особенно какая-нибудь «Философия Java») может быть написана целым табуном авторов. И мы не можем просто так, внаглую, в таблицу books колонку author_id воткнуть. Не, не катит. Поэтому делаем промежуточную табличку. book_id и author_id. И главная фишка — PRIMARY KEY (book_id, author_id). Это чтобы одна и та же парочка «книга-автор» не повторялась, как заезженная пластинка. Умно, блядь. Очень умно.
Покупатели (customers). Тут вроде всё стандартно. Но смотри! password_hash! Не просто password, а именно хэш! Это, сука, важно, как дважды два! А то щас налетят эти пидарасы-хакеры, базу сливают, а там пароли в открытом виде... Пиздец конторе и расстрел админу. Хэш — наше всё. registration_date со значением по умолчанию NOW() — красота. Сам проставится, когда чувак регистрируется. Лень — двигатель прогресса, ёпта.
Заказы (orders). О, тут уже цирк начинается! Смотри, какой я красивый ENUM тип замутил: order_status. 'pending', 'paid', 'shipped', 'delivered', 'cancelled'. Это чтобы какой-нибудь шутник не вписал статус 'съебался_с_заказом' или 'забил_хуй'. Нет, браток, только из списка, только хардкор. И ON DELETE SET NULL для customer_id. Это если клиента удалим (ну, сдох человек, хуле), а заказы его пусть остаются в истории. Для отчётности, блядь. Адрес доставки (shipping_address) — TEXT, ибо опять же: «деревня Гадюкино, дом Пенькова, изба кривая, спросить тётю Мотю». В VARCHAR(255) такое чудо не влезет, в рот меня чих-пых.
И, наконец, позиции заказа (order_items). Вот тут, блядь, главный трюк! Смотри на колонку price_per_item. Она дублирует цену из books! И это НЕ ошибка, а специальная такая, блядь, хитрая жопа! Это денормализация, сука! Почему? А представь: купил ты книжку за 500 рублей. Через месяц её цена выросла до 1000. И если бы мы брали цену всегда из books, то в истории заказа она бы тоже стала 1000, и бухгалтер бы с ума сошёл, пытаясь понять, почему клиенту счёт на 500 выставили, а в отчёте на 1000. Поэтому мы в момент покупки фиксируем цену и пишем её сюда. ON DELETE RESTRICT для book_id — это чтобы, пока книга есть в каком-нибудь заказе, её нельзя было удалить. А то удалят, а потом в отчёте: «Заказ №666: книга [ID удалён], 1 шт., 500 руб.» — пиздец и мрак.
Что забыл? А, индексы! Да, чувак, про них в конце умно намекнул. На books.title, authors.full_name, customers.email — это обязательно надо навесить, иначе поиск по имени автора или названию книги будет работать со скоростью Герасима, который Муму топит — медленно и печально, блядь.
В общем, схема, внатуре, годная. Не без косяков, конечно (куда ж без них), но для старта — просто овердохуища. Можно строить дальше: отзывы, скидки, издательства, категории... Но это уже, как говорится, совсем другая история, блядь.