书籍,作者,出版商和具有书架的用户的数据库架构

时间:2014-01-16 10:37:11

标签: php mysql database

我无法找到建立表之间关系的有效方法。我希望有一个书籍,作者,出版商和注册并拥有书架的用户数据库(阅读,正在阅读,想要阅读(或计划阅读))。我希望用户能够选择他们阅读,想要阅读或正在阅读的书籍。

P.S。我知道数据库表关系中的PK和FK。

编辑:也许这是一种更好的方法:

enter image description here

然后我将使用“状态”=(阅读,植物阅读和当前阅读) - 请告诉我这是否良好和有效!

6 个答案:

答案 0 :(得分:6)

您需要booksauthors之间的N:M链接,因为一本书可能有多位作者,而且每位作者可能写过多本书。在RDBMS中,这意味着您需要一个written_by表。

bookspublishers之间的链接不同。任何给定的图书只能有一个出版商(除非在您的系统中,不同版本的图书被视为同一本书)。所以你需要的只是publisher_id

中的books外键

最后,最重要的是,您正在关注读者/用户。他们与书籍的关系。当然,这也是N:M关系。我当然希望人们阅读不止一本书(我们都知道如果你只读过一本书会发生什么......)当然,一本书不止一本人阅读。这需要一个book_users连接表。这里真正的问题是,如何设计它。有三种基本设计。

  1. 按关系类型分隔表格。 (由@just_somebody概述)优点:你只有INSERTS和DELETES,永远不会更新。虽然这看起来很简洁,但在查询优化方面有所帮助,但大部分时间除了展示大型数据库图表外,它没有任何实际用途。

  2. 一个status指标的表格。 (由@Hardcoded概述)优点:您只有一个表。缺点:您将拥有INSERTS,UPDATES和DELETES - RDBMS可以轻松处理的内容,但由于各种原因(后面会有更多内容)存在缺陷。此外,单个status字段意味着一个读者只能拥有一个任何时候与书籍的连接,意味着他在任何时间点都只能处于plan_to_readis_readinghas_read状态,并且它会假定发生这种情况的时间顺序。如果那个人打算再次读取,或暂停,然后从开头等重读,这么简单的一系列状态指示器很容易就会失败,因为突然间那个人{{1}现在,还有is_reading这件事。对于大多数应用程序而言,这仍然是一种合理的方法,并且通常有设计状态字段的方法,因此它们是互斥的。

  3. 日志。您将每个状态作为表中的新行插入 - 书籍和阅读器的相同组合将不止一次出现。您使用has_read插入第一行和时间戳。另一个plan_to_read。然后另一个is_reading。优点:您只需要INSERT行,就可以得到完整事件的简洁年表。缺点:交叉表连接现在必须处理比上述更简单的方法更多的数据(并且更复杂)。

  4. 你可能会问自己,为什么要强调你在什么情况下插入,更新或删除?简而言之,每当您运行UPDATE或DELETE语句时,您很可能实际上丢失数据。那时你需要停止在你的设计过程中思考“我在这里失去了什么?”在这种情况下,您将失去事件的时间顺序。如果用户使用他们的书籍是您的应用程序的中心,您可能希望收集尽可能多的数据。即使现在无关紧要,这也是可能让您稍后进行“魔术”的数据类型。您可以了解某人阅读的速度,完成一本书所需的尝试次数等等。所有这些都无需向用户询问任何额外的输入。

    所以,我的最终答案实际上是一个问题:

      

    告诉某人去年读了多少本书会不会有帮助?

    修改

    由于可能不清楚日志的外观以及它的运行方式,以下是这样一个表的示例:

    has_read

    现在,无论何时书的状态发生变化,您都不会更新设计模式中的“user_read”表,而是在日志中插入相同的数据,现在这些数据填充了信息年表:

    CREATE TABLE users_reading_log (
      user_id INT,
      book_id INT,
      status ENUM('plans_to_read', 'is_reading', 'has_read'),
      ts TIMESTAMP DEFAULT NOW()
    )
    

    当那个人真正开始阅读时,你会再做一次插入:

    INSERT INTO users_reading_log SET 
      user_id=1,
      book_id=1,
      status='plans_to_read';
    

    等等。现在你有一个“事件”数据库,由于时间戳列自动填充,你现在可以告诉发生了什么。请注意,此系统不能确保只存在一个特定用户帐簿对的“is_reading”。有人可能会停止阅读,然后继续阅读。您的加入必须考虑到这一点。

答案 1 :(得分:1)

首先为书籍,作者,出版商和作者创建4个表格。用户。比

  • 创建一个与books book和table authers有关系的books_authers表。
  • 创建一个与books book和table publishers有关系的books_publishers表。
  • 创建一个table books_user表,它与表书和表用户有关系。同样在这个表中使用一个标志来显示用户阅读,当前正在阅读,想要阅读(或计划阅读)的书籍ID。 这只是标记试试

答案 2 :(得分:1)

数据库表是数学relation,换句话说是predicate和一组该谓词为真的元组(“行”)。这意味着“表格”中的每个“行”都是(真)proposition

这可能看起来都很吓人,但基本原则非常简单,值得严格掌握和理解:你会更好地了解你在做什么。

如果从小binary relation开始,

关系很简单。例如,在所有整数的集合上有一个二元关系>(大于),它“包含”所有有序的整数对xy谓词x > y成立的地方。注意:您不希望将此特定关系实现为数据库表。 :)

您希望图书,作者,出版商和用户拥有书架(阅读,正在阅读,想要阅读)。那个谓词是什么? “用户U已阅读书籍B”,“用户U正在阅读书籍B”,“用户U想要阅读书籍B”将是其中的一部分; “书B有ISBN#I,题目T,作者A”将是另一个,但有些书有多位作者。在这种情况下,你最好把它拆分成一个单独的谓词:“书B是由作者A写的。”

CREATE TABLE book (
  id INT NOT NULL PRIMARY KEY
);
CREATE TABLE author (
  id INT NOT NULL PRIMARY KEY
, name TEXT NOT NULL
);
CREATE TABLE written_by (
  book INT NOT NULL REFERENCES book (id)
, author INT NOT NULL REFERENCES author (id)
);
CREATE TABLE reader (
  id INT NOT NULL PRIMARY KEY
);
CREATE TABLE has_read (
  reader INT NOT NULL REFERENCES reader (id)
, book INT NOT NULL REFERENCES book (id)
);
CREATE TABLE is_reading (
  reader INT NOT NULL REFERENCES reader (id)
, book INT NOT NULL REFERENCES book (id)
);
CREATE TABLE plans_reading (
  reader INT NOT NULL REFERENCES reader (id)
, book INT NOT NULL REFERENCES book (id)
);

等等。

修改C. J. DateIntroduction to Database Systems

答案 3 :(得分:1)

我会有一个图书表,其中包含:title,author,publisher,isbn。一个 Book_Statuses 表,包含一个id(PK)和一个状态(Read,Reading等)。 user_books 的第三个表,其中有一个与Books表相关的fk_book_id,以及一个链接到Book_Statuses表的fk_status_id。

所有这些共同为您提供了一个易于访问的数据结构。

这是假设我理解你的问题。如果您想拥有作者,出版商和图书的表格。我需要澄清你的需求。

答案 4 :(得分:1)

如果我是你,我会使用类似以下的模式:

TABLE user
-- Stores user's basic info.
( user_id       INTEGER         PRIMARY KEY
, username      VARCHAR(50)    NOT NULL
, password      VARCHAR(50)    NOT NULL
, ...
, ...
, ...
);

TABLE author
-- Stores author's basic info
( author_id      INTEGER         PRIMARY KEY
, author_name    VARCHAR(50)
, date_of_birth  DATE
, ...
, ...
, ...
);

TABLE publisher
-- Stores publisher's basic info
( publisher_id   INTEGER         PRIMARY KEY
, publisher_name VARCHAR(50)
, ...
, ...
, ...
);

TABLE book
-- Stores book info
( book_id        INTEGER         PRIMARY KEY
, title          VARCHAR(50)    NOT NULL
, author_id      INTEGER         NOT NULL
, publisher_id   INTEGER         NOT NULL
, published_dt   DATE
, ...
, ...
, ...
, FOREIGN KEY (author_id) REFERENCES author(author_id)
, FOREIGN KEY (publisher_id) REFERENCES publisher(publisher_id)
);

TABLE common_lookup
-- This column stores common values that are used in various select lists.
-- The first three values are going to be
-- a - Read
-- b - Currently reading
-- c - Want to read
( element_id    INTEGER         PRIMARY KEY
, element_value VARCHAR(2000)  NOT NULL
);

TABLE user_books
-- This table contains which user has read / is reading / want to read which book
-- There is a many-to-many relationship between users and books.
-- One user may read many books and one single book can be read by many users.
-- Hence we use this table to maintain that information.
( user_id        INTEGER         NOT NULL
, book_id        INTEGER         NOT NULL
, status_id      INTEGER         NOT NULL
, ...
, ...
, ...
, FOREIGN KEY (user_id) REFERENCES user(user_id)
, FOREIGN KEY (book_id) REFERENCES book(book_id)
, FOREIGN KEY (status_id) REFERENCES common_lookup(element_id)
);

TABLE audit_entry_log
-- This is an audit entry log table where you can track changes and log them here.
( audit_entry_log_id    INTEGER         PRIMARY KEY
, audit_entry_type      VARCHAR(10)    NOT NULL
     -- Stores the entry type or DML event - INSERT, UPDATE or DELETE.
, table_name            VARCHAR(30)
    -- Stores the name of the table which got changed
, column_name           VARCHAR(30)
    -- Stores the name of the column which was changed
, primary_key           INTEGER
    -- Stores the PK column value of the row which was changed.
    -- This is to uniquely identify the row which has been changed.
, ts                    TIMESTAMP
    -- Timestamp when the change was made.
, old_number            NUMBER(36, 2)
    -- If the changed field was a number, the old value should be stored here.
    -- If it's an INSERT event, this would be null.
, new_number            NUMBER(36,2)
    -- If the changed field was a number, the new value in it should be stored here.
    -- If it's a DELETE statement, this would be null.
, old_text              VARCHAR(2000)
    -- Similar to old_number but for a text/varchar field.
, new_text              VARCHAR(2000)
    -- Similar to new_number but for a text/varchar field.
, old_date              VARCHAR(2000)
    -- Similar to old_date but for a date field.
, new_date              VARCHAR(2000)
    -- Similar to new_number but for a date field.
, ...
, ... -- Any other data types you wish to include.
, ...
);

然后,我会在几个表上创建触发器,以跟踪更改并在audit_entry_log表中输入数据。

答案 5 :(得分:0)

你的答案是最好的方法。例如,假设您有书籍和类别表,并且书籍可以适合多个类别。保持这些数据创建第三个表以保持书籍类别关系的最佳方法。否则你必须为每个类别创建列。

  ID    name      comedy    adventure   etc
  5     BookName   yes       no         no
像这样。这是最糟糕的事情。相信我。你的解决方案是最好的方法。

并且不知道PK&数据库表关系中的FK。如果您使用它们,它将比手动完成它们更快更安全。