66

I have two tables Books and Audiobooks, both of which have ISBN as their primary keys. I have a table writtenby that has an isbn attribute that has a foreign key constraint to Books and Audiobooks ISBN.

The issue that comes up when I insert into writtenby is that postgresql wants the ISBN I insert into writtenby to be in both Books and Audiobooks.

It makes sense to me to have a table writtenby that stores authors and the books/audiobooks they have written, however this does not translate to a table in postgresql.

The alternative solution I am thinking of implementing was having two new relations audiobook_writtenby and books_writtenby but I am not sure that is a good alternative.

Could you give me an idea of how I would implement my original idea of having a single table writtenby referencing two different tables or how I could better design my database? Let me know if you need more information.

4 Answers 4

93

There's more than one way to do this in PostgreSQL. Personally, I prefer this way.

-- This table should contain all the columns common to both 
-- audio books and printed books.
create table books (
  isbn char(13) primary key,
  title varchar(100) not null,
  book_type char(1) not null default 'p'
    check(book_type in ('a', 'p')),
  -- This unique constraint lets the tables books_printed and books_audio 
  -- target the isbn *and* the type in a foreign key constraint.
  -- This prevents you from having an audio book in this table 
  -- linked to a printed book in another table.
  unique (isbn, book_type)
);

-- Columns unique to printed books.
create table books_printed (
  isbn char(13) primary key references books (isbn),
  -- Allows only one value. This plus the FK constraint below guarantee
  -- that this row will relate to a printed book row, not an audio book
  -- row, in the table books. The table "books_audio" is similar.
  book_type char(1) default 'p'
    check (book_type = 'p'),
  foreign key (isbn, book_type) references books (isbn, book_type),
  other_columns_for_printed_books char(1) default '?'
);

-- Columns unique to audio books.
create table books_audio (
  isbn char(13) primary key references books (isbn),
  book_type char(1) default 'a'
    check (book_type = 'a'),
  foreign key (isbn, book_type) references books (isbn, book_type),
  other_columns_for_audio_books char(1) default '?'
);

-- Authors are common to both audio and printed books, so the isbn here
-- references the table of books.
create table book_authors (
  isbn char(13) not null references books (isbn),
  author_id integer not null references authors (author_id), -- not shown
  primary key (isbn, author_id)
);
Sign up to request clarification or add additional context in comments.

15 Comments

You da real mvp, any limitations known to this method?
@Weier: Generic foreign keys is neither a relational concept nor a SQL concept. In any case, the foreign keys in the schema I posted are not generic foreign keys in the sense the article defines that term.
@MikeSherrill'CatRecall' You're right. I read your code too fast and thought you were proposing the same concept as implemented in those "Generic FK".
edit ^: the cascade does work actually, I just had to add the on delete cascade to the PK line also.
@hazer_hazer: "Use the good features of a language; avoid the bad ones." (The Elements of Programming Style, 2nd ed., Kernighan and Plauger, p19) IMO, enums in PostgreSQL are a bad one. In addition to the reasons I gave above (and there are several more), you can argue that they don't fit into the relational model, which requires that all database data be represented in columns of rows in tables. Having said that, you can do anything you want with respect to PostgreSQL. But you can't persuade me that enums are a good idea here.
|
9

You could use table inheritance to kinda get the best of both worlds. Create the audiobook_writtenby and books_writtenby with an INHERITS clause referencing the writtenby table. The foreign keys could be defined at the child level as you describe, but you could still reference data at the higher level. (You could also do this with a view, but it sounds like inheritance might be cleaner in this case.)

See the docs:

http://www.postgresql.org/docs/current/interactive/sql-createtable.html

http://www.postgresql.org/docs/current/interactive/tutorial-inheritance.html

http://www.postgresql.org/docs/current/interactive/ddl-inherit.html

Note that you will probably want to add a BEFORE INSERT trigger on the writtenby table if you do this.

Comments

5

RDBMS's do not support polymorphic foreign key constraints. What you want to do is reasonable, but not something accommodated well by the relational model and one of the real problems of object relational impedance mismatch when making ORM systems. Nice discussion on this on Ward's WIki

One approach to your problem might be to make a separate table, known_isbns, and set up constraints and/or triggers on Books and AudioBooks so that table contains all the valid isbns of both type specific book tables. Then your FK constraint on writtenby will check against known_isbns.

1 Comment

The relational model and SQL databases actually handle this kind of thing well. The problem isn't relational or SQL; the problem is that one of the obvious constraints is implemented incorrectly. (The constraint is that the ISBNs for books and for audiobooks are drawn from the same domain.)
-3

In this specific example there is absolutely no need to use multiple tables. Just use the table "Book" and add the columns from "AudioBook", if applicable. If you have to differentiate on table level with very specific columns, create views. Have you checked to see if a "Book" and an "Audio Book" with the same content have the same ISBN?

2 Comments

Even though your answer is technically correct I don't think it should be followed. PostgreSQL supports modelling this cleanly quite well. Folding together multiple objects into a single table usually ends up in a big mess.
some of the issues with this approach are: sparse table(in a row lots of empty columns), maintainability(it's confusing to look at a table and see columns that aren't related to all rows), performance(related to sparse tables)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.