2

我正在制作一个类似于公告板的东西,它要求读者承认他们已经阅读了它,并且想知道是否有更有效的方法来做到这一点。

我在 MySQL 端有 3 个表:

    +-----------------+        +-----------------+        +-----------------+
    |  Announcements  |        | Acknowledgement |        |      User       |
    +-----------------+        +-----------------+        +-----------------+
    | announce_id     |        | ack_id          |        | user_id         |
    | announce_msg    |        | announce_id     |        | user_name       |
    | ...             |        | user_id         |        | ...             |
    +-----------------+        +-----------------+        +-----------------+

当用户“阅读”公告(通过单击按钮)时,Acknowledgment将插入带有公告 ID 和用户 ID 的表格。当第二个用户“阅读”相同的公告时,Acknowledgement将再次插入具有相同公告 ID 和第二个用户 ID 的表,依此类推......

    +--------------------------------+
    |        Acknowledgement         |
    +--------+-------------+---------+
    | ack_id | announce_id | user_id |
    +--------+-------------+---------+
    |   1    |      1      |    1    |
    |   2    |      1      |    4    |
    |   3    |      1      |    3    |
    |   4    |      3      |    1    |
    |   5    |      3      |    6    |
    |   6    |      3      |    2    |
    +--------+-------------+---------+

现在来解决问题。在前端,当我在一个页面上列出所有公告时,我必须首先查询所有公告。然后,对于每个公告,我都必须对所有阅读过此公告的用户进行另一次查询。

 $sql = "select * from Announcements";
 $result = $pdo->query($sql);
 while ($row = $result->fetch())
 {
   $announce_id = $row['announce_id'];
   $announce_msg = $row['annouce_msg'];
   $readers = "";

   $sql2 = "select u.user_name from Acknowledgement as a INNER JOIN User as u where announcement_id =".$annouce_id;
   $result2 = $pdo->query($sql);
   while ($row2 = $result2->fetch())
   {
     $readers .= $row2['user_name'].", ";
   }

   echo "id:".$annouce_id.", message:".$announce_msg.", Readers:".$readers;
 }

所以如果页面上有10条公告,那么每条公告就会有10个子查询。我现在所拥有的现在可以完成这项工作......但是如果有 1000 个公告怎么办?那么会有1000个子查询呢?听起来数据库真的会受到重创。所以我希望有更好的方法来做到这一点。

此外,如果用户表中的 1000 人阅读了所有 1000 条公告,则确认表将有 1000x1000 个条目。看起来确认表会变得非常长。随着时间的推移,这会成为问题吗?

这是我正在尝试做的一个非常粗略的例子,但我确实花了很长时间来写这一切。如果需要更多详细信息,请告诉我。

4

2 回答 2

3

有一个更好的办法。您可以对 group_concat 使用单个查询:

select a.*, group_concat(u.user_name separator ', ') as AllUsers
from Announcements a join
     Acknowledgement ak
     on a.Announce_Id = ak.Announce_Id join
     User u
     on u.user_ID = ak.User_ID
group by a.announce_id

这使用隐藏列的 MySQL 功能仅按一个列(announce_id)分组,但仍然拉入一堆没有聚合的其他列(所有其他列都由“*”拉入)。

于 2012-08-22T02:04:40.550 回答
2

如果您在此处的目的是过滤掉当前用户已阅读的公告,则可以采用完全不同的方式来执行此操作。无需查询每个公告,然后找出所有已阅读这些公告的用户并检查这些结果以找到您的用户已阅读并从显示的列表中删除它们,您只需一次查询特定的所有内容用户(或用户列表)尚未阅读。

将您的查询更改为:

SELECT * FROM Announcements WHERE Announce_id NOT IN (SELECT ANNOUNCE_ID FROM Acknowledgement WHERE User_ID = <INSERT USER ID HERE>)

这应该返回该特定用户尚未确认的所有 Announcement 行。如果将最后一个WHERE子句更改为,WHERE User_ID IN ()则可以指定用户 ID 列表。

编辑:鉴于您在上面发布的评论,您可以使用此查询来获取没有人阅读的所有公告:

SELECT * FROM Announcements WHERE Announce_id NOT IN (SELECT ANNOUNCE_ID FROM Acknowledgement WHERE User_ID IN (SELECT User_ID FROM User))

将查询放在一起以查找某人(如果不是所有人)尚未阅读的公告的逻辑在这一秒就逃脱了我的视线。

编辑第二个:每个公告,以及所有阅读过和未阅读过的人,都需要使用您在上面使用过的不同类型的连接,即FULL OUTER JOIN. 不幸的是 MySQL 没有 IIRC 的那个特性,但是它可以用联合查询来模拟

SELECT A.*, ACK.*, U.* FROM Announcements AS A 
INNER JOIN Acknowledgement AS ACK ON A.Announce_ID = ACK.Announce_ID 
LEFT OUTER JOIN User AS U ON ACK.User_ID = U.User_ID 
WHERE U.User_ID IS NOT NULL
UNION ALL
SELECT A.*, ACK.*, U.* FROM Announcements AS A
LEFT OUTER JOIN Acknowledgement AS ACK ON A.Announce_ID = ACK.Announce_ID
RIGHT OUTER JOIN User AS U ON ACK.User_ID = U.User_ID

我认为应该这样做。当然,目前没有可测试的设施。

于 2012-08-22T02:01:39.390 回答