【问题标题】:sql oracle - constraint on 2 columns from different tablessql oracle - 对来自不同表的 2 列的约束
【发布时间】:2022-01-18 02:18:32
【问题描述】:

我设计了一个机票系统预订航班。我想添加一个约束,使您可以插入的票数少于飞机上的座位数。

假设我插入了一个带有 10 个座位的飞机的航班。我只能插入该特定航班的 10 张机票。否则,应该会出现错误消息。

我尝试使用航班号的计数功能来触发。

CREATE OR REPLACE TRIGGER trg_ticket_BRIU 
    BEFORE INSERT OR UPDATE ON Ticket 
    FOR EACH ROW 
DECLARE
    l_numberofseats flight.numberofseats%type;
BEGIN
    select numberofseats into l_numberofseats
    from flight
    where flightnumber=:new.flightnumber;

    IF :new.count(flightnumber) > l_numberofseats
    THEN
        raise_application_error(-2000, 'Not enough seats');
    END IF;
END;

但我收到此错误

Trigger TRG_TICKET_BRIU compiled

LINE/COL  ERROR
--------- -------------------------------------------------------------
8/5       PLS-00049: bad bind variable 'NEW.COUNT'
Errors: check compiler log

【问题讨论】:

  • 绑定变量上没有“COUNT”属性。

标签: sql oracle


【解决方案1】:

就个人而言,我会添加一个AIRCRAFT 和一个SEAT 表:

CREATE TABLE aircraft (
  id           NUMBER
               GENERATED ALWAYS AS IDENTITY
               CONSTRAINT aircraft__id__pk PRIMARY KEY,
  tail_number  VARCHAR2(6)
               NOT NULL
               CONSTRAINT aircraft__tn__u UNIQUE
               CONSTRAINT aircraft__tn_chk CHECK(
                 REGEXP_LIKE(
                   tail_number,
                   '[A-Z]\d{1,5}|[A-Z]\d{1,4}[A-Z]|[A-Z]\d{1,3}[A-Z]{2}'
                 )
               ),
  manufacturer VARCHAR2(20)
               NOT NULL,
  model        VARCHAR2(20)
               NOT NULL,
  airline_id   CONSTRAINT aircraft__aid__fk REFERENCES airline(airline_id)
               NOT NULL
);

CREATE TABLE seat (
  id           NUMBER
               GENERATED ALWAYS AS IDENTITY
               CONSTRAINT seat__id__pk PRIMARY KEY,
  aircraft_id  CONSTRAINT seat__aid__fk REFERENCES aircraft(id)
               NOT NULL,
  seat_row     VARCHAR2(3)
               NOT NULL,
  seat_column  NUMBER
               NOT NULL,
  CONSTRAINT seat__aid_r_c__u UNIQUE (aircraft_id, seat_row, seat_column)
);

那么您的flight 表将引用aircraft

CREATE TABLE flight (
  id           NUMBER
               GENERATED ALWAYS AS IDENTITY
               CONSTRAINT flight__id__pk PRIMARY KEY,
  aircraft_id  CONSTRAINT flight__aid__fk REFERENCES aircraft(id)
               NOT NULL
  -- ...
);

ticket 将引用flightseat

CREATE TABLE ticket (
  id           NUMBER
               GENERATED ALWAYS AS IDENTITY
               CONSTRAINT ticket__id__pk PRIMARY KEY,
  flight_id    CONSTRAINT ticket__fid__fk REFERENCES flight(id)
               NOT NULL,
  seat_id      CONSTRAINT ticket__sid__fk REFERENCES seat(id)
               NOT NULL,
  -- ...
  CONSTRAINT ticket__fid_sid__u UNIQUE (flight_id, seat_id)
);

那么您永远不能出售 aircraft 上不存在的 seat 并且不需要计算最大门票数量并将其与座位进行比较(并且 seat 已添加属性,例如其位置可以显示在机票上的飞机)。

然后,您需要确保引用一致性,对于ticketflightseat 在同一个aircraft 上;这可以通过触发器来完成:

CREATE TRIGGER ticket_check_seat_on_flight
  BEFORE INSERT OR UPDATE ON ticket
  FOR EACH ROW
DECLARE
  is_valid NUMBER(1);
BEGIN
  SELECT 1
  INTO   is_valid
  FROM   flight f
         INNER JOIN seat s
         ON (f.aircraft_id = s.aircraft_id)
  WHERE  f.id = :NEW.flight_id
  AND    s.id = :NEW.seat_id;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    RAISE_APPLICATION_ERROR(
      -20000,
      'Flight and seat are on different aircraft.'
    );
END;
/

db小提琴here

【讨论】:

    【解决方案2】:

    您可以使用AFTER STATEMENT 触发器:

    CREATE TRIGGER ticket__check_number_of_seats
      AFTER INSERT OR UPDATE OR DELETE ON ticket
    DECLARE
      is_invalid NUMBER(1,0);
    BEGIN
      SELECT 1
      INTO   is_invalid
      FROM   flight f
             INNER JOIN (
               SELECT flight_id,
                      COUNT(*) AS tickets_sold
               FROM   ticket
               GROUP BY flight_id
             ) t
             ON f.id = t.flight_id
      WHERE  t.tickets_sold > f.number_of_seats;
      
      RAISE_APPLICATION_ERROR(
        -20000,
        'Too many tickets sold for flight.'
      );
    EXCEPTION
      WHEN NO_DATA_FOUND THEN
        NULL;
    END;
    /
    

    通过使用复合触发器将每一行的flight_id 值整理到一个集合中,然后在语句之后仅检查这些航班的票数,可以提高效率;但是,我将把这个扩展作为 OP 的练习。

    db小提琴here

    【讨论】:

      【解决方案3】:

      正如其他人指出的那样,没有 :new.count 列。这是因为 :new(和 :old)创建了一个伪行,其中包含与表定义完全相同的列。此外,您将得到一个Mutating 异常,因为您需要从tickets 计算flight_number。但是,由于那是导致他触发触发的表,因此您无法引用它。那么该怎么做:创建一个compound trigger,和一个支持类型(嵌套表)。在其中使用after row 部分来捕获已处理的航班号。然后在after statement 部分,您可以选择每个航班的票数。如果该计数> 0,则引发您的异常。 (见Demo

      create type flight_tickets_ntt
               is table of integer;
      
      create or replace trigger trg_ticket_ciu
         for update or insert on tickets
             compound trigger
             
          l_flights  flight_tickets_ntt := flight_tickets_ntt(); 
          
          after each row is 
          begin 
              if :new.flight_number not member of l_flights then 
                  l_flights.extend ;
                  l_flights(l_flights.count) := :new.flight_number; 
              end if; 
          end after each row; 
          
          after statement is
              l_flight_cnt flight.flight_number%type;
          begin 
               select count(*)
                 into l_flight_cnt
                 from flight f 
                where f.number_of_seats < 
                      ( select count(*)
                          from tickets t 
                         where t.flight_number in 
                               ( select * 
                                   from table (l_flights)
                               ) 
                      );  
             
                if l_flight_cnt > 0 then 
                   raise_application_error(-20000, 'Not enough seats');
                end if; 
                 
          end after statement; 
      end trg_ticket_ciu; 
      

      还有一个您需要处理的问题:如果更新更改了航班号或可能(缺失的列)航班数据会发生什么。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-05-10
        • 1970-01-01
        • 2022-07-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多