【问题标题】:SQL - message schema - need to find an existing message thread given a set of usersSQL - 消息模式 - 需要找到给定一组用户的现有消息线程
【发布时间】:2012-04-12 06:02:03
【问题描述】:

我正在设计一个简单的消息传递模式,其中 线程 将在一组用户之间发送的所有消息分组。当我必须找到一组用户的现有线程时,我遇到了困难。

发送消息有两种场景:

发送到线程:查看线程时,会直接向该线程发送消息,因此线程ID 是已知的。 (不是问题)

发送给收件人:用户创建一条新消息并从头开始指定一组收件人。如果这些用户之间不存在一个新线程,我只想创建一个新线程,这就是我卡住的地方。我需要一个查询来查找给定一组用户的现有 threadID。 ThreadMembers 表将用户映射到线程。这甚至可能吗?还是我需要修改我的表格?

我的桌子:

话题:
线程 ID (id)
最后发送(时间戳)

线程成员:
threadFK(线程外键)
userFK(用户的外键)

消息:
threadFK(线程外键)
senderFK(用户的外键)
msgID (id)
msgDate(时间戳)
msgText(文本)

非常感谢!

【问题讨论】:

  • 可能类似于:SELECT threadFK FROM ThreadMembers WHERE and userFK IN (user1,user2,user3,user4)
  • 哪个 DBMS? (Oracle?PostgreSQL?MySQL?SQL Server?)
  • 顺便说一句——这种方法是否意味着从用户 A 到用户 B 或反之亦然的每条消息都将属于一个线程,即使这些消息跨越多年?这有点违反直觉。
  • SQL Server... 是的,线程将永远跨越。一个线程将给定任何收件人组(无论是 2 人还是 200 人)将所有邮件联系在一起。

标签: sql sql-server schema messaging


【解决方案1】:

这是一个使用 MS SQL Server 2008 的答案示例(答案号 1)。这假定表:MessageThreadUsers (threadFK - int, userFK - varchar) 已定义(您的密钥类型可能是不同):

DELETE FROM MessageThreadUsers
GO

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (1, 'user1')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (1, 'user2')

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (2, 'user1')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (2, 'user2')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (2, 'user3')

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (3, 'user1')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (3, 'user2')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (3, 'user3')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (3, 'user4')

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (4, 'user1')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (4, 'user2')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (4, 'user3')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (4, 'user4')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (4, 'user5')

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user1')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user2')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user3')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user4')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user5')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user6')

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (6, 'user6')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (6, 'user3')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (6, 'user1')

GO

WITH Selected_Users (id) AS (
    SELECT 'user3' UNION
    SELECT 'user1' UNION
    SELECT 'user6'
)
SELECT a.threadFk
FROM MessageThreadUsers as a
JOIN Selected_Users as b
ON b.id = a.userFk
GROUP BY a.threadFk
HAVING COUNT(*) = (SELECT COUNT(*) FROM Selected_Users)
AND COUNT(*) = (SELECT COUNT(*) from MessageThreadUsers as c
                WHERE c.threadFk = a.threadFk)

【讨论】:

    【解决方案2】:

    编辑:

    在尝试解释该查询的过程中,我意识到它并不总是能正常工作。所以,我回去想办法测试这个。我仍然被模式设置所困扰——也就是说,它意味着新用户不能添加到现有线程中,并且一组特定的用户只能在一个线程中交谈——但纠正一下很好查询。

    WITH Selected_Users(id) as (VALUES (@id1), (@id2), --etc--),
         Threads(id) as (SELECT DISTINCT threadFk
                         FROM ThreadMembers as a
                         JOIN Selected_Users as b
                         ON b.id = a.userFk)
    SELECT a.id
    FROM Threads as a
    WHERE NOT EXISTS (SELECT '1'
                      FROM ThreadMembers as b
                      LEFT JOIN Selected_Users as c
                      ON c.id = b.userFk
                      WHERE c.id IS NULL
                      AND b.threadFk = a.id)
    AND NOT EXISTS (SELECT '1'
                    FROM Selected_Users as b
                    LEFT JOIN ThreadMembers as c
                    ON c.userFk = b.id
                    AND c.threadFk = a.id
                    WHERE c.userFk IS NULL) 
    

    该语句可能必须是动态的,以构建选定用户的列表,除非 SQL Server 有办法提供一个列表作为主变量(我知道 DB2 可以,至少在 iSeries 中是这样)。我没有完美的数据集来测试它,但是针对数百万行表(只有多对一关系),它几乎立即返回 - 我正在获得仅索引访问(提示提示) .

    解释:

    WITH Selected_Users(id) as (VALUES (@id1), (@id2), --etc--),
    

    此 CTE 正在构建用户列表,以便可以将其作为表格引用。这使它最容易处理,尽管可以在任何地方简单地用 IN 语句替换它(但需要多个引用)。

         Threads(id) as (SELECT DISTINCT threadFk
                         FROM ThreadMembers as a
                         JOIN Selected_Users as b
                         ON b.id = a.userFk)
    

    此 CTE 获取用户参与的(不同的)线程列表。大多数情况下,这只是将列表分解为对 threadFk 的单个引用。

    SELECT a.id
    FROM Threads as a
    

    ...获取选定的线程集...

    WHERE NOT EXISTS (SELECT '1'
                      FROM ThreadMembers as b
                      LEFT JOIN Selected_Users as c
                      ON c.id = b.userFk
                      WHERE c.id IS NULL
                      AND b.threadFk = a.id)
    

    如果所选用户列表中没有任何人“丢失” - 也就是说,它会消除具有较大用户列表子集的用户列表的线程。它还消除了从选择中列出了一些用户的线程,但也消除了一些未列出的线程,这意味着用户的计数会匹配,但实际用户不会匹配(这是我的第一个版本失败了)。


    编辑:

    我意识到,虽然现有语句处理了提供的用户列表是为给定线程列出的用户子集的情况,但我没有处理所选用户列表包含子集,即给定线程的用户列表。

    AND NOT EXISTS (SELECT '1'
                    FROM Selected_Users as b
                    LEFT JOIN ThreadMembers as c
                    ON c.userFk = b.id
                    AND c.threadFk = a.id
                    WHERE c.userFk IS NULL) 
    

    这个条款解决了这个问题。它确保在排除特定线程的用户后,选择列表中没有任何剩余用户。

    这个声明现在让我有点烦——我可能有更好的方法来做到这一点......


    编辑:

    Muwahaha,一个COUNT(*)的版本,应该也更快:

    WITH Selected_Users(id) as (VALUES (@id1), (@id2), --etc--),
    SELECT a.threadFk
    FROM ThreadMembers as a
    JOIN Selected_Users as b
    ON b.id = a.userFk
    GROUP BY a.threadFk
    HAVING COUNT(*) = (SELECT COUNT(*) FROM Selected_Users)
    AND COUNT(*) = (SELECT COUNT(*) from ThreadMembers as c
                    WHERE c.threadFk = a.threadFk)
    

    解释:

    SELECT a.threadFk
    FROM ThreadMembers as a
    JOIN Selected_Users as b
    ON b.id = a.userFk
    

    这是加入以获取列出的成员所属的所有线程。这相当于上面的Threads CTE。实际上,您也可以在上述查询中删除该 CTE。

    GROUP BY a.threadFk
    

    毕竟我们只想要给定线程的一个实例。此外(至少在 DB2 中),该语句的其余部分除非存在,否则无效。

    HAVING COUNT(*) = (SELECT COUNT(*) FROM Selected_Users)
    

    验证,对于给定的线程,所有选定的用户都存在。或者,所有选定的用户都必须出现在给定的线程中。

    AND COUNT(*) = (SELECT COUNT(*) from ThreadMembers as c
                    WHERE c.threadFk = a.threadFk)
    

    验证,对于给定的线程,没有未选择的用户。或者,不能有任何用户“被排除在外”

    应该为此获得仅索引访问权限(我似乎是)。结果行的COUNT(*)(对于GROUP BY)应该只执行一次,并重复使用。 HAVING 子句在发生GROUP BY 之后评估(如果我没记错的话),因此从原始表中进行计数的子选择应该只发生一次 em> 每个threadFk

    【讨论】:

    • 感谢 X-Zero!我正试图围绕查询展开我的头脑......虽然我已经写了很多 SQL,但我绝不是专家,这些类型的查询让我感到困惑。您能否添加一条评论,简要说明查询的作用?而且,如果有数百万个线程,您是否预见到任何性能问题?
    • 线程被定义为一组用户之间的对话。我没有想过将新用户添加到线程中,但为什么不呢?似乎是一个很好的功能。我要试试这个,但我不确定它会带我去哪里。但是,我将无法在几天内完成此操作......同时,我给你点帮助!
    • 我正在尝试在 SQL Server 2008 中编写此查询,但遇到了一些错误。第一部分是根据用户的 ID 构建一个表,对吗?我相信 TSQL 中的语法是不同的。你知道如何用 TSQL 写这个吗?
    • 是的,第一部分只是获取用户表。对不起,我真的不知道。我以为您的主机变量是由'@' 指定的,但我可能错了。如果您正在动态构建它,您应该能够列出 id。那个确切的声明(嗯,替换了主机变量)在我的 (DB2) 系统上工作,我认为它会是通用的。
    • 好的,我想我已经启动了临时表,但我在列名方面遇到了一些问题。第二行:Threads(id)...id不是Threads中的字段...不应该是Threads(threadID)吗?
    【解决方案3】:

    我不建议这样做,但我认为您最好通过向Thread 添加一列来稍微反规范化,其中包含一个以逗号分隔的外键排序列表到User。并索引该列。然后,您的应用程序只需对发件人的用户 ID + 所有收件人进行排序,用逗号加入排序列表,然后查找 Thread 记录。

    因为——根据定义——线程中的用户列表永远不会改变,您只需要在插入时正确填充这些内容,而不必担心以后的更新是否一致。

    (要明确:您所描述的内容绝对可以通过适当的规范化模式实现。但它会很丑,而且我认为它会表现不佳。)

    【讨论】:

    • 有趣的方法...我倾向于避免包含逗号分隔值的字段,但我知道这如何有效。
    • 考虑到这一点后,我不确定这是否容易、高效或不那么难看。如果您的线程中有 100 个收件人怎么办?收件人字段将是一个以逗号分隔的长列表。您必须确保每个收件人都在该列表中,这(我认为)会非常难看。
    • @Redtopia:我不知道您所说的“您必须确保每个收件人都在该列表中”是什么意思-您是说如果两条消息属于同一个线程他们的用户列表完全相同相同,所以这只是简单的旧字符串相等问题(WHERE comma_separated_sorted_list = '...')。如果您担心长度,另一种方法是存储列表的 MD5 或 SHA-1 哈希而不是列表本身,或者在列表的前 100 个字符之外存储这样的哈希,等等。
    • 好的,所以需要对列表进行排序。这是有道理的。
    【解决方案4】:

    说您是否对是否存在任何线程感兴趣是否正确:1)在按threadFK分组时,线程成员的数量与您感兴趣的组的成员数量相同,2)具有并链接到每个成员?如果是这样,我认为将从那里得到解决方案(所以这是一个建议的答案)。确切的机制会因您使用的数据库品牌而异,oracle、postgres 或 sql server 可能会比其他品牌更简单。你想如何调用这个东西,作为一个存储过程,它接受一个用户表、一个用户名列表,并返回什么,如果有匹配的键,或者 NULL?

    【讨论】:

    • 会有多个线程具有相同的用户数,所以我不确定这会有所帮助。看起来它应该很简单,尽管与任何消息传递系统一样,它也必须高效。给定一组用户,我需要查看他们之间是否存在线程。如果没有,我会创建一个。任何用户组之间应该只有一个线程。假设会有数百万个线程。我还没有实现任何东西,所以答案可以建议对我的表进行修改。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-17
    • 1970-01-01
    • 2013-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多