Загрузка данных
-- ============================================================
-- 1. БАЗОВЫЕ СПРАВОЧНИКИ
-- ============================================================
==((ПРАВКИ))
INSERT INTO Manufacturers (ManufacturerName, CountryID)
VALUES
(N'Benelli', 4),
(N'FN Herstal', 3),
(N'Accuracy International', 5);
INSERT INTO Weapons (ModelName, TypeID, ManufacturerID, CaliberID)
VALUES
(N'Glock-18', 1, 2, 2), -- Пистолет
(N'AWP', 2, 3, 1), -- Винтовка
(N'AK-47', 3, 1, 1), -- Автомат
(N'XM1014', 4, 4, 3), -- Дробовик
(N'M249', 5, 5, 1), -- Пулемет
(N'Drone Unit', 6, 6, 2), -- Дроны
(N'HE Grenade', 7, 7, 3); -- Граната
CREATE TABLE Characteristics (
ID INT IDENTITY(1,1) PRIMARY KEY,
WeaponID INT NOT NULL,
CharacteristicName NVARCHAR(100) NOT NULL,
CharacteristicValue NVARCHAR(100) NOT NULL,
CONSTRAINT FK_Characteristics_Weapon
FOREIGN KEY (WeaponID) REFERENCES Weapons(ID)
);
INSERT INTO Characteristics (WeaponID, CharacteristicName, CharacteristicValue)
SELECT w.ID, v.CharacteristicName, v.CharacteristicValue
FROM Weapons w
JOIN (VALUES
-- Glock-18
(N'Glock-18', N'Модель', N'Glock-18'),
(N'Glock-18', N'Длина (мм)', N'186'),
(N'Glock-18', N'Прицельная дальность (м)', N'20'),
(N'Glock-18', N'Масса (кг)', N'0.65'),
(N'Glock-18', N'Тип ствола', N'Нарезной'),
(N'Glock-18', N'Скорострельность (выстр/мин)', N'1200 (burst)'),
(N'Glock-18', N'Урон (тело)', N'30'),
(N'Glock-18', N'Режим огня', N'Одиночный/очереди по 3'),
-- AWP
(N'AWP', N'Модель', N'AWP'),
(N'AWP', N'Длина (мм)', N'1100'),
(N'AWP', N'Прицельная дальность (м)', N'2000+'),
(N'AWP', N'Масса (кг)', N'6.6'),
(N'AWP', N'Тип ствола', N'Нарезной'),
(N'AWP', N'Скорострельность (выстр/мин)', N'41'),
(N'AWP', N'Урон (тело)', N'115'),
(N'AWP', N'Режим огня', N'Одиночный'),
-- AK-47
(N'AK-47', N'Модель', N'AK-47'),
(N'AK-47', N'Длина (мм)', N'943'),
(N'AK-47', N'Прицельная дальность (м)', N'400'),
(N'AK-47', N'Масса (кг)', N'4.78'),
(N'AK-47', N'Тип ствола', N'Нарезной'),
(N'AK-47', N'Скорострельность (выстр/мин)', N'600'),
(N'AK-47', N'Урон (тело)', N'36'),
(N'AK-47', N'Режим огня', N'Автоматический/одиночный'),
-- XM1014
(N'XM1014', N'Модель', N'XM1014'),
(N'XM1014', N'Длина (мм)', N'1010'),
(N'XM1014', N'Прицельная дальность (м)', N'3'),
(N'XM1014', N'Масса (кг)', N'3.8'),
(N'XM1014', N'Тип ствола', N'Гладкоствольный'),
(N'XM1014', N'Скорострельность (выстр/мин)', N'171'),
(N'XM1014', N'Урон (тело)', N'20'),
(N'XM1014', N'Режим огня', N'Полуавтоматический'),
-- M249
(N'M249', N'Модель', N'M249'),
(N'M249', N'Длина (мм)', N'1030'),
(N'M249', N'Прицельная дальность (м)', N'1800'),
(N'M249', N'Масса (кг)', N'10'),
(N'M249', N'Тип ствола', N'Нарезной'),
(N'M249', N'Скорострельность (выстр/мин)', N'750'),
(N'M249', N'Урон (тело)', N'32'),
(N'M249', N'Режим огня', N'Автоматический'),
-- Drone Unit (осмысленные характеристики)
(N'Drone Unit', N'Модель', N'Drone Unit'),
(N'Drone Unit', N'Масса (кг)', N'0.8'),
(N'Drone Unit', N'Дальность связи (м)', N'500'),
(N'Drone Unit', N'Время полёта (мин)', N'15'),
(N'Drone Unit', N'Тип управления', N'Дистанционное'),
(N'Drone Unit', N'Полезная нагрузка', N'Есть'),
(N'Drone Unit', N'Режим полёта', N'Автономный/ручной'),
(N'Drone Unit', N'Скорость (км/ч)', N'60'),
-- HE Grenade (без "длины ствола" ??)
(N'HE Grenade', N'Модель', N'HE Grenade'),
(N'HE Grenade', N'Масса (кг)', N'0.4'),
(N'HE Grenade', N'Радиус поражения (м)', N'5'),
(N'HE Grenade', N'Урон (тело)', N'115 (взрыв)'),
(N'HE Grenade', N'Тип боеприпаса', N'Осколочно-фугасный'),
(N'HE Grenade', N'Время задержки (с)', N'3'),
(N'HE Grenade', N'Начальная скорость', N'Бросок рукой'),
(N'HE Grenade', N'Режим применения', N'Одноразовый')
) v(ModelName, CharacteristicName, CharacteristicValue)
ON w.ModelName = v.ModelName;
CREATE TABLE Countries (
ID INT IDENTITY(1,1) PRIMARY KEY,
CountryName NVARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE Positions (
ID INT IDENTITY(1,1) PRIMARY KEY,
PositionName NVARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE WeaponTypes (
ID INT IDENTITY(1,1) PRIMARY KEY,
TypeName NVARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE Calibers (
ID INT IDENTITY(1,1) PRIMARY KEY,
CaliberName NVARCHAR(20) NOT NULL UNIQUE
);
-- ============================================================
-- 2. СУЩНОСТИ 2-ГО УРОВНЯ
-- ============================================================
CREATE TABLE Manufacturers (
ID INT IDENTITY(1,1) PRIMARY KEY,
ManufacturerName NVARCHAR(100) NOT NULL,
CountryID INT NOT NULL,
CONSTRAINT FK_Manufacturers_Country FOREIGN KEY (CountryID) REFERENCES Countries(ID)
);
CREATE TABLE Suppliers (
ID INT IDENTITY(1,1) PRIMARY KEY,
SupplierName NVARCHAR(100) NOT NULL,
CountryID INT NOT NULL,
ContactPhone NVARCHAR(20) NOT NULL,
CONSTRAINT FK_Suppliers_Country FOREIGN KEY (CountryID) REFERENCES Countries(ID)
);
CREATE TABLE Employees (
ID INT IDENTITY(1,1) PRIMARY KEY,
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NOT NULL,
PositionID INT NOT NULL,
EmployeeNumber NVARCHAR(20) NOT NULL UNIQUE,
HireDate DATE NOT NULL,
CONSTRAINT FK_Employees_Position FOREIGN KEY (PositionID) REFERENCES Positions(ID)
);
-- ============================================================
-- 3. ОСНОВНЫЕ СУЩНОСТИ И СКЛАД
-- ============================================================
CREATE TABLE Weapons (
ID INT IDENTITY(1,1) PRIMARY KEY,
ModelName NVARCHAR(100) NOT NULL,
TypeID INT NOT NULL,
ManufacturerID INT NOT NULL,
CaliberID INT NOT NULL,
CONSTRAINT FK_Weapons_Type FOREIGN KEY (TypeID) REFERENCES WeaponTypes(ID),
CONSTRAINT FK_Weapons_Manufacturer FOREIGN KEY (ManufacturerID) REFERENCES Manufacturers(ID),
CONSTRAINT FK_Weapons_Caliber FOREIGN KEY (CaliberID) REFERENCES Calibers(ID)
);
-- ИСПРАВЛЕНИЕ 1: добавлен суррогатный первичный ключ ID,
-- WeaponID сохранён как UNIQUE NOT NULL (гарантирует 1:1 с Weapons)
CREATE TABLE Stock (
ID INT IDENTITY(1,1) PRIMARY KEY, -- добавлен ID
WeaponID INT NOT NULL UNIQUE, -- уникальность сохранена
Quantity INT NOT NULL DEFAULT 0 CHECK (Quantity >= 0),
CONSTRAINT FK_Stock_Weapon FOREIGN KEY (WeaponID) REFERENCES Weapons(ID)
);
CREATE TABLE PriceHistory (
ID INT IDENTITY(1,1) PRIMARY KEY,
WeaponID INT NOT NULL,
Price DECIMAL(10,2) NOT NULL CHECK (Price > 0),
EffectiveDate DATE NOT NULL,
CONSTRAINT FK_PriceHistory_Weapon FOREIGN KEY (WeaponID) REFERENCES Weapons(ID)
);
-- Вьюшка для получения актуальной цены (последняя запись по дате)
GO
CREATE VIEW vw_CurrentPrice AS
SELECT w.ID AS WeaponID, w.ModelName, ph.Price, ph.EffectiveDate
FROM Weapons w
CROSS APPLY (
SELECT TOP 1 Price, EffectiveDate
FROM PriceHistory
WHERE WeaponID = w.ID
ORDER BY EffectiveDate DESC
) ph;
GO
CREATE TABLE Clients (
ID INT IDENTITY(1,1) PRIMARY KEY,
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NOT NULL,
PassportNumber NVARCHAR(20) NOT NULL UNIQUE,
Phone NVARCHAR(20) NOT NULL
);
CREATE TABLE Licenses (
ID INT IDENTITY(1,1) PRIMARY KEY,
ClientID INT NOT NULL,
LicenseNumber NVARCHAR(30) NOT NULL UNIQUE,
IssueDate DATE NOT NULL,
ExpiryDate DATE NOT NULL,
CONSTRAINT FK_Licenses_Client FOREIGN KEY (ClientID) REFERENCES Clients(ID),
CONSTRAINT CHK_LicenseDates CHECK (ExpiryDate > IssueDate)
);
-- ============================================================
-- 4. ОПЕРАЦИИ: ПОСТАВКИ
-- ============================================================
CREATE TABLE Deliveries (
ID INT IDENTITY(1,1) PRIMARY KEY,
SupplierID INT NOT NULL,
EmployeeID INT NOT NULL,
DeliveryDate DATE NOT NULL,
CONSTRAINT FK_Deliveries_Supplier FOREIGN KEY (SupplierID) REFERENCES Suppliers(ID),
CONSTRAINT FK_Deliveries_Employee FOREIGN KEY (EmployeeID) REFERENCES Employees(ID)
);
CREATE TABLE DeliveryDetails (
ID INT IDENTITY(1,1) PRIMARY KEY,
DeliveryID INT NOT NULL,
WeaponID INT NOT NULL,
Quantity INT NOT NULL CHECK (Quantity > 0),
UnitPrice DECIMAL(10,2) NOT NULL CHECK (UnitPrice > 0),
CONSTRAINT FK_DeliveryDetails_Delivery FOREIGN KEY (DeliveryID) REFERENCES Deliveries(ID) ON DELETE CASCADE,
CONSTRAINT FK_DeliveryDetails_Weapon FOREIGN KEY (WeaponID) REFERENCES Weapons(ID)
);
-- ============================================================
-- 5. ОПЕРАЦИИ: ПРОДАЖИ
-- ============================================================
-- ИСПРАВЛЕНИЕ 2: добавлен тип INT для EmployeeID (был синтаксическая ошибка)
CREATE TABLE Sales (
ID INT IDENTITY(1,1) PRIMARY KEY,
ClientID INT NOT NULL,
LicenseID INT NOT NULL,
EmployeeID INT NOT NULL, -- исправлено: добавлен тип данных INT
SaleDate DATE NOT NULL,
CONSTRAINT FK_Sales_Client FOREIGN KEY (ClientID) REFERENCES Clients(ID),
CONSTRAINT FK_Sales_License FOREIGN KEY (LicenseID) REFERENCES Licenses(ID),
CONSTRAINT FK_Sales_Employee FOREIGN KEY (EmployeeID) REFERENCES Employees(ID)
);
CREATE TABLE SaleDetails (
ID INT IDENTITY(1,1) PRIMARY KEY,
SaleID INT NOT NULL,
WeaponID INT NOT NULL,
Quantity INT NOT NULL CHECK (Quantity > 0),
SalePrice DECIMAL(10,2) NOT NULL CHECK (SalePrice > 0),
CONSTRAINT FK_SaleDetails_Sale FOREIGN KEY (SaleID) REFERENCES Sales(ID) ON DELETE CASCADE,
CONSTRAINT FK_SaleDetails_Weapon FOREIGN KEY (WeaponID) REFERENCES Weapons(ID)
);
GO
-- ============================================================
-- ИНДЕКСЫ НА ВНЕШНИЕ КЛЮЧИ
-- (SQL Server не создаёт их автоматически, в отличие от MySQL)
-- ============================================================
CREATE INDEX IX_Manufacturers_CountryID ON Manufacturers(CountryID);
CREATE INDEX IX_Suppliers_CountryID ON Suppliers(CountryID);
CREATE INDEX IX_Employees_PositionID ON Employees(PositionID);
CREATE INDEX IX_Weapons_TypeID ON Weapons(TypeID);
CREATE INDEX IX_Weapons_ManufacturerID ON Weapons(ManufacturerID);
CREATE INDEX IX_Weapons_CaliberID ON Weapons(CaliberID);
CREATE INDEX IX_PriceHistory_WeaponID ON PriceHistory(WeaponID);
CREATE INDEX IX_Licenses_ClientID ON Licenses(ClientID);
CREATE INDEX IX_Deliveries_SupplierID ON Deliveries(SupplierID);
CREATE INDEX IX_Deliveries_EmployeeID ON Deliveries(EmployeeID);
CREATE INDEX IX_DeliveryDetails_DeliveryID ON DeliveryDetails(DeliveryID);
CREATE INDEX IX_DeliveryDetails_WeaponID ON DeliveryDetails(WeaponID);
CREATE INDEX IX_Sales_ClientID ON Sales(ClientID);
CREATE INDEX IX_Sales_LicenseID ON Sales(LicenseID);
CREATE INDEX IX_Sales_EmployeeID ON Sales(EmployeeID);
CREATE INDEX IX_SaleDetails_SaleID ON SaleDetails(SaleID);
CREATE INDEX IX_SaleDetails_WeaponID ON SaleDetails(WeaponID);
GO
-- ============================================================
-- ТРИГГЕРЫ ДЛЯ АВТОМАТИЗАЦИИ СКЛАДА (STOCK)
-- ============================================================
-- Триггер 1: приход товара — увеличивает остаток в Stock
CREATE TRIGGER trg_DeliveryDetails_UpdateStock
ON DeliveryDetails
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
-- Обновляем существующие записи
UPDATE s
SET s.Quantity = s.Quantity + i.Quantity
FROM Stock s
JOIN inserted i ON s.WeaponID = i.WeaponID;
-- Добавляем новые записи для оружия, которого ещё не было на складе
INSERT INTO Stock (WeaponID, Quantity)
SELECT i.WeaponID, i.Quantity
FROM inserted i
WHERE NOT EXISTS (SELECT 1 FROM Stock s WHERE s.WeaponID = i.WeaponID);
END;
GO
-- Триггер 2: продажа товара — проверяет наличие и уменьшает остаток
CREATE TRIGGER trg_SaleDetails_UpdateStock
ON SaleDetails
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
-- Проверка: достаточно ли товара на складе?
IF EXISTS (
SELECT 1
FROM inserted i
LEFT JOIN Stock s ON i.WeaponID = s.WeaponID
WHERE ISNULL(s.Quantity, 0) < i.Quantity
)
BEGIN
-- ИСПРАВЛЕНИЕ 3: THROW вместо устаревшего RAISERROR
THROW 50001, N'Ошибка: недостаточно товара на складе для совершения продажи.', 1;
END;
-- Списываем количество
UPDATE s
SET s.Quantity = s.Quantity - i.Quantity
FROM Stock s
JOIN inserted i ON s.WeaponID = i.WeaponID;
END;
GO
-- ============================================================
-- ТРИГГЕР: ПРОВЕРКА ПРИНАДЛЕЖНОСТИ ЛИЦЕНЗИИ КЛИЕНТУ
-- ИСПРАВЛЕНИЕ 4: лицензия должна принадлежать клиенту из той же продажи
-- ============================================================
CREATE TRIGGER trg_Sales_CheckLicenseOwner
ON Sales
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT 1
FROM inserted i
JOIN Licenses l ON i.LicenseID = l.ID
WHERE l.ClientID <> i.ClientID
)
BEGIN
THROW 50002, N'Ошибка: лицензия не принадлежит указанному клиенту.', 1;
END;
END;
GO
-- ============================================================
-- ТРИГГЕР: ПРОВЕРКА СРОКА ДЕЙСТВИЯ ЛИЦЕНЗИИ ПРИ ПРОДАЖЕ
-- ИСПРАВЛЕНИЕ 5: нельзя продавать по просроченной лицензии
-- ============================================================
CREATE TRIGGER trg_Sales_CheckLicenseExpiry
ON Sales
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT 1
FROM inserted i
JOIN Licenses l ON i.LicenseID = l.ID
WHERE l.ExpiryDate < i.SaleDate
)
BEGIN
THROW 50003, N'Ошибка: срок действия лицензии истёк.', 1;
END;
END;
GO
-- ============================================================
-- ЗАПОЛНЕНИЕ ТЕСТОВЫМИ ДАННЫМИ
-- ============================================================
-- Справочники
INSERT INTO Countries (CountryName) VALUES ('Россия'), ('Австрия'), ('США'), ('Италия'), ('Германия');
INSERT INTO Positions (PositionName) VALUES ('Продавец-консультант'), ('Кладовщик');
INSERT INTO WeaponTypes (TypeName) VALUES ('Пистолет'), ('Винтовка'), ('Автомат');
INSERT INTO Calibers (CaliberName) VALUES ('5.45x39'), ('9x19'), ('12/76');
-- Производители
INSERT INTO Manufacturers (ManufacturerName, CountryID) VALUES
('Kalashnikov Concern', 1),
('Glock GmbH', 2);
-- Поставщики и сотрудники
INSERT INTO Suppliers (SupplierName, CountryID, ContactPhone) VALUES ('АрмТорг', 1, '+7-495-111');
INSERT INTO Employees (FirstName, LastName, PositionID, EmployeeNumber, HireDate)
VALUES ('Иван', 'Петров', 1, 'EMP-001', '2020-03-15');
-- Оружие
INSERT INTO Weapons (ModelName, TypeID, ManufacturerID, CaliberID) VALUES
('АК-74', 3, 1, 1),
('Glock 17', 1, 2, 2);
-- История цен
INSERT INTO PriceHistory (WeaponID, Price, EffectiveDate) VALUES
(1, 30000.00, '2023-01-01'),
(1, 32000.00, '2024-01-01'),
(2, 22000.00, '2023-01-01'),
(2, 25000.00, '2024-01-01');
-- Клиенты
INSERT INTO Clients (FirstName, LastName, PassportNumber, Phone)
VALUES ('Андрей', 'Волков', 'MD123456', '+373-69');
INSERT INTO Licenses (ClientID, LicenseNumber, IssueDate, ExpiryDate)
VALUES (1, 'ЛИЦ-001', '2023-01-15', '2026-01-15');
-- Поставка (триггер автоматически обновит Stock)
INSERT INTO Deliveries (SupplierID, EmployeeID, DeliveryDate) VALUES (1, 1, '2024-01-10');
INSERT INTO DeliveryDetails (DeliveryID, WeaponID, Quantity, UnitPrice) VALUES
(1, 1, 10, 32000.00), -- 10 АК-74
(1, 2, 5, 25000.00); -- 5 Glock 17
-- Продажа (триггер проверит остаток, принадлежность лицензии и её срок)
INSERT INTO Sales (ClientID, LicenseID, EmployeeID, SaleDate) VALUES (1, 1, 1, '2024-02-01');
INSERT INTO SaleDetails (SaleID, WeaponID, Quantity, SalePrice) VALUES
(1, 1, 2, 45000.00); -- 2 АК-74 (останется 8)
-- ============================================================
-- ПРОВЕРОЧНЫЕ ЗАПРОСЫ
-- ============================================================
-- Текущие остатки на складе
SELECT w.ModelName, s.Quantity
FROM Stock s
JOIN Weapons w ON s.WeaponID = w.ID;
-- Актуальные цены (через вьюшку)
SELECT * FROM vw_CurrentPrice;
-- Продажи с данными клиента и лицензии
SELECT
sa.ID AS SaleID,
sa.SaleDate,
c.FirstName + ' ' + c.LastName AS Client,
l.LicenseNumber,
l.ExpiryDate,
w.ModelName,
sd.Quantity,
sd.SalePrice
FROM Sales sa
JOIN Clients c ON sa.ClientID = c.ID
JOIN Licenses l ON sa.LicenseID = l.ID
JOIN SaleDetails sd ON sa.ID = sd.SaleID
JOIN Weapons w ON sd.WeaponID = w.ID;