问题 私人消息数据库设计


好吧,所以我觉得我很接近拥有我需要的东西,但我不确定几件事情:

TABLE messages

message_id
message_type
sender_id
timestamp

TABLE message_type

message_type_code (1, 2, 3)
name (global, company, personal)

TABLE message_to_user

message_id
receiver_id
status (read/unread)

目标:

  1. 能够向所有用户发送GLOBAL消息。
  2. 在1个或更多用户之间发送PERSONAL消息。
  3. 确定接收方是否已读取这些消息中的任何消息。

问题:

  1. 我的架构是否照顾到它需要的所有内容?
  2. 什么样的SQL查询看起来像填充某人收件箱,引入GLOBAL消息以及PERSONAL消息 - 我希望能够确定哪个是UI。

如果您觉得它会有所帮助,请随意添加到我的架构中。


6597
2018-06-09 00:45


起源

你能解释一下这三张桌子的含义吗?我已经猜到了消息,但是message_type和message_to_user我不是100% - lethalMango
当然......坚持下去。 - dcolumbus
下面有一些调整建议,如果它们适合你,我会向他们添加SQL代码。如果没有,我会让大脑齿轮休息:) - lethalMango


答案:


架构看起来会起作用。应该也应该有一个创建日期。虽然没有为每个人创建条目,但无法知道您是否已阅读全局消息。

这是一些SQL:

SELECT M.*, MTU.* 
FROM messages M 
    LEFT JOIN message_to_user MTU ON MTU.message_id=M.message_id
WHERE MTU.receiver_id={$UserID} OR M.message_type={$GlobalType}
ORDER BY M.created_on DESC

[编辑] 问题:每个用户都需要拥有自己独特的全局电子邮件“读取”状态。您可能还希望让他们能够“删除”/隐藏此电子邮件,这样他们就不必一直看着它。如果没有为每封电子邮件创建任何一行,就没有办法解决这个问题,这可能会让很多人立刻做出这样的行动......或者更好的是,在阅读之前不要创建状态。这样,只有在读取消息时才会发生全局电子邮件的INSERTS。

messages
    message_id
    message_type
    sender_id
    timestamp

message_recipient
    message_id
    user_id

message_status
    message_status_id
    message_id
    user_id
    is_read
    read_datetime
    is_deleted
    deleted_datetime

SELECT M.*, MR.*, MS.*
FROM messages M
    LEFT JOIN message_recipient MR ON MR.message_id=M.message_id
    LEFT JOIN message_status MS ON MS.message_id=M.message_id
WHERE 
    (MS.message_status_id IS NULL OR MS.is_deleted = 0)
    (MR.user_id={$UserId} OR M.message_type={$GlobalType})
ORDER BY M.timestamp DESC

[编辑] 是将message_type用作数据库表还是仅仅作为代码中的设置部分是个人偏好,部分是您的需求。如果您需要查询数据库并直接从查询中查看文本“个人”和“全局”,那么您需要使用message_type表。但是,如果您只需要“类型”来处理业务逻辑,但不需要在查询结果中看到它,那么我将使用“Enum”样式方法。枚举是一个C#的东西......在PHP中,你得到的最接近的是一个带有常量的类...类似于:

class MessageTypes {
    public const Global   = 0;
    public const Personal = 1;
}

所以,你的查询将是: WHERE ... message_type=".MessageTypes::Global."...


10
2018-06-09 00:55



我编辑了上面的一些表^^ ...所以没有办法能够有效地显示是否已读取全局消息...如果用户登录读取全局消息? - dcolumbus
当然有。创建一个“全局读取”表,其中包含用户和消息ID列。在消息ID和用户上添加左连接,然后检查全局读取中的其中一个字段是否为null。对于任何非全局或用户未读取的消息,这将返回null。 - Tyler Eaves
很好,但我不认为他需要检查消息是否是全局的。如果它是一个全局消息,它将只有多个收件人ID(这意味着它将在messages_to_users表中出现多次)。按收件人选择应该足够了。 - jedd.ahyoung
对不起,并不意味着你不能这样做......只是如果不为全局消息的每个用户创建标记为“读取”的message_to_user,上面的模式就不会这样做。或者正如Tyler建议的那样,创建另一个表...但是我会说只使用一个表来读取状态...不要将它分成两个或者你使它过于复杂。 - Kevin Nelson
Lunchmeat ...根据他的架构,我假设他不想为每个用户创建一行。如果他这样做,那么它解决了我提到的所有问题,但会产生很多INSERT。 - Kevin Nelson


一种方法可以是将全局消息与个人消息分开,就像我认为你已经尝试过的那样。

要有效地获取全局消息的读取状态,您需要添加一个表,其中包含一起包含global_message_id和user_id的复合键。

messages_tbl
 - message_id    |  int(11)  | Primary Key / Auto_Increment
 - message_type  |  int(11)
 - sender_id     |  int(11)  | FK to sender
 - receiver_id   |  int(11)  | FK to receiver
 - status        |  int(1)   | 0/1 for Unread / Read
 - message       |  text
 - date          |  datetime

global_message_tbl
 - g_message_id   |  int(11)  | Primary Key / Auto_Increment
 - g_message_type |  int(11)
 - sender_id      |  int(11)  | FK to sender
 - date           |  datetime

global_readstatus_tbl
 - user_id       |  int(11)   | Primary Key
 - g_message_id  |  int(11)   | Primary Key
 - date          |  datetime

或者合并 messages_tbl 和 global_message_tbl 所以他们每个用户都在一个循环中亲自发送一个全局消息。这会将您的架构减少到一个表。

messages_tbl
 - message_id    |  int(11)     | Primary Key / Auto_Increment
 - sender_id     |  int(11)     | FK to sender
 - receiver_id   |  int(11)     | FK to receiver
 - status        |  int(1)      | 0/1 for Unread / Read
 - message_type  |  varchar(8)  | Personal / Global / Company
 - message       |  text
 - date          |  datetime
 - type          |  varchar(8)  

如果您希望能够更好地规范化表格,并且将来更容易添加消息类型,请将message_type再次移回其自己的表中,然后制作 message_type 一个FK的 message_type_id

message_type_tbl
 - message_type_id  |  int(11)    | Primary Key / Auto_Increment
 - message_type     |  varchar(8) | Personal / Global / Company

更新 - 样本表(1表)

message_tbl

message_id | message_type | sender_id | receiver_id |  status  |  message  |      datetime
    1      |   personal   |     2     |      3      |   read   |  foobar   |  12/04/11 00:09:00
    2      |   personal   |     2     |      4      |  unread  |  foobar   |  12/04/11 00:09:00
    3      |   personal   |     3     |      2      |  unread  |  barfoo   |  12/04/11 02:05:00
    4      |   global     |     1     |      2      |  unread  |  gmessage |  13/04/11 17:05:00
    5      |   global     |     1     |      3      |  unread  |  gmessage |  13/04/11 17:05:00
    6      |   global     |     1     |      4      |   read   |  gmessage |  13/04/11 17:05:00

user_tbl

 user_id  |  name
    1     |  Admin
    2     |  johnsmith
    3     |  mjordan
    4     |  spippen

以上假设用户2,3和4是一般用户向对方发送消息,用户1是将用于发送全局消息的管理员帐户(直接分发给每个用户),允许您查看相同的信息,就像它一样是个人信息。

要以这种格式发送全局消息,您只需循环遍历users表以获取要将全局消息发送到的所有ID,然后简单地 INSERT 每个用户的行 messages_tbl

如果您不希望您的用户每天向数百万用户发送数百万条消息以及定期发送全局消息,则行数不应成为问题。您始终可以通过创建清理脚本来清除用户的旧读取消息。


5
2018-06-09 01:02



好吧,也许我应该确切地说明我要完成的事情:个人信息(一对多),从有权这样做的人发送的全球信息(一对多)。我希望能够跟踪接收者是否为了UI而读取了这些消息。 - dcolumbus
另外,你是否建议如果它是一体的 messages_tbl,在“对话”中会为每个人创建一行吗?因此,如果 约翰·史密斯 发了一条消息给 mjordan 和 spippen,会创建3条记录吗? mjordan 和 spippen 将成为接收者。对于对话的每个用户部分,会有一行专用于他们?我想我自己很困惑...... - dcolumbus
@dcolumbus - 我已经更新了我的答案,包括上面的一个样本表,希望它清除一点点 - lethalMango
LethalMango,这看起来真的很好......如果我错了,请纠正我,但是通过在一个表中存储这么多信息真的可以减轻数据库的负担,因为它可以被编入索引吗?几乎每个查询都将在那一个表上...... SELECT,INSERT,UPDATE,DELETE,对吗? - dcolumbus
@dcolumbus - 使用上面建议的模式,您可以根据需要索引列,这是降低资源的主要关键。根据您计划发送/存储的消息数量,将其存储在多个表和一个表中的区别将是名义上的。 Codewise,你只会使用一个 INSERT, UPDATE 和 DELETE 声明会轻微减少负荷。 - lethalMango