Syl*_*eow 1 php mysql database database-design design-patterns
我目前正在开发一个应用程序,允许客户通过自定义表单注册事件。该自定义表单将由事件管理员构建,用于客户的特定输入。
客户将转到表格,完成输入并选择一个地点,然后将显示可用的时间段。我坚持使用这两种数据库设计,想知道哪一种是更好的方法。
Table 'Customers' -
| id | name |
Table 'Events' -
| id | name | form_fields (json)
Table 'Venues' -
| id | address | event_id |
Table 'Timeslots' -
| id | datetime | slots | venue_id |
Pivot Table 'Tickets' -
|id | customer_id | timeslot_id | event_id | form_data (json)
Run Code Online (Sandbox Code Playgroud)
Table 'Customers' -
| id | name |
Table 'Events' -
| id | name | form_fields (json)
Table 'Venues' -
| id | address | event_id |
Table 'Timeslots' -
| id | datetime | slots | venue_id |
Pivot Table 'Tickets' -
| id | customer_id | timeslot_id |
Pivot Table 'EventCustomers' -
| id | customer id | event_id | form_data (json)
Run Code Online (Sandbox Code Playgroud)
此外,我会将 admin 构建的自定义表单的 HTML 标记存储在“form_fields”(json)中,并让客户完成表单并将值存储在“form_data”(json)中。将自定义表单和数据保存在 json 中是否也明智?
谢谢你。
回答你的问题(即使它有点离题):
以上都没有。
为了对数据建模,我们必须问自己有哪些约束。数据通常更容易定义为它不能做什么,而不是它可以做什么。
例如,您是否可以有一个 Tickets 记录:
如果您对所有这些都回答“否”,那么我们必须一次将这些数据汇总在一起(但不一定在同一张表中)。
现在在我看来,很明显你可以/不应该有一张票:
所以我会假设这些是我们的“基本”约束
你的第二种情况的问题:
您可以在特定时间段(在场地)向客户出售门票,但用于未知事件。在 Tickets 中记录,在 EventCustomers 表中没有记录
您也可以让客户注册一个活动,没有门票或时间段/场地。在 EventCustomers 中记录,在 Tickets 表中没有记录
对我来说,这似乎有些不合逻辑,而且确实违反了我上面概述的约束。
第一个案例的问题:
从表面上看,就我们上面的约束条件而言,第一种情况看起来不错。但是当我工作时,出现了一些问题。为了理解这些,作为一般规则,我们总是希望数据透视表中所有外键的唯一索引(又名唯一复合键)。
所以在第一种情况下,我们想要这个(理想情况下):
Pivot Table 'Tickets' -
|id | customer_id | timeslot_id | event_id | form_data (json)
//for this table you would want this compound, unique index
Unique Key ticket (customer_id,timeslot_id,event_id)
Run Code Online (Sandbox Code Playgroud)
这让我想到了“时段”的数量,因为这意味着客户每个活动和时间段/场地只能拥有一张门票记录。这与我所说的您最常错过的部分有关,即您无法跟踪您使用了多少。起初,您可能希望在此表中允许重复。“我们可以多加几张票吧?” - 你想,这很容易解决,不是。
附件A:
Pivot Table 'Tickets' -
|id | customer_id | timeslot_id | event_id | form_data (json)
| 1 | 1 | 1 | 1 | {}
| 2 | 1 | 1 | 1 | {}
Run Code Online (Sandbox Code Playgroud)
在考虑时,请Exhibit A考虑一些基本的数据库设计规则:
在一个好的数据库设计中你总是想要(理想情况下)
idcustomer,您可以将其设为唯一以防止添加重复的客户。它是一段数据,它本质上是独一无二的,是数据的一部分。第一个(代理键)允许您在不了解数据本身的情况下使用数据。这很好,因为它为我们提供了一些关注点分离,我们的代码和数据之间的一些抽象。当您根据主键、外键关系连接两个表时,您不需要了解有关数据的任何其他信息。
第二个(自然键)对于防止重复数据至关重要。在数据透视表的情况下,外键(它们各自表中的代理键)成为数据透视表中的自然键。这些现在是数据透视表上下文中数据的一部分,它们唯一且自然地标识该数据。
为什么独特性如此重要?
一旦允许数据透视表重复,您将遇到几个问题(特别是如果您有像 form_data 这样的附件数据):
短时间,它真的变得一团糟。
最后(我的建议)
Table 'customer' -
| id | name |
Table 'event' -
| id | name | form_fields (json)
Table 'venue' -
| id | address | slots |
Table 'show' -
| id | datetime | venue_id | event_id |
Table 'purchase' -
| id | show_id | customer_id | slots | created |
Table 'ticket' ( customers_shows )
| id | purchase_id | guid |
Run Code Online (Sandbox Code Playgroud)
我更改了很多东西(您可以使用其中的部分或全部更改):
我把复数的名字改成了单数。当我做没有附件数据的数据透视表时,我只使用复数,这样的名字是venues_events. 这是因为来自客户的记录是单个实体,我不需要进行任何连接来获取有用的数据。我们假设中的记录venues_events将包含 2 个实体,因此我会立即知道无论如何我都需要进行连接,因为除了外键之外没有其他数据。
现在,在 的情况下show,您可能会注意到它本质上是一个数据透视表。那么为什么我没有venues_events像上面列出的那样命名它。原因是我们datetime在那里有一列,这就是我所说的“附件”数据。所以在这种情况下,我可以只从show我想要的数据中提取数据,datetime而我不需要连接来完成它。因此,它可以被视为具有多对一关系的单个实体。(多对多是多对一和一对多,这就是我们需要数据透视表的原因)稍后会详细介绍此表。
字母大小写和间距。我建议使用所有小写字母,不要使用空格。MySql 区分大小写,不能很好地处理空格。这是一个从没有记住我们什么它命名的角度来看只是更容易venuesEvents或VenuesEvents或Venuesevents等..一致性命名约定是在良好的数据库设计至关重要。
以上主要是基于意见的,这是我的答案,所以这是我的意见。处理它。
表显示
我将slots列移至会场。我假设场地将决定有多少个空位可用,在我看来,这是场地本身的物理要求或属性。例如,电影院只有 X 个座位,无论电影在什么时间播放都不会改变有多少座位。如果这些假设是正确的,那么每次我们参加演出时,我们都可以省去很多努力去记住一个场地有多少个座位。
我更改timeslot为的原因show是,在您的原始案例中,数据模型存在一些不和谐之处。有些事情只是没有像他们应该的那样联系在一起。例如,您的时间段与事件没有直接关系。
图表 B(使用您的结构):
Table 'event' -
| id | name | form_fields (json) |
| 1 | "Event A" | "{}" |
| 2 | "Event B" | "{}" |
Table 'Venues' -
| id | address | event_id |
| 1 | "123 ABC SE" | 1 |
| 2 | "123 AB SE" | 2 | //address entered wrong as AB instead ABC
Table 'Timeslots' -
| id | datetime | slots | venue_id |
| 1 | "2018-01-27 04:41:23" | 200 | 1 |
| 2 | "2018-01-27 04:41:23" | 200 | 2 |
Run Code Online (Sandbox Code Playgroud)
在上面的展览中,我们可以立即看到我们必须复制地址才能在给定场地创建多个活动。因此,如果地址输入错误,则在某些场所可能是正确的,而在其他场所则可能是错误的。这可能是一个真正的问题,因为以编程方式,当此记录的场地 ID 和事件 ID 都不同时,您怎么知道AB应该是ABC这样。基本上你如何在运行时区分这些记录?你会发现这很难做到。主要问题是你有太多的数据Veneues,你试图用它做太多,而这种关系不符合数据的约束。
随着进一步的问题的出现,这甚至不是最糟糕的,因为现在venue_id不同了,我们可以破坏我们的Timeslots表,并在同一地点同时有 2 条记录。然后,因为slots与这张桌子相关联,我们也可以破坏下游的事情,例如在那个时间和地点卖出比我们应该多的票。一切都开始破裂。
即使计算给定场地的演出数量也成为一个真正的挑战,这个“缺陷”存在于您提供的两个数据模型中。
我的模型中的相同数据
#with Unique compound Key datetime_venue_id( show.datetime, show.venue_id)
Table 'event' -
| id | name | form_fields (json) |
| 1 | "Event A" | "{}" |
#| 2 | "Event B" | "{}" |
Table 'venue' -
| id | address | slots |
| 1 | "123 ABC SE" | 200 |
Table 'show' -
| id | datetime | venue_id | event_id |
| 1 | "2018-01-27 04:41:23" | 1 | 1 |
#| 2 | "2018-01-27 04:41:23" | 1 | 2 |
Run Code Online (Sandbox Code Playgroud)
如您所见,您不再拥有重复的地址。虽然看起来您可以同时输入 2 个相同的节目venue,但这只是因为我们没有包含datetime和venue_idaka 的复合唯一键Unique Key datetime_venue_id( datetime, venue_id)。如果您尝试使用该约束插入该数据,MySql 会对您造成影响。如果您在同一个“事务”中包含两个插入(事件和显示)(这就是我在 innodb 引擎中所做的),整个事情都会失败并回滚,事件或显示都不会被插入。
现在您可以尝试争辩说您可以对 Exhibit B 具有相同的唯一约束,但是由于那里的 Venue ID 不同,您就错了。
总之,show是我们新的主数据透视表与外键event和venue然后附件数据datetime。
除了我上面介绍的内容,此设置为我们提供了优于旧结构的几个优点,在这张表中,我们现在可以访问:
这一切都围绕着show记录。我们可以建立一个独立于客户或门票的“表演”。因为实际上客户不是节目的一部分,并且将他们很快(或迟到,取决于您如何看待它)包含在数据模型中会使一切变得混乱。
附件 C
#in your first case
Pivot Table 'Tickets' -
|id | customer_id | timeslot_id | event_id | form_data (json)
#in your second case
Pivot Table 'Tickets' -
| id | customer_id | timeslot_id |
Pivot Table 'EventCustomers' -
| id | customer id | event_id | form_data (json)
Run Code Online (Sandbox Code Playgroud)
正如我上面show所说,如果没有客户 ID(在您的任一数据模型中),您就不能将我所说的内容、地点和时间放在一起。当您稍后围绕此构建应用程序时,它将成为一个大问题。这在运行时可能是无法克服的。基本上,您需要收集所有数据并等待 customer_id。在您的两个模型中,情况并非如此,并且您可能无法轻松访问某些数据。例如,对于(旧结构的)第一种情况,您如何知道timeslot_id=20ANDevent_id=32加上客户等于有效票?包含客户的数据透视表之间timeslot和event外部没有直接关系。timeslot_id=20可能对任何事件都有效,而您无法知道这一点。
抓住说show=32并检查剩余的插槽数量,然后做购买记录要容易得多。一切都准备好了,等待它。
表购买
我还添加了购买或订购表,即使“演出”是免费的,这张表也为我们提供了一些很好的实用性。这也是一个数据透视表,但它和 show 一样有一些附属数据。(slots和created)。
这个表
slots分组进行汇总,show_id以查看我们“售出”了多少个插槽。通过从showto 的一个 join ,venue我们可以找出这个“show”总共有多少个插槽,使用我们上面用来聚合的相同整数键(show.id)。那么比较这两者将是一件简单的事情,如果您想获得幻想,您可以在一个查询中完成所有这些。桌票
现在您可能需要也可能不需要这张桌子。它与餐桌购买有着多对一的关系。所以 Oneorder可以有 Many tickets。购买时会生成此处的记录,数量取决于插槽中的内容。此表的主要用途只是为每个单独的票提供唯一的记录。为此我有guid列可以只是一个唯一的散列。基本上这会给你一些单张票的跟踪能力,我真的没有足够的信息来知道这在你的情况下是如何工作的。如果不担心搜索该表,您甚至可以用 JSON 数据替换该表,并且在某些门票退款的情况下,这将使维护更容易。但正如我所暗示的,这非常依赖于您的特定用例。
一些简短的 SQL 示例
加入一切(只是为了显示关系):
SELECT
{some fields}
FROM
ticket AS t
JOIN
puchase AS p ON t.purchase_id = p.id
JOIN
customer AS c ON p.customer_id = c.id
JOIN
show AS s ON p.show_id = s.id
JOIN
venue AS v ON s.venue_id = s.id
JOIN
event AS e ON s.event_id = e.id
Run Code Online (Sandbox Code Playgroud)
计算节目使用的插槽数:
SELECT
SUM(slots) AS used_slots
FROM
puchase
WHERE
show_id = :show_id
GROUP BY show_id
Run Code Online (Sandbox Code Playgroud)
获取节目的可用插槽:
SELECT
v.name,
v.slots
FROM
venue AS v
JOIN
show AS s ON s.venue_id = v.id
WHERE
v.show_id = :show_id
# or you could do s.id = :show_id
Run Code Online (Sandbox Code Playgroud)
所有表格都以不同的字母开头也很好,这使得别名更容易一些。
-注意- 表名event可能是 MySql 中的保留字,我不确定它是否可以用作表名。根据使用的上下文,一些保留字在查询的某些部分仍然有效。即使这是真的,我相信您可以想出一个解决方法。巧合的是,这就是为什么我将purchase其命名order为“order”而不是“order”是保留字的原因。(我只是碰巧想到了事件)
我希望这有帮助并且有意义。我可能花在这上面的时间比我应该花的时间多得多,但我设计这样的东西是为了谋生,我真的很喜欢它的数据架构部分,所以我有时会有点得意忘形。