0

I am designing a DVD rental database. There are two types of Users, Members and Staff, who all have a membership card. The MEMBERSHIP_CARD could be my supertype and MEMBERS and STAFF would be my subtypes.

I am trying to implement a limit for renting DVD's. Staff can rent 5 DVD's at a time while Members can only rent 3.

MEMBERSHIP_CARD: Card_id (PK), Fname, Lname, Email, Status 
DVD: DVD_id (PK), Title, Duration, Publisher (FK)
RENTALS: Rental_id (PK), Issue_date, Return_date, DVD_id (FK), Card_id (FK)

Which option should I use?

  1. Each MEMBERSHIP_CARD has a maximum loans attribute. But that is redundant--many people have the same value.

  2. When a rental is created a trigger checks if it's for a member or staff and checks how many rentals a member already has. But then I am hard coding these values, which is not flexible.

  3. We add a MEMBERSHIP_TYPE entity with a name and maximum rentals. But I don't know whether this is overkill if there are only 2 membership types.

    MEMBERSHIP_TYPE: Membership_type_id (PK), Membership_name, Rental_max
    

    Example row: 1, STAFF, 5.

    MEMBERSHIP_CARD adds column Membership_type_id (FK).

philipxy
  • 14,867
  • 6
  • 39
  • 83
Alfonso
  • 89
  • 2
  • 8
  • 1
    Could you please give us the table structure ,some sample data and expected output. On initial thought you could try creating a check Constraint, https://stackoverflow.com/questions/28158144/using-a-case-statement-in-a-check-constraint – Himanshu Kandpal Oct 24 '22 at 15:45
  • There's no such thing as "best" in engineering unless *you* define it. And then, how are you stuck deciding? Also unfortunately all reasonable practical definitions require a ridiculous amount of experience with a ridiculous number of factors that interact with chaotic sensitivity to details. Make straightforward designs. When you demonstrate via measurement that a design & all alternatives you can think of have problems (whatever that means at the time), then ask 1 specific researched non-duplicate question. [Strategy for “Which is better” questions](https://meta.stackexchange.com/q/204461) – philipxy Oct 31 '22 at 21:23
  • Please clarify via edits, not comments. Please delete & flag obsolete comments. Please don't insert "EDIT"s/"UPDATE"s, just make your post the best presentation as of edit time. Please read the '?' & advanced edit help re block & inline formats for code & quotes, line breaks, lists, etc. [ask] [Help] [meta] [meta.se] – philipxy Oct 31 '22 at 21:33

2 Answers2

1

I see three tables involved here:

  • user_type with two rows MEMBER and STAFF, contains the rental limit
  • users with one row per user, each user is tagged with a user_type
  • rentals with the users' rentals and a return date, so we see when a rental is ended

Then I'd create a compound trigger to check whether reantal inserts violate a user's rental limit.

CREATE OR REPLACE TRIGGER trg_rental_limit_check
FOR INSERT ON rentals COMPOUND TRIGGER
  -- -----------------------------------------------------------
  -- DECLARATION SECTION
  -- -----------------------------------------------------------
  v_user_ids sys.ora_mining_number_nt := sys.ora_mining_number_nt();

  -- -----------------------------------------------------------
  -- AFTER EACH ROW SECTION
  -- -----------------------------------------------------------
  AFTER EACH ROW IS
  BEGIN
    v_user_ids.extend(1);
    v_user_ids(v_user_ids.count) := :new.user_id;
  END AFTER EACH ROW;

  -- -----------------------------------------------------------
  -- AFTER STATEMENT SECTION
  -- -----------------------------------------------------------
  AFTER STATEMENT IS
  BEGIN
    FOR rec IN
    (
      select listagg(user_id, ', ') within group (order by user_id) as users
      from
      (
        select user_id
        from rentals r
        where user_id in (select * from table(v_user_ids))
        and return_date is null
        group by user_id
        having count(*) > (select ut.rental_limit
                           from users u
                           join user_type ut on ut.user_type_id = u.user_type_id
                           where u.user_id = r.user_id
                          )
      )
    )
    LOOP
      RAISE_APPLICATION_ERROR(-20001, 'Too many rentals for user(s) ' || rec.users);
    END LOOP;
  END AFTER STATEMENT;
END trg_rental_limit_check;

(Of course you can call the users table MEMBERSHIP_CARD and the user_type table MEMBERSHIP_TYPE, if you like that better :-)

As to

But is this overkill if there are only 2 membership types..?

No, this is just the proper way to establish the relation in the database. Even without a rental limit there are two types of users, so the least thing you'd want for this is a flag, and Oracle doesn't even have a boolean data type for this. So, if it were just for MEMBER and STAFF, you could already have a separate table indicating the user type. And well, as there is a rental limit depending on the user type, it would be inappropriate not to have this table.

And the trigger guarantees the desired consistency. You don't want to allow rentals exceeding the limit, and you may want your app to check this and prevent this from happening. With the trigger in place, even a faulty app cannot insert reantals that are violating the rule.

Thorsten Kettner
  • 89,309
  • 7
  • 49
  • 73
  • Yeah this makes sense and is in line with what I was thinking would be my preferred approach. Thanks for also taking the time to write the code for the trigger, very much appreciated! – Alfonso Oct 25 '22 at 13:54
  • As a follow up question, would it be reasonable to also have a separate table for `Format` of a DVD (DVD or Blu-ray) or is that something we can simply store as an attribute with constraints? Gut feeling here is that it should simply be an attribute and not a relationship.. – Alfonso Oct 26 '22 at 16:29
  • 1
    I like lookup tables and find nothing wrong with having a format table. With a foreign key to the table it is guaranteed to only enter valid formats. Of course you can use a simple column instead, decide for some format string e.g. 'DVD' and 'BLURAY' (and add '4KBLURAY' and other formats later) and have a check constraint making sure no other value gets entered. But in your app you will have to know all valid formats then, rather than just using the available formats from the formats table. – Thorsten Kettner Oct 26 '22 at 20:32
0

A member_type coumn which defines if as user is staff or not. Also a loan column where you keepo track howmany loan a user has

the rest is a simple CHECK constraint will help keep always track that a user will nocht get more dvd as it is allowed

CREATE TABLE member ( id int, member_type int, loan int 
,
  CONSTRAINT check_loan
  CHECK ((member_type = 1 AND loan <= 5) OR (member_type = 2 AND loan <= 2)))
INSERT INTO member VALUEs (1,1,0)
1 rows affected
UPDATE member SET loan = 2 WHERE id = 1
1 rows affected
UPDATE member SET loan = loan + 4 WHERE id = 1
ORA-02290: check constraint (FIDDLE_IHFVIILOHPJRJZLFVGTY.CHECK_LOAN) violated

fiddle

nbk
  • 45,398
  • 8
  • 30
  • 47
  • Thanks for the reply, this makes a lot of sense and was what I had in mind originally. But I was trying to avoid storing the total number of loans, and was planning on just using the `RENTALS` table to derive the total for each member. Is there any way we can achieve this same logic without having a loans column? – Alfonso Oct 24 '22 at 16:03
  • I edited my main post to include the current table structure if that helps! Thanks again! – Alfonso Oct 24 '22 at 16:32
  • this solution is quicker and every select with a triger to join will take more resources than necessary as you nee only a trandction and no slow trigger – nbk Oct 24 '22 at 16:47