У меня есть проект на основе баз данных (Oracle) по созданию магазина проката автомобилей. До сих пор все шло хорошо, но я застрял на триггере, который должен проверять клиента, а также автомобиль между арендой и датой возврата, так что клиент, который уже арендовал автомобиль, не может арендовать другой, а другой клиент не может арендовать уже арендованный автомобиль.
Аренда стола:
CREATE TABLE RentAuto
(
auto_id INT,
customer_id INT,
employee_id INT,
date_rent DATE,
date_return DATE,
rent_days VARCHAR2(5)
);
Таблица автомобилей:
CREATE TABLE Automobile
(
auto_id INTEGER NOT NULL,
year_of_production VARCHAR(10),
auto_current_km VARCHAR(20),
price_per_day VARCHAR(20),
color_id INT,
is_auto_av INT,
model_id INT,
PRIMARY KEY(auto_id)
);
Внесите в таблицу:
INSERT INTO RentAuto (auto_id,customer_id,employee_id,date_rent,date_return, rent_days)
VALUES(14,13,3,TO_DATE(sysdate, 'dd-mm-yyyy'), '29-03-2022','2');
Триггер, который я написал ниже, выдает ошибку в операторе IF, и я не могу понять, как ее исправить. (PLS-00201: должен быть объявлен идентификатор NEW.CUSTOMER_ID.)
create or replace TRIGGER RENTINGTRIGGER
BEFORE INSERT OR UPDATE OF AUTO_ID,CUSTOMER_ID,DATE_RENT,DATE_RETURN ON RENTAUTO
FOR EACH ROW
BEGIN
IF RENTAUTO.CUSTOMER_ID = :NEW.CUSTOMER_ID
and ((new.DATE_RENT >= RentAuto.DATE_RETURN and new.DATE_RENT < RentAuto.DATE_RETURN)
or (new.DATE_RETURN > RentAuto.DATE_RENT and new.DATE_RETURN < RentAuto.DATE_RETURN))
THEN
RAISE_APPLICATION_ERROR(-2099, 'You can only book one car per single customer a day');
IF RentAuto.AUTO_ID = :NEW.AUTO_ID
and
((new.date_rent >= RentAuto.date_return and new.date_rent < RentAuto.date_return)
or (new.date_return > RentAuto.date_rent and new.date_return < RentAuto.date_return))
THEN
RAISE_APPLICATION_ERROR(-2099, 'Car has already been rented by another customer!');
END IF;
END IF;
END;
@Dai Я знаю, что реализовывать это как триггер - плохая практика, но я должен сделать это с помощью триггера (так сказал человек, который передал нам проект)
В некоторых местах вам не хватает двоеточия перед new
. А в IF RENTAUTO.CUSTOMER_ID
какой ряд должен быть RENTAUTO
? В триггере есть строка :new и строка :old, вот и все.
@Thorsten Kettner Ну, в основном это попытка сравнить клиента, чей идентификатор находится в RentAuto (таблица), и того, который был недавно вставлен. Я просто не знаю, как это сделать.
Здесь вам нужны запросы для поиска перекрывающихся строк в таблице. Проблема с этим: таблица, которую вы хотите запросить, находится в процессе изменения. Самый простой способ решить эту проблему — триггер оператора after. Если вы хотите, чтобы это было более сложно, вам нужен составной триггер.
@ThorstenKettner Да, мне никогда раньше не приходилось реализовывать составной триггер, но я проверю его, большое спасибо!
У вас есть несколько столбцов, которые определены как VARCHAR. В оракуле они должны быть VARCHAR2. Также у вас есть несколько столбцов, которые явно являются числовыми данными, но вы определили их как VARCHAR — year_of_production, auto_current_km, price_per_day, rent_days. И наоборот, у вас есть некоторые столбцы, которые определены как INT, но характер данных не является числовым, даже если по стандарту действительные данные содержат только числовые СИМВОЛЫ — customer_id, employee_id. Эмпирическое правило - если вы не будете заниматься математикой, это не число. Нет необходимости в арендных_днях, так как их всегда можно вычислить из даты_возврата — даты_ренты.
@EdStevens Я все изменил, как вы упомянули, большое спасибо! Теперь он полностью работает с новыми значениями столбца, я также установил контрольное ограничение для RentAuto, чтобы дата возврата не могла быть ниже даты аренды: ALTER TABLE RentAuto ADD CONSTRAINT CHECK_PERIOD CHECK (date_rent IS NULL OR date_return > date_rent);
Вот простой триггер оператора after, который создает исключение, когда вставка или обновление приводит к тому, что клиент бронирует более одного автомобиля в день или автомобиль бронируется более одного раза в день.
Если оператор вставки или обновления находится более чем в одной строке, триггер не сообщает нам, какое резервирование привело к сбою. Кроме того, каждый раз, когда происходит вставка или обновление, мы должны сканировать всю таблицу на наличие конфликтов. Если вы хотите реагировать только на вставляемые или обновляемые строки, вам нужен составной триггер, в котором вы запоминаете рассматриваемые строки в части «после каждой строки», а затем сравниваете их с другими строками в таблице в часть «после утверждения».
CREATE OR REPLACE TRIGGER rentingtrigger
AFTER INSERT OR UPDATE OF auto_id, customer_id, date_rent, date_return ON rentauto
v_count INTEGER;
BEGIN
select count(*)
into v_count
from rentauto
where exists
(
select null
from rentauto other
where other.customer_id = rentauto.customer_id
and other.date_rent <= rentauto.date_return
and other.date_return >= rentauto.date_rent
and other.rowid <> rentauto.rowid
);
IF v_count > 0 THEN
RAISE_APPLICATION_ERROR(-20099, 'You can only book one car per single customer a day');
END IF;
select count(*)
into v_count
from rentauto
where exists
(
select null
from rentauto other
where other.auto_id = rentauto.auto_id
and other.date_rent <= rentauto.date_return
and other.date_return >= rentauto.date_rent
and other.rowid <> rentauto.rowid
);
IF v_count > 0 THEN
RAISE_APPLICATION_ERROR(-20099, 'Car has already been rented by another customer!');
END IF;
END rentingtrigger;
Почему вы используете триггер для реализации этого вместо контрольного ограничения? Также может быть возможно реализовать использование только уникального ограничения.