【问题标题】:MySQL - display/count joined records when "normal" join is impossible [duplicate]MySQL - 当“正常”连接不可能时显示/计算连接记录[重复]
【发布时间】:2017-02-28 12:07:27
【问题描述】:

有 2 个表格 - 联系人和消息:

contact_id  |   contact_email
1           |   some@mail.com
2           |   other@mail.com
3           |   no@nono.com

message_id  |   message_recipients
1           |   1,2,3
2           |   3

message_recipients 字段包含已分配给联系人的 ID。每条消息都可以分配一个或多个 ID,因此它们之间用, 符号分隔。

我需要显示所有联系人,并将消息数分配给每个联系人。由于message_recipients 字段可能包含多个ID,因此我无法运行SELECT * FROM contacts, messages WHERE contacts.contact_id=messages.message_recipients 之类的查询,因为它无法正常工作。

如果我运行SELECT * FROM contacts FULL JOIN messages,它会从contacts 表中返回许多重复的行。当然,我可以运行SELECT * FROM contacts FULL JOIN messages GROUP BY contact_id,但这个只返回来自messages 表的第一条消息。

我知道,为了计算每个联系人分配了多少条消息,我可能需要将每行中的 message_recipients 字段分解为数组,并使用 if (in_array($contact_id, $message_recipients_array)) {$total++;} 之类的代码或类似代码。现在我主要关心的是如何通过编写尽可能简单的查询来满足我的所有需求。

【问题讨论】:

  • 修复你的表结构。 不要在一个单元格中存储多个值。见Normalization
  • 任何 cmets 应该是什么样子?当然,我总是可以在 message_recipients 字段中存储一个 id,但在这种情况下,messages 表将填充数百条重复记录(其中只有 ID 字段会不同,而包含消息文本和其他详细信息的字段将是相同的)。我认为这不是一个好习惯。

标签: mysql sql join count


【解决方案1】:

修复您的表结构。 不要在一个单元格中存储多个值。见Normalization

目前,您可以使用FIND_IN_SET

select c.contact_id,
    c.contact_email,
    count(*) no_of_messages
from messages m
join contacts c on find_in_set(c.contact_id, m.message_recipients) > 0
group by c.contact_id,
    c.contact_email

但这会缓慢,因为它不能在contact_id或message_recipients上使用任何索引。

要真正解决问题,请不要在消息表中包含收件人 ID。

您应该将单个收件人存储在单独的映射表中的一行中,该映射表具有与(可能)以下结构的多对多关系。

messages_recipients (
    id int PK,
    message_id int FK referring message(message_id),
    message_recipient_id int FK referring contacts(contact_id)
)

那么你所要做的就是:

select c.contact_id,
    c.contact_email,
    count(*) no_of_messages
from messages_recipients m
join contacts c on c.contact_id = m.message_recipient_id
group by c.contact_id,
    c.contact_email

这个查询是Sargable,会更快。

【讨论】:

  • 你的意思是在messages表中有数千个重复条目还是更好?
  • 谢谢。但是,我相信它在我的场景中不起作用,因为稍后编辑消息可能会让人头疼。现在,当我编辑一条消息时,它会同时针对所有分配的收件人进行更新。因此,如果一封邮件有 100 个收件人,我只编辑一次邮件。如果我在消息表中有 100 条单独的记录,我将需要编辑 100 条消息。果然,我可以通过为每个创建的消息分配一些哈希值来自动完成(所以当消息被编辑时,它会自动处理具有相同哈希的其他消息),但我只是不知道......
  • 如果您有消息详细信息等,请将消息表分开。不要在该表中包含收件人 ID。创建一个单独的表,其中包含 message_id 和 recipient_id 之间的映射。我也在我的答案中更新了它。请检查
  • 谢谢,会试试...
  • 只是一个快速更新:按照这个关于创建映射表的建议,一切似乎都很好。只需要使用不同的 SQL 查询(因为如果没有分配消息,答案中提供的查询不会显示联系人),但除此之外一切都很好。
【解决方案2】:

修复您的数据结构!将 id 存储在字符串中是一个非常糟糕的主意。为什么?

  • 数字应该存储为数字而不是字符串。
  • SQL 不提供非常好的字符串函数。
  • 应正确表达外键约束。
  • 查询优化器不能使用索引或分区。
  • SQL 有一个很好的存储列表的方法:它被称为“表”。

有时,我们会被其他人的非常非常糟糕的设计决定所困扰。 MySQL 确实提供了一种方法来做你想做的事,find_in_set()。这是一种解决不良数据布局缺点的技巧:

select . . .
from contacts c join
     messages m
     on find_in_set(c.contact_id, m.message_recipients) > 0

【讨论】:

  • 感谢您的意见。任何适当的结构应该是什么样子的例子?请记住,一条消息可能分配了数百个联系人,所以如果我为消息表中的每个联系人创建一个单独的记录,它将被数千条相同的记录淹没(其中只有 ID 会不同)。
猜你喜欢
  • 2012-10-26
  • 1970-01-01
  • 1970-01-01
  • 2014-09-18
  • 1970-01-01
  • 1970-01-01
  • 2014-09-20
  • 2011-12-01
相关资源
最近更新 更多