Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions koik.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
--region Enums
CREATE TYPE PAYMENT_METHOD AS ENUM ('pay_after_receiving', 'pay_after_buying');
CREATE TYPE SEX AS ENUM ('m', 'f');
--endregion

--region Core Tables (независимые)
CREATE TABLE table_emails (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
email TEXT NOT NULL CHECK (email != '' AND email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
);

CREATE TABLE table_phone_numbers (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
phone_number TEXT NOT NULL CHECK (phone_number != '' AND length(phone_number) >= 10)
);

CREATE TABLE table_units (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
unit_type TEXT NOT NULL CHECK (unit_type != '' AND unit_type !~* '^\s+|\s+$')
);

CREATE TABLE table_product_types (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
name TEXT NOT NULL CHECK (name != '' AND name !~* '^\s+|\s+$')
);

CREATE TABLE table_salespoint (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
address TEXT NOT NULL CHECK (address != '' AND address !~* '^\s+|\s+$'),
name TEXT NOT NULL CHECK (name != '' AND name !~* '^\s+|\s+$')
);
--endregion

--region Reference Tables (зависят от core)
CREATE TABLE table_manufacturers (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
name TEXT NOT NULL CHECK (name != ''),
address TEXT NOT NULL CHECK (address != ''),
phone_number TEXT NOT NULL CHECK (phone_number != ''),
website TEXT NOT NULL CHECK (website ~ '^https?://'),
manufacture_country TEXT NOT NULL CHECK (manufacture_country != ''),
brand TEXT NOT NULL CHECK (brand != ''),
license TEXT NOT NULL CHECK (license != '')
);

CREATE TABLE table_persons (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
name TEXT NOT NULL CHECK (name != '' AND name !~* '^\s+|\s+$'),
surname TEXT NOT NULL CHECK (surname != '' AND surname !~* '^\s+|\s+$'),
patronymic TEXT NOT NULL CHECK (patronymic !~* '^\s+|\s+$'),
sex SEX NOT NULL,
email_id INTEGER NOT NULL,
phone_number_id INTEGER NOT NULL,
FOREIGN KEY (email_id) REFERENCES table_emails(id) ON DELETE RESTRICT,
FOREIGN KEY (phone_number_id) REFERENCES table_phone_numbers(id) ON DELETE RESTRICT,
UNIQUE(email_id, phone_number_id)
);

CREATE TABLE table_products (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
type_id INTEGER NOT NULL,
unit_id INTEGER NOT NULL,
manufacturer_id INTEGER NOT NULL,
name TEXT NOT NULL CHECK (name != ''),
description TEXT NOT NULL CHECK (description != ''),
series TEXT NOT NULL CHECK (series != ''),
purchase_price NUMERIC(7,2) NOT NULL CHECK (purchase_price > 0),
unit_value NUMERIC(10,2) NOT NULL CHECK (unit_value > 0),
production_date DATE NOT NULL,
expiration_date DATE NOT NULL CHECK (expiration_date > production_date),
selling_price NUMERIC(7,2) NOT NULL CHECK (selling_price > purchase_price),
FOREIGN KEY (type_id) REFERENCES table_product_types(id),
FOREIGN KEY (unit_id) REFERENCES table_units(id),
FOREIGN KEY (manufacturer_id) REFERENCES table_manufacturers(id),
CHECK (selling_price > purchase_price)
);
--endregion

--region Business Logic Tables (зависят от persons/products)
CREATE TABLE table_clients (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
person_id INTEGER NOT NULL UNIQUE,
discount NUMERIC(4,2) NOT NULL CHECK (discount >= 0 AND discount <= 100),
agreed_on_mailing BOOLEAN NOT NULL DEFAULT false,
FOREIGN KEY (person_id) REFERENCES table_persons(id) ON DELETE CASCADE
);

CREATE TABLE table_employees (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
person_id INTEGER NOT NULL UNIQUE,
position TEXT NOT NULL CHECK (position != ''),
hire_date DATE NOT NULL,
salary_in_rubles NUMERIC(7,2) NOT NULL CHECK (salary_in_rubles > 0),
FOREIGN KEY (person_id) REFERENCES table_persons(id) ON DELETE CASCADE
);
--endregion

--region Purchase Tables (финальный уровень)
CREATE TABLE table_online_purchases (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
delivery_salespoint_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
payment_method PAYMENT_METHOD NOT NULL,
FOREIGN KEY (delivery_salespoint_id) REFERENCES table_salespoint(id),
FOREIGN KEY (product_id) REFERENCES table_products(id)
);

CREATE TABLE table_offline_purchases (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
client_id INTEGER NOT NULL DEFAULT 0,
employee_id INTEGER NOT NULL,
salespoint_id INTEGER NOT NULL,
FOREIGN KEY (client_id) REFERENCES table_clients(id) ON DELETE SET DEFAULT,
FOREIGN KEY (employee_id) REFERENCES table_employees(id),
FOREIGN KEY (salespoint_id) REFERENCES table_salespoint(id)
);

CREATE TABLE table_purchases (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY NOT NULL,
online_purchase_id INTEGER,
offline_purchase_id INTEGER,
product_id INTEGER NOT NULL,
purchase_date DATE NOT NULL DEFAULT CURRENT_DATE,
CHECK ((online_purchase_id IS NULL) != (offline_purchase_id IS NULL)),
FOREIGN KEY (online_purchase_id) REFERENCES table_online_purchases(id),
FOREIGN KEY (offline_purchase_id) REFERENCES table_offline_purchases(id),
FOREIGN KEY (product_id) REFERENCES table_products(id)
);
--endregion
199 changes: 199 additions & 0 deletions sport_shop.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
SET TIME ZONE 'Europe/Moscow';

CREATE TYPE SEX AS ENUM ('M', 'F');

CREATE TYPE email_domain_enum AS ENUM (
'gmail.com', 'yandex.ru', 'mail.ru', 'outlook.com',
'yahoo.com', 'rambler.ru', 'proton.me', 'icloud.com'
);


CREATE TABLE table_manufacturers
(
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name_manufacturer TEXT NOT NULL CHECK ( name_manufacturer != ''),
country TEXT NOT NULL CHECK ( country != '')
);


CREATE TABLE table_products
(
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
manufacturer_id INT NOT NULL,
product_name TEXT NOT NULL CHECK ( product_name != '' ),
product_type TEXT NOT NULL CHECK ( product_type != '' ),
stock_quantity INT NOT NULL CHECK ( stock_quantity >= 0),
cost_price NUMERIC(12, 2) NOT NULL CHECK ( cost_price >= 0),
selling_price NUMERIC(12, 2) NOT NULL CHECK ( selling_price >= 0),

FOREIGN KEY (manufacturer_id) REFERENCES table_manufacturers (id)
);

CREATE TABLE table_clients
(
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
first_name TEXT NOT NULL CHECK ( first_name != '' ),
patronymic TEXT,
last_name TEXT NOT NULL CHECK ( last_name != '' ),
sex SEX NOT NULL,
order_history TEXT,
discount_percent NUMERIC(5, 2) CHECK ( discount_percent BETWEEN 0 AND 70),
is_subscribed_newsletter BOOLEAN NOT NULL DEFAULT false
);


CREATE TABLE table_employees
(
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
first_name TEXT NOT NULL CHECK ( first_name != '' ),
patronymic TEXT,
last_name TEXT NOT NULL CHECK ( last_name != '' ),
position TEXT NOT NULL CHECK ( position != '' ),
hire_date DATE NOT NULL,
sex SEX NOT NULL,
salary NUMERIC(12, 2) CHECK ( salary >= 0 )
);

CREATE TABLE table_sales_orders
(
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
client_id INT,
employee_id INT NOT NULL,
product_id INT NOT NULL,
selling_price NUMERIC(12, 2) NOT NULL CHECK ( selling_price >= 0 ),
quantity INT NOT NULL CHECK ( quantity > 0 ),
sale_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),

FOREIGN KEY (client_id) REFERENCES table_clients (id),
FOREIGN KEY (employee_id) REFERENCES table_employees (id),
FOREIGN KEY (product_id) REFERENCES table_products (id)
);

CREATE TABLE table_client_emails
(
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
client_id INT NOT NULL,
email TEXT NOT NULL CHECK ( email != '' ),
email_type email_domain_enum NOT NULL,

FOREIGN KEY (client_id) REFERENCES table_clients (id)
);

CREATE TABLE table_client_phones
(
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
client_id INT NOT NULL,
phone_number TEXT NOT NULL CHECK ( phone_number != '' ),

FOREIGN KEY (client_id) REFERENCES table_clients (id)
);



CREATE OR REPLACE FUNCTION trg_decrease_stock()
RETURNS TRIGGER AS
$$
BEGIN

IF (SELECT stock_quantity
FROM table_products
WHERE id = NEW.product_id) < NEW.quantity THEN
RAISE EXCEPTION 'Not enought stock for products %', NEW.product_id;
END IF;

UPDATE table_products
SET stock_quantity = stock_quantity - NEW.quantity
WHERE id = NEW.product_id;

RETURN NEW;

END
$$
LANGUAGE plpgsql;

CREATE TRIGGER before_insert_sales_orders_decrease_stock
BEFORE INSERT
ON table_sales_orders
FOR EACH ROW
EXECUTE FUNCTION trg_decrease_stock();

-- Производители
INSERT INTO table_manufacturers (name_manufacturer, country)
VALUES ('Nike', 'USA'),
('Adidas', 'Germany'),
('Puma', 'Germany'),
('Reebok', 'USA'),
('Decathlon', 'France');

-- Товары
INSERT INTO table_products (manufacturer_id, product_name, product_type, stock_quantity, cost_price, selling_price)
VALUES (1, 'Nike Air Max 90', 'Footwear', 50, 2500.00, 5999.00),
(1, 'Nike Dri-FIT T-Shirt', 'Clothing', 120, 800.00, 1999.00),
(2, 'Adidas Ultraboost 23', 'Footwear', 35, 3000.00, 7499.00),
(2, 'Adidas Running Shorts', 'Clothing', 80, 600.00, 1499.00),
(3, 'Puma RS-X', 'Footwear', 45, 2200.00, 4999.00),
(4, 'Reebok Classics', 'Footwear', 25, 1800.00, 3999.00),
(5, 'Decathlon Yoga Mat', 'Equipment', 100, 300.00, 799.00),
(5, 'Decathlon Water Bottle', 'Equipment', 200, 150.00, 399.00);

-- Клиенты
INSERT INTO table_clients (first_name, patronymic, last_name, sex, discount_percent, is_subscribed_newsletter)
VALUES ('Иван', 'Сергеевич', 'Петров', 'M', 5.00, true),
('Мария', 'Ивановна', 'Сидорова', 'F', 10.00, true),
('Алексей', 'Владимирович', 'Иванов', 'M', 0.00, false),
('Екатерина', 'Александровна', 'Смирнова', 'F', 7.50, true),
('Павел', NULL, 'Козлов', 'M', 0.00, false);

-- Email клиентов
INSERT INTO table_client_emails (client_id, email, email_type)
VALUES (1, 'ivan.petrov@gmail.com', 'gmail.com'),
(1, 'ivan.petrov@yandex.ru', 'yandex.ru'),
(2, 'maria.sidorova@mail.ru', 'mail.ru'),
(3, 'alex.ivanov@gmail.com', 'gmail.com'),
(4, 'katerina.smirnova@outlook.com', 'outlook.com'),
(5, 'pavel.kozlov@yandex.ru', 'yandex.ru');

-- Телефоны клиентов
INSERT INTO table_client_phones (client_id, phone_number)
VALUES (1, '+7-999-123-45-67'),
(1, '+7-995-987-65-43'),
(2, '+7-996-555-11-22'),
(3, '+7-998-777-88-99'),
(4, '+7-999-444-33-22'),
(5, '+7-995-111-22-33');

-- Сотрудники
INSERT INTO table_employees (first_name, patronymic, last_name, position, hire_date, sex, salary)
VALUES ('Игорь', 'Эдуардович', 'Морозов', 'Sales Manager', '2023-01-15', 'M', 45000.00),
('Анна', 'Петровна', 'Волкова', 'Sales Associate', '2023-06-01', 'F', 30000.00),
('Дмитрий', 'Сергеевич', 'Соколов', 'Sales Associate', '2024-02-10', 'M', 30000.00),
('Светлана', 'Викторовна', 'Новикова', 'Store Manager', '2022-11-20', 'F', 55000.00),
('Юрий', 'Львович', 'Лебедев', 'Warehouse Manager', '2023-03-05', 'M', 40000.00);

-- Продажи
INSERT INTO table_sales_orders (client_id, employee_id, product_id, selling_price, quantity, sale_date)
VALUES (1, 2, 1, 5999.00, 1, '2025-12-01 10:30:00'),
(2, 3, 4, 1499.00, 2, '2025-12-02 14:15:00'),
(3, 2, 7, 799.00, 3, '2025-12-03 09:45:00'),
(4, 1, 3, 7499.00, 1, '2025-12-04 16:20:00'),
(1, 3, 8, 399.00, 5, '2025-12-05 11:00:00'),
(NULL, 2, 2, 1999.00, 1, '2025-12-06 13:30:00'), -- покупатель без регистрации
(2, 1, 5, 4999.00, 1, '2025-12-07 15:45:00');


CREATE OR REPLACE VIEW v_employee_sales_stats AS
SELECT
e.id,
e.first_name || ' ' || e.last_name AS employee_name,
e.position,
COUNT(so.id) AS total_sales,
SUM(so.quantity) AS total_items_sold,
SUM(so.quantity * so.selling_price) AS total_revenue,
ROUND(AVG(so.quantity * so.selling_price), 2) AS avg_sale_amount
FROM table_employees e
LEFT JOIN table_sales_orders so ON e.id = so.employee_id
GROUP BY e.id, e.first_name, e.last_name, e.position
ORDER BY total_revenue DESC;


SELECT * FROM v_employee_sales_stats;