Home > Software engineering >  SQL trigger not not allowing to insert value
SQL trigger not not allowing to insert value

Time:10-04

So, I am creating a database using Oracle and this is the problem I ran into. I have this table:

CREATE TABLE T_SCHEDULE
(
    sched_id number(4) NOT NULL,
    master_id number(10) REFERENCES T_MASTER(master_id) ON DELETE CASCADE NOT NULL,
    client_id number(10) REFERENCES T_CLIENT(client_id) ON DELETE CASCADE NOT NULL,
    box_num number(5) REFERENCES T_BOX(box_num) ON DELETE CASCADE NOT NULL,
    car_num varchar2(10) REFERENCES T_CAR(car_num) ON DELETE CASCADE NOT NULL,
    price number(7) NOT NULL,
    job_start timestamp NOT NULL,
    job_stop timestamp NOT NULL,
    PRIMARY KEY(sched_id)
);

And this trigger:

CREATE OR REPLACE TRIGGER HOURS_A_DAY
    BEFORE INSERT OR UPDATE ON T_SCHEDULE
    FOR EACH ROW
    DECLARE
    v_count number := 0;
    BEGIN
        SELECT COUNT(*)
        INTO v_count
        FROM (SELECT SUM(TO_NUMBER(TRUNC(TO_DATE(job_stop - job_start), 'MI'))) daily
            FROM T_SCHEDULE
            GROUP BY TRUNC(job_start, 'DD'), master_id)
        WHERE daily > 480;
        IF v_count <> 0 THEN
            ROLLBACK;
        END IF;
    END;

(The table has references to other tables, but I don't think it's relevant to the problem). This are the values I am inserting:

INSERT INTO T_SCHEDULE VALUES (4001, 1001, 2002, 3002, 'Р232ХВ', 20000, TIMESTAMP '2021-10-02 12:30:00.0', TIMESTAMP '2021-10-02 17:30:00.0');
INSERT INTO T_SCHEDULE VALUES (4002, 1002, 2003, 3003, 'А847КР', 8000, TIMESTAMP '2021-10-02 08:15:00.0', TIMESTAMP '2021-10-02 12:15:00.0');
INSERT INTO T_SCHEDULE VALUES (4003, 1003, 2004, 3005, 'С966ЕС', 5000, TIMESTAMP '2021-10-02 10:45:00.0', TIMESTAMP '2021-10-02 13:45:00.0');
INSERT INTO T_SCHEDULE VALUES (4004, 1004, 2005, 3001, 'Т138УВ', 10000, TIMESTAMP '2021-10-02 10:30:00.0', TIMESTAMP '2021-10-02 15:30:00.0');
INSERT INTO T_SCHEDULE VALUES (4005, 1005, 2006, 3006, 'Р364ВЕ', 15000, TIMESTAMP '2021-10-02 09:00:00.0', TIMESTAMP '2021-10-02 11:00:00.0');
INSERT INTO T_SCHEDULE VALUES (4006, 1001, 2007, 3005, 'О117УУ', 7000, TIMESTAMP '2021-10-03 14:10:00.0', TIMESTAMP '2021-10-02 17:10:00.0');
INSERT INTO T_SCHEDULE VALUES (4007, 1002, 2008, 3002, 'Н439АМ', 30000, TIMESTAMP '2021-10-03 10:40:00.0', TIMESTAMP '2021-10-03 15:40:00.0');
INSERT INTO T_SCHEDULE VALUES (4008, 1003, 2009, 3003, 'О896МТ', 4000, TIMESTAMP '2021-10-02 14:30:00.0', TIMESTAMP '2021-10-02 18:30:00.0');
INSERT INTO T_SCHEDULE VALUES (4009, 1004, 2010, 3004, 'Т694КС', 12000, TIMESTAMP '2021-10-03 09:50:00.0', TIMESTAMP '2021-10-03 17:50:00.0');
INSERT INTO T_SCHEDULE VALUES (4010, 1005, 2001, 3001, 'У601КК', 9000, TIMESTAMP '2021-10-02 16:00:00.0', TIMESTAMP '2021-10-02 20:00:00.0');

When I run this, the first row is inserted, however all the others receive ORA-01847 (day of month must be between 1 and last day of month), ORA-06512 (at "SYSTEM.HOURS_A_DAY", line 4) and ORA-04088 (error during execution of trigger 'SYSTEM.HOURS_A_DAY') errors. The main one I think is ORA-01847. All of this to me is weird, since I inserted the same values in the same table, just without the trigger and everything was fine. So I am guessing my trigger affects it. How can I fix it?

CodePudding user response:

The problem is in SUM(TO_NUMBER(TRUNC(TO_DATE(job_stop - job_start), 'MI'))). job_stop - job_start, a calculation involving two TIMESTAMPs, returns a INTERVAL rather than a DATE or a NUMBER. As such, you need to extract the days, hours, minutes, and seconds from the resulting interval, adjusting each to a value in seconds.

You've also got a couple other potential issues:

  1. You're selecting from T_SCHEDULE in a trigger defined on T_SCHEDULE. This is likely to cause an ORA-04091 "Table T_SCHEDULE is mutating; trigger cannot see it" error. The solution is to define this as a statement trigger rather than a row trigger, by simply removing the FOR EACH ROW line. In this case this should work, but in many cases it's not a viable solution and other solutions will have to be tried.

  2. You're trying to perform a ROLLBACK in a trigger. This is not allowed - raise an exception instead.

Putting this all together we get:

CREATE OR REPLACE TRIGGER HOURS_A_DAY
  BEFORE INSERT OR UPDATE ON T_SCHEDULE
DECLARE
  v_count number := 0;
BEGIN
  WITH cteInterval
    AS (SELECT JOB_START,
               MASTER_ID,
               job_stop - job_start AS JOB_INTERVAL
          FROM T_SCHEDULE)
  SELECT COUNT(*)
    INTO v_count
    FROM (SELECT SUM((EXTRACT(DAY    FROM JOB_INTERVAL) * 24 * 60 * 60)  
                     (EXTRACT(HOUR   FROM JOB_INTERVAL) * 60 * 60)       
                     (EXTRACT(MINUTE FROM JOB_INTERVAL) * 60)            
                     (EXTRACT(SECOND FROM JOB_INTERVAL))) / 60 AS daily
            FROM cteInterval
            GROUP BY TRUNC(job_start, 'DD'),
                     master_id)
    WHERE daily > 480;

  IF v_count <> 0 THEN
    RAISE_APPLICATION_ERROR(-20001, 'Difference greater than 8 hours found');
  END IF;
END HOURS_A_DAY;

db<>fiddle here

CodePudding user response:

You appear to be checking, for each master_id and day, if the total of the time difference between the job start and job end time is more than 8 hours (before you insert the new row).

Which could be simplified to:

CREATE OR REPLACE TRIGGER HOURS_A_DAY
  BEFORE INSERT OR UPDATE ON T_SCHEDULE
  FOR EACH ROW
DECLARE
  v_hours number := 0;
BEGIN
  SELECT SUM(CAST(job_stop AS DATE) - CAST(job_start AS DATE)) * 24
  INTO   v_hours
  FROM   T_SCHEDULE
  WHERE  job_start >= TRUNC(:NEW.job_start)
  AND    job_start <  TRUNC(:NEW.job_start)   INTERVAL '1' DAY
  AND    master_id = :NEW.master_id;
      
  IF v_hours > 8 THEN
    RAISE_APPLICATION_ERROR(
      -20000,
      'Cannot schedule for more than 8 hours in a day.'
    );
  END IF;
END;
/

db<>fiddle here


Update - Including the new rows:

Create the types:

CREATE TYPE hours_detail IS OBJECT(
  master_id NUMBER(10),
  day       DATE,
  hours     NUMBER
);

CREATE TYPE hours_tbl IS TABLE OF hours_detail;

Then the compound trigger:

CREATE TRIGGER hours_a_day
FOR INSERT OR UPDATE ON t_schedule
COMPOUND TRIGGER
  hours        hours_tbl;
  v_overbooked PLS_INTEGER;
BEFORE STATEMENT
  IS
  BEGIN
    SELECT hours_detail(
             master_id,
             TRUNC(job_start),
             SUM(CAST(job_stop AS DATE) - CAST(job_start AS DATE)) * 24
           )
    BULK COLLECT INTO hours
    FROM   T_SCHEDULE
    GROUP BY master_id, TRUNC(job_start);
  END BEFORE STATEMENT;
BEFORE EACH ROW
  IS
  BEGIN
    hours.EXTEND();
    hours(hours.COUNT) := hours_detail(
      :NEW.master_id,
      TRUNC(:NEW.job_start),
      (CAST(:NEW.job_stop AS DATE) - CAST(:NEW.job_start AS DATE)) * 24
    );
  END BEFORE EACH ROW;
AFTER STATEMENT
  IS
  BEGIN
    SELECT 1
    INTO   v_overbooked
    FROM   TABLE(hours)
    GROUP BY master_id, day
    HAVING SUM(hours) > 8
    FETCH FIRST ROW ONLY;

    RAISE_APPLICATION_ERROR(
      -20000,
      'Cannot schedule for more than 8 hours in a day.'
    );
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      NULL;
  END AFTER STATEMENT;
END;
/

This will raise errors when multiple rows are inserted at the same time that individually do not total more than 8 hours but together go over that limit.

db<>fiddle here

  • Related