Загрузка данных


CREATE OR ALTER PROCEDURE sp_issue_book
    @book_id INT,
    @reader_id INT
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Проверка существования книги
    IF NOT EXISTS (SELECT 1 FROM Books WHERE book_id = @book_id)
    BEGIN
        RAISERROR('Ошибка: Книга с ID %d не найдена.', 16, 1, @book_id);
        RETURN;
    END

    -- Проверка существования читателя
    IF NOT EXISTS (SELECT 1 FROM Readers WHERE reader_id = @reader_id)
    BEGIN
        RAISERROR('Ошибка: Читатель с ID %d не найден.', 16, 1, @reader_id);
        RETURN;
    END

    -- Проверка наличия свободных экземпляров
    DECLARE @available_copies INT;
    SELECT @available_copies = copies_count FROM Books WHERE book_id = @book_id;

    IF @available_copies <= 0
    BEGIN
        RAISERROR('Ошибка: У книги (ID: %d) нет доступных экземпляров для выдачи.', 16, 1, @book_id);
        RETURN;
    END

    -- Выполнение транзакции выдачи книги
    BEGIN TRANSACTION;
    BEGIN TRY
        -- 1. Создание записи в таблице Loans
        INSERT INTO Loans (book_id, reader_id, loan_date, return_date)
        VALUES (@book_id, @reader_id, GETDATE(), NULL);

        -- 2. Уменьшение количества экземпляров в Books
        UPDATE Books
        SET copies_count = copies_count - 1
        WHERE book_id = @book_id;

        COMMIT TRANSACTION;
        PRINT 'Успех: Книга успешно выдана читателю.';
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
        DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
        RAISERROR(@ErrorMessage, 16, 1);
    END CATCH
END;
GO
CREATE OR ALTER TRIGGER trg_check_loan_dates
ON Loans
AFTER INSERT, UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    -- Проверка: если дата возврата указана и она раньше даты выдачи
    IF EXISTS (
        SELECT 1 
        FROM inserted 
        WHERE return_date IS NOT NULL 
          AND return_date < loan_date
    )
    BEGIN
        RAISERROR('Ошибка валидации: Дата возврата книги не может быть раньше даты выдачи!', 16, 1);
        ROLLBACK TRANSACTION;
        RETURN;
    END
END;
GO
-- Индекс для быстрого поиска по ФИО читателя
CREATE NONCLUSTERED INDEX IX_Readers_FullName 
ON Readers(full_name);

-- Индекс для быстрого поиска по названию книги
CREATE NONCLUSTERED INDEX IX_Books_Title 
ON Books(title);

-- Составной индекс по датам выдачи и возврата для оптимизации аналитических запросов
CREATE NONCLUSTERED INDEX IX_Loans_Dates 
ON Loans(loan_date, return_date);
GO
-- А. Тест: Успешная выдача книги (у книги есть экземпляры)
EXEC sp_issue_book @book_id = 1, @reader_id = 2;

-- Проверяем, что создалась запись в Loans и уменьшилось количество в Books
SELECT * FROM Loans WHERE book_id = 1 AND reader_id = 2;
SELECT book_id, title, copies_count FROM Books WHERE book_id = 1;

-- Б. Тест: Попытка выдать книгу с 0 экземплярами
-- Обнуляем экземпляры книги для проверки
UPDATE Books SET copies_count = 0 WHERE book_id = 5;

-- Вызов процедуры должен завершиться ошибкой
EXEC sp_issue_book @book_id = 5, @reader_id = 1;
-- Тест: Попытка обновить дату возврата на дату, которая раньше даты выдачи
-- (Ожидается срабатывание ошибки из триггера и отмена операции)
UPDATE Loans 
SET return_date = '2020-01-01' 
WHERE loan_id = 1;