SQL: триггер, чтобы предотвратить вставку строки в таблицу на основе условия - PullRequest
0 голосов
/ 25 октября 2019

У меня есть следующие таблицы:

CREATE TABLE review 
(
    review_id NUMBER(2) NOT NULL,
    review_date DATE NOT NULL,
    review_rating NUMBER(1) NOT NULL,
    driver_no NUMBER(2) NOT NULL,
    vehicle_id NUMBER(3) NOT NULL
);

CREATE TABLE testing 
(
    testing_id NUMBER(2) NOT NULL,
    testing_start DATE NOT NULL,
    testing_end DATE NOT NULL
    driver_no NUMBER(2) NOT NULL,
    vehicle_id NUMBER(3) NOT NULL
);

В основном автомобили проверяются водителями между двумя датами. После того, как тестирование закончено, водитель рассматривает транспортное средство.

Я хочу создать триггер, который предотвратит добавление неверного обзора. Проверка считается недействительной, если водитель проверяет транспортное средство до даты окончания испытания. Проверка также недействительна, если водитель проверяет транспортное средство, которое он не вел.

Например, водитель 1 испытывает транспортное средство 7 с 01 февраля 2019 года по 07 февраля 2019 года. Если проверка добавлена ​​на 5 февраля 2019 года,Я хочу, чтобы триггер не вставлял это. Кроме того, если для автомобиля 5 добавлен обзор (когда автомобиль 7 тестировался), я хочу, чтобы триггер не вставлял его.

Это то, что у меня есть:

CREATE OR REPLACE TRIGGER review_check_validity
AFTER INSERT ON review
FOR EACH ROW
BEGIN
    SELECT testing_start
    FROM testing
    WHERE driver_no = :new.driver_no;

    SELECT vehicle_id
    FROM testing
    WHERE driver_no = :new.driver_no;

    IF :new.review_date < testing_end THEN
    raise_application_error(-20000, 'Review date cannot be before 
    testing end date');

    END IF;

    IF :new.vehicle_id != vehicle_id THEN
    raise_application_error(-20000, 'Driver has never driven this 
    vehicle');

    END IF;
END;
/

Триггер компилируется без каких-либо ошибок - однако, когда я пытаюсь проверить его, вставив неверную строку в таблицу REVIEW, я получаю сообщение об ошибке, указывающее

Точная выборка возвращает больше, чем запрошеноколичество строк

Может ли кто-нибудь указать, какие изменения необходимо внести в мой код для достижения желаемого результата?

Ответы [ 3 ]

1 голос
/ 25 октября 2019

Я бы изменил здесь логику и:

  1. Проверьте, проверил ли водитель автомобиль, затем
  2. Проверьте, предпринята ли попытка проверки до даты окончания тестирования для транспортного средства (что-то, что вы пропустили).

В Oracle PL / SQL, который включает в себя код триггера, вы не можете просто SELECT. Вы должны SELECT INTO переменная. Затем вы можете использовать переменную в своей логике.

Не менее важно, когда вы SELECT INTO переменная, запрос может вернуть только один результат. Несколько строк приведут к ошибке, с которой вы столкнулись.

CREATE OR REPLACE TRIGGER review_check_validity
AFTER INSERT ON review
FOR EACH ROW
DECLARE
    testEnd DATE;
    vehicleTestCount NUMBER;
BEGIN

    SELECT COUNT(*)
      INTO vehicleTestCount
      FROM testing
      WHERE vehicle_id = :new.vehicle_id;

    IF vehicleTestCount = 0 THEN
      raise_application_error(-20000, 'Driver has never driven this vehicle');
    END IF;

    -- Assumes one test per driver per vehicle
    SELECT testing_end
      INTO testEnd
      FROM testing
      WHERE driver_no = :new.driver_no
        AND vehicle_id = :new.vehicle_id;

    IF :new.review_date < testEnd THEN
      raise_application_error(-20000, 'Review date cannot be before 
    testing end date');

    END IF;

END;
/

Наконец, ваша структура таблицы позволяет проводить несколько тестов одного и того же транспортного средства одним и тем же водителем. Если это должно разрешить это, то таблица review должна связываться с таблицей testing на testing_id, а не driver_no и vehicle_id.

0 голосов
/ 25 октября 2019

В вашем коде есть несколько ошибок:

  • В PL / SQL-запросах выбора должно быть предложение INTO, где данные, извлеченные из запроса SELECT, сохраняются в некоторых переменных,- Эта часть отсутствует в вашем коде

  • Вы ищете в таблице тестирования только driverno, который даст вам все записи этого driverno (всетранспортные средства, проверенные этим driveno). Вам нужно включить vehicleid вместе с driverno, чтобы получить подробную информацию о тестировании, имеющую отношение к текущему обзору.

Итак, ваш триггерный код может быть переписан следующим образом:

CREATE OR REPLACE TRIGGER REVIEW_CHECK_VALIDITY 
BEFORE INSERT ON REVIEW -- USING BEFORE INSERT TRIGGER TO AVOID ANY UNDOs
    FOR EACH ROW
DECLARE
    LV_TEST_END_DATE   DATE;
    LV_TEST_COUNT      NUMBER;
BEGIN
    -- FETCHING RELEVANT DATA USING SINGLE QUERY
    SELECT
        COUNT(1),
        MAX(TESTING_END) -- PLEASE HANDLE THE SCENARIO WHERE THE TESTING END DATE IS NULL
    INTO
        LV_TEST_COUNT,
        LV_TEST_END_DATE
    FROM
        TESTING
    WHERE
        VEHICLE_ID = :NEW.VEHICLE_ID
        AND DRIVER_NO = :NEW.DRIVER_NO;

    IF LV_TEST_COUNT = 0 THEN
        RAISE_APPLICATION_ERROR(-20000, 'Driver has never driven this vehicle');
    ELSIF :NEW.REVIEW_DATE < LV_TEST_END_DATE THEN
        RAISE_APPLICATION_ERROR(-20000, 'Review date cannot be before testing end date');
    END IF;

END;
/

Cheers !!

0 голосов
/ 25 октября 2019

Я думаю, что вы должны иметь предложение INTO в своих выборках SELECT. Я добавил несколько примечаний в ваш запрос, чтобы попытаться помочь вам очистить его.

CREATE OR REPLACE TRIGGER review_check_validity
AFTER INSERT ON review
FOR EACH ROW

-- Need to declare variables for usage in your SELECT fetches.
DECLARE
var_revdate number; -- or date, if applicable
var_revvehi number; -- or varchar(n), if applicable

BEGIN
    -- In here you should have an INTO clause to assign your date parameter into 
    -- the predefined variable. As well, you need to ensure this fetch will provide
    -- only one row.

    SELECT testing_start INTO var_revdate -- why are you fetching "testing_start"?
        FROM testing
            WHERE driver_no = :new.driver_no;

    -- Same case, it can only retrieve one row. If you need to do more than one row, 
    -- you may need to use a BULK & a LOOP.

    SELECT vehicle_id INTO var_vehicid
        FROM testing
            WHERE driver_no = :new.driver_no;

    IF :new.review_date < testing_end THEN
    raise_application_error(-20000, 'Review date cannot be before testing end date');

    END IF;

    IF :new.vehicle_id != var_vehicid THEN
    raise_application_error(-20000, 'Driver has never driven this vehicle');

    END IF;
END;
/

Надеюсь, это поможет.

...