create_database.sql 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. -- ============================================================
  2. -- БАЗА ДАННЫХ: Продажа компьютерной техники
  3. -- СУБД: PostgreSQL
  4. -- ============================================================
  5. DROP TABLE IF EXISTS order_items CASCADE;
  6. DROP TABLE IF EXISTS orders CASCADE;
  7. DROP TABLE IF EXISTS app_users CASCADE;
  8. DROP TABLE IF EXISTS products CASCADE;
  9. DROP TABLE IF EXISTS suppliers CASCADE;
  10. DROP TABLE IF EXISTS clients CASCADE;
  11. DROP TABLE IF EXISTS employees CASCADE;
  12. DROP VIEW IF EXISTS v_orders_full_info CASCADE;
  13. DROP VIEW IF EXISTS v_late_orders CASCADE;
  14. DROP VIEW IF EXISTS v_order_items_full CASCADE;
  15. DROP VIEW IF EXISTS v_products_dictionary CASCADE;
  16. DROP FUNCTION IF EXISTS fn_get_late_orders_by_client(INTEGER);
  17. DROP FUNCTION IF EXISTS fn_calculate_order_total(INTEGER);
  18. DROP FUNCTION IF EXISTS fn_update_order_total();
  19. DROP FUNCTION IF EXISTS fn_reduce_product_stock();
  20. CREATE TABLE suppliers (
  21. supplier_id SERIAL PRIMARY KEY,
  22. company_name VARCHAR(150) NOT NULL,
  23. contact_person VARCHAR(100) NOT NULL,
  24. phone VARCHAR(20) NOT NULL,
  25. address VARCHAR(250) NOT NULL
  26. );
  27. CREATE TABLE products (
  28. product_id SERIAL PRIMARY KEY,
  29. product_name VARCHAR(200) NOT NULL,
  30. price NUMERIC(12,2) NOT NULL CHECK (price >= 0),
  31. stock_quantity INTEGER NOT NULL DEFAULT 0 CHECK (stock_quantity >= 0),
  32. category VARCHAR(100) NOT NULL,
  33. supplier_id INTEGER NOT NULL REFERENCES suppliers(supplier_id) ON UPDATE CASCADE
  34. );
  35. CREATE TABLE clients (
  36. client_id SERIAL PRIMARY KEY,
  37. first_name VARCHAR(80) NOT NULL,
  38. last_name VARCHAR(80) NOT NULL,
  39. address VARCHAR(250) NOT NULL,
  40. phone VARCHAR(20),
  41. email VARCHAR(150) UNIQUE
  42. );
  43. CREATE TABLE employees (
  44. employee_id SERIAL PRIMARY KEY,
  45. last_name VARCHAR(80) NOT NULL,
  46. first_name VARCHAR(80) NOT NULL,
  47. phone VARCHAR(20),
  48. email VARCHAR(150) UNIQUE,
  49. position VARCHAR(100) NOT NULL
  50. );
  51. CREATE TABLE orders (
  52. order_id SERIAL PRIMARY KEY,
  53. order_date TIMESTAMP NOT NULL DEFAULT NOW(),
  54. status VARCHAR(50) NOT NULL DEFAULT 'новый'
  55. CHECK (status IN ('новый','в обработке','отправлен','выполнен','отменён')),
  56. total_cost NUMERIC(14,2) NOT NULL DEFAULT 0 CHECK (total_cost >= 0),
  57. client_id INTEGER NOT NULL REFERENCES clients(client_id),
  58. employee_id INTEGER NOT NULL REFERENCES employees(employee_id)
  59. );
  60. CREATE TABLE order_items (
  61. order_item_id SERIAL PRIMARY KEY,
  62. order_id INTEGER NOT NULL REFERENCES orders(order_id) ON DELETE CASCADE,
  63. product_id INTEGER NOT NULL REFERENCES products(product_id),
  64. unit_price NUMERIC(12,2) NOT NULL CHECK (unit_price >= 0),
  65. quantity INTEGER NOT NULL CHECK (quantity > 0)
  66. );
  67. CREATE TABLE app_users (
  68. user_id SERIAL PRIMARY KEY,
  69. login VARCHAR(50) NOT NULL UNIQUE,
  70. password_hash TEXT NOT NULL,
  71. role_name VARCHAR(30) NOT NULL CHECK (role_name IN ('admin','manager','viewer'))
  72. );
  73. CREATE OR REPLACE VIEW v_orders_full_info AS
  74. SELECT
  75. o.order_id,
  76. o.order_date,
  77. o.status,
  78. o.total_cost,
  79. c.client_id,
  80. c.last_name || ' ' || c.first_name AS client_name,
  81. c.phone AS client_phone,
  82. c.email AS client_email,
  83. e.employee_id,
  84. e.last_name || ' ' || e.first_name AS employee_name,
  85. e.position AS employee_position
  86. FROM orders o
  87. JOIN clients c ON o.client_id = c.client_id
  88. JOIN employees e ON o.employee_id = e.employee_id;
  89. CREATE OR REPLACE VIEW v_late_orders AS
  90. SELECT *
  91. FROM v_orders_full_info
  92. WHERE status NOT IN ('выполнен', 'отменён')
  93. AND order_date < NOW() - INTERVAL '7 days';
  94. CREATE OR REPLACE VIEW v_order_items_full AS
  95. SELECT
  96. oi.order_item_id,
  97. oi.order_id,
  98. p.product_id,
  99. p.product_name,
  100. p.category,
  101. oi.unit_price,
  102. oi.quantity,
  103. oi.unit_price * oi.quantity AS item_sum
  104. FROM order_items oi
  105. JOIN products p ON oi.product_id = p.product_id;
  106. CREATE OR REPLACE VIEW v_products_dictionary AS
  107. SELECT
  108. p.product_id,
  109. p.product_name,
  110. p.category,
  111. p.price,
  112. p.stock_quantity,
  113. s.supplier_id,
  114. s.company_name AS supplier_name,
  115. s.contact_person,
  116. s.phone AS supplier_phone,
  117. s.address AS supplier_address
  118. FROM products p
  119. JOIN suppliers s ON p.supplier_id = s.supplier_id;
  120. CREATE OR REPLACE FUNCTION fn_get_late_orders_by_client(p_client_id INTEGER)
  121. RETURNS TABLE (
  122. order_id INTEGER,
  123. order_date TIMESTAMP,
  124. status VARCHAR,
  125. total_cost NUMERIC,
  126. client_id INTEGER,
  127. client_name TEXT,
  128. client_phone VARCHAR,
  129. client_email VARCHAR,
  130. employee_id INTEGER,
  131. employee_name TEXT
  132. )
  133. LANGUAGE plpgsql
  134. AS $$
  135. BEGIN
  136. RETURN QUERY
  137. SELECT
  138. v.order_id,
  139. v.order_date,
  140. v.status,
  141. v.total_cost,
  142. v.client_id,
  143. v.client_name,
  144. v.client_phone,
  145. v.client_email,
  146. v.employee_id,
  147. v.employee_name
  148. FROM v_late_orders v
  149. WHERE v.client_id = p_client_id
  150. ORDER BY v.order_date DESC;
  151. END;
  152. $$;
  153. CREATE OR REPLACE FUNCTION fn_calculate_order_total(p_order_id INTEGER)
  154. RETURNS NUMERIC
  155. LANGUAGE plpgsql
  156. AS $$
  157. DECLARE
  158. v_total NUMERIC(14,2);
  159. BEGIN
  160. SELECT COALESCE(SUM(unit_price * quantity), 0)
  161. INTO v_total
  162. FROM order_items
  163. WHERE order_id = p_order_id;
  164. RETURN v_total;
  165. END;
  166. $$;
  167. CREATE OR REPLACE FUNCTION fn_update_order_total()
  168. RETURNS TRIGGER
  169. LANGUAGE plpgsql
  170. AS $$
  171. DECLARE
  172. v_order_id INTEGER;
  173. BEGIN
  174. v_order_id := COALESCE(NEW.order_id, OLD.order_id);
  175. UPDATE orders
  176. SET total_cost = fn_calculate_order_total(v_order_id)
  177. WHERE order_id = v_order_id;
  178. RETURN COALESCE(NEW, OLD);
  179. END;
  180. $$;
  181. CREATE OR REPLACE FUNCTION fn_reduce_product_stock()
  182. RETURNS TRIGGER
  183. LANGUAGE plpgsql
  184. AS $$
  185. DECLARE
  186. v_stock INTEGER;
  187. BEGIN
  188. SELECT stock_quantity INTO v_stock
  189. FROM products
  190. WHERE product_id = NEW.product_id;
  191. IF v_stock IS NULL THEN
  192. RAISE EXCEPTION 'Товар не найден';
  193. END IF;
  194. IF v_stock < NEW.quantity THEN
  195. RAISE EXCEPTION 'Недостаточно товара на складе. Доступно: %, требуется: %', v_stock, NEW.quantity;
  196. END IF;
  197. UPDATE products
  198. SET stock_quantity = stock_quantity - NEW.quantity
  199. WHERE product_id = NEW.product_id;
  200. RETURN NEW;
  201. END;
  202. $$;
  203. CREATE TRIGGER trg_update_order_total_insert
  204. AFTER INSERT ON order_items
  205. FOR EACH ROW
  206. EXECUTE FUNCTION fn_update_order_total();
  207. CREATE TRIGGER trg_update_order_total_update
  208. AFTER UPDATE ON order_items
  209. FOR EACH ROW
  210. EXECUTE FUNCTION fn_update_order_total();
  211. CREATE TRIGGER trg_update_order_total_delete
  212. AFTER DELETE ON order_items
  213. FOR EACH ROW
  214. EXECUTE FUNCTION fn_update_order_total();
  215. CREATE TRIGGER trg_reduce_product_stock
  216. BEFORE INSERT ON order_items
  217. FOR EACH ROW
  218. EXECUTE FUNCTION fn_reduce_product_stock();
  219. CREATE INDEX idx_orders_client_id ON orders(client_id);
  220. CREATE INDEX idx_orders_employee_id ON orders(employee_id);
  221. CREATE INDEX idx_orders_status ON orders(status);
  222. CREATE INDEX idx_orders_order_date ON orders(order_date);
  223. CREATE INDEX idx_products_supplier_id ON products(supplier_id);
  224. CREATE INDEX idx_products_category ON products(category);
  225. CREATE INDEX idx_order_items_order_id ON order_items(order_id);
  226. CREATE INDEX idx_order_items_product_id ON order_items(product_id);
  227. INSERT INTO suppliers(company_name, contact_person, phone, address) VALUES
  228. ('TechImport', 'Иванов Сергей', '+79001112233', 'Москва, ул. Ленина, д. 10'),
  229. ('CompMarket', 'Петров Алексей', '+79004445566', 'Санкт-Петербург, Невский пр., д. 20'),
  230. ('DigitalTrade', 'Соколова Анна', '+79007778899', 'Великий Новгород, ул. Большая Санкт-Петербургская, д. 15');
  231. INSERT INTO products(product_name, price, stock_quantity, category, supplier_id) VALUES
  232. ('Ноутбук Lenovo IdeaPad 3', 65000.00, 15, 'Ноутбуки', 1),
  233. ('Ноутбук ASUS VivoBook', 72000.00, 10, 'Ноутбуки', 1),
  234. ('Монитор Samsung 24', 18000.00, 30, 'Мониторы', 2),
  235. ('Монитор LG UltraGear', 26000.00, 12, 'Мониторы', 2),
  236. ('Клавиатура Logitech K120', 1500.00, 50, 'Периферия', 3),
  237. ('Мышь Logitech M185', 1200.00, 60, 'Периферия', 3),
  238. ('SSD Kingston 1TB', 7200.00, 40, 'Комплектующие', 2),
  239. ('Видеокарта MSI RTX 4060', 42000.00, 8, 'Комплектующие', 1);
  240. INSERT INTO clients(first_name, last_name, address, phone, email) VALUES
  241. ('Андрей', 'Смирнов', 'Великий Новгород, ул. Мира, д. 5', '+79110000001', 'smirnov@mail.ru'),
  242. ('Ольга', 'Кузнецова', 'Великий Новгород, ул. Парковая, д. 8', '+79110000002', 'kuznetsova@mail.ru'),
  243. ('Иван', 'Павлов', 'Великий Новгород, ул. Ломоносова, д. 3', '+79110000003', 'pavlov@mail.ru'),
  244. ('Мария', 'Васильева', 'Великий Новгород, ул. Попова, д. 12', '+79110000004', 'vasilieva@mail.ru');
  245. INSERT INTO employees(last_name, first_name, phone, email, position) VALUES
  246. ('Иванова', 'Мария', '+79220000001', 'ivanova@shop.ru', 'Менеджер'),
  247. ('Соколов', 'Дмитрий', '+79220000002', 'sokolov@shop.ru', 'Администратор'),
  248. ('Фёдорова', 'Елена', '+79220000003', 'fedorova@shop.ru', 'Менеджер');
  249. INSERT INTO orders(order_date, status, client_id, employee_id) VALUES
  250. (NOW() - INTERVAL '12 days', 'в обработке', 1, 1),
  251. (NOW() - INTERVAL '10 days', 'отправлен', 1, 1),
  252. (NOW() - INTERVAL '3 days', 'новый', 2, 3),
  253. (NOW() - INTERVAL '15 days', 'выполнен', 2, 1),
  254. (NOW() - INTERVAL '20 days', 'в обработке', 3, 3),
  255. (NOW() - INTERVAL '4 days', 'отменён', 4, 1);
  256. INSERT INTO order_items(order_id, product_id, unit_price, quantity) VALUES
  257. (1, 1, 65000.00, 1),
  258. (1, 5, 1500.00, 2),
  259. (2, 3, 18000.00, 2),
  260. (3, 6, 1200.00, 1),
  261. (3, 7, 7200.00, 1),
  262. (4, 2, 72000.00, 1),
  263. (5, 8, 42000.00, 1),
  264. (5, 4, 26000.00, 1),
  265. (6, 5, 1500.00, 1);
  266. -- ============================================================
  267. -- ПОЛЬЗОВАТЕЛИ ПРИЛОЖЕНИЯ
  268. -- Роли PostgreSQL не создаются, так как в учебной БД это запрещено.
  269. -- Разграничение прав выполняется на уровне приложения через app_users.role_name.
  270. -- Пароли для проверки: admin123, manager123, viewer123.
  271. -- ============================================================
  272. DELETE FROM app_users;
  273. INSERT INTO app_users(login, password_hash, role_name) VALUES
  274. ('admin', '240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9', 'admin'),
  275. ('manager', '866485796cfa8d7c0cf7111640205b83076433547577511d81f8030ae99ecea5', 'manager'),
  276. ('viewer', '65375049b9e4d7cad6c9ba286fdeb9394b28135a3e84136404cfccfdcc438894', 'viewer');
  277. -- Проверочные запросы
  278. SELECT * FROM app_users;
  279. SELECT * FROM v_orders_full_info;
  280. SELECT * FROM v_late_orders;
  281. SELECT * FROM v_products_dictionary;
  282. SELECT * FROM fn_get_late_orders_by_client(1);