【问题标题】:Complex SQL query for messaging app消息应用程序的复杂 SQL 查询
【发布时间】:2015-03-13 03:59:55
【问题描述】:

我正在使用 PostgreSQL 9.3.4 在 Django 1.6.2 应用程序中构建消息传递功能。在用户的“消息”主页上,我将显示用户与其他用户的对话列表。每个对话“图块”或块将显示该对话中其他用户的图片和姓名、该对话中最后一条消息的发送日期以及该最后一条消息中的前 25 个字符。如果最后一条消息是由正在查看这些对话的用户发送的,我还会显示一个小的“回复”图标。我的查询已经到了可以识别查看者和所有其他用户之间的所有对话的地步,但是我无法从用户和消息表中提取我需要的字段。

我的表格(显示在底部)是用户、消息和对话。虽然我已经实现了我的表模式,以便在用户和对话之间存在多对多的关系,但一开始我将创建我的界面,以便用户只能向另一个用户发送消息,而不是多个用户。

当我对下面显示的数据运行查询时,我试图返回的是用户 3、4、5 的对话和用户 ID 以及他们关联的用户名,该对话中的最后一条消息,谁发送了它,以及它的发送日期。相反,我收到了错误:

ERROR: syntax error at or near "WHERE"

谁能帮我解决这个问题?我对速度比对优雅更感兴趣。

测试用例

conversation_user 链接表中的数据:

 id | conversation_id | user_id 
----+-----------------+---------
  1 |               1 |      32
  2 |               1 |       3   <- want this
  3 |               2 |      32
  4 |               2 |       4   <- want this
  6 |               3 |       3
  7 |               3 |       1
  8 |               4 |      32
  9 |               4 |       5   <- want this
 10 |               5 |       7
 11 |               5 |       9

我想返回的行。每条消息都是该对话中的最后一条消息。

conversation_id | user_id | username  | from_user | message | send_date
----------------+---------+-----------+-----------+---------+----------
 1              | 3       | user3     | u3 or u32 | <msg3>  | <date>
 2              | 4       | user4     | u4 or u32 | <msg4>  | <date>
 4              | 5       | user5     | u5 or u32 | <msg5>  | <date>

不工作的查询:

SELECT cu.conversation_id,
       cu.user_id,
       au.username,
       m.from_user,
       m.message,
       m.send_date
FROM conversation_user cu
INNER JOIN auth_user au ON cu.user_id = au.id
INNER JOIN message m ON cu.conversation_id = m.conversation_id
ORDER BY m.send_date DESC LIMIT 1
WHERE conversation_id IN
    (SELECT conversation_id
     FROM conversation_user
     WHERE user_id = 32)
  AND user_id != 32;

表定义

# auth_user
--------------+--------------------------+------------------------------
 id           | integer                  | not null default nextval(...
 username     | character varying(30)    | not null
Referenced by:
    TABLE "conversation_user" CONSTRAINT "conversation_user_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
    TABLE "message" CONSTRAINT "message_from_user_id_fkey" FOREIGN KEY (from_user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED

# conversation
------------+--------------------------+--------------------------------
 id         | integer                  | not null default nextval(...
 start_date | timestamp with time zone | not null
Referenced by:
    TABLE "conversation_user" CONSTRAINT "conversation_id_refs_id_4344ca71" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED
    TABLE "message" CONSTRAINT "message_conversation_id_fkey" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED

# conversation_user
-----------------+---------+--------------------------------------------
 id              | integer | not null default nextval(...
 conversation_id | integer | not null
 user_id         | integer | not null
Foreign-key constraints:
    "conversation_id_refs_id_4344ca71" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED
    "conversation_user_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED

# message
     Column      |           Type           |
-----------------+--------------------------+---------------------------
 id              | integer                  | not null default nextval(...
 conversation_id | integer                  | not null
 from_user_id    | integer                  | not null
 to_user_uid     | integer                  | not null
 message         | text                     | not null
 send_date       | timestamp with time zone | not null
Foreign-key constraints:
    "message_conversation_id_fkey" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED
    "message_from_user_id_fkey" FOREIGN KEY (from_user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED

【问题讨论】:

  • ORDER BY移动到WHERE条件之后:WHERE conversation_id..... ORDER BY m.send_date DESC LIMIT 1
  • 为什么要编写原始 SQL 而不是使用 Django 模型层?
  • @DanielRoseman,我使用的是原始 SQL,因为虽然我不是 SQL 专家,但我更喜欢编写 SQL,而不是使用 Django 的 ORM 命令。另外,我想消除由于 Django 必须将 ORM 命令转换为 SQL 而导致的任何可能的性能开销。
  • @DanielRoseman:使用 Django 模型层有什么好处?
  • @Lamak,感谢您的意见,但我必须感谢 Erwin,因为他为我提供了大量帮助。

标签: sql django postgresql database-design greatest-n-per-group


【解决方案1】:

修正语法

基本上,您只需将 WHERE 条件移动到适当的位置,例如 @Lamak commented:

SELECT  ...
FROM conversation_user cu
INNER JOIN ...
WHERE conversation_id IN
    (SELECT conversation_id
     FROM conversation_user
     WHERE user_id = 32)
AND user_id != 32
ORDER BY m.send_date DESC
LIMIT 1;

加快速度

根据评论:

我正在尝试选择用户 32 正在进行的每个 [...] 对话中的最后一条消息。

SELECT cu.conversation_id
     , ufrom.username AS from_user
     , uto.username   AS to_user
     , m.message
     , m.send_date
FROM   conversation_user cu
LEFT   JOIN LATERAL (
   SELECT from_user_id, to_user_id, message, send_date
   FROM   message   m
   WHERE  m.conversation_id = cu.conversation_id
   ORDER  BY send_date DESC
   LIMIT  1
   ) m ON TRUE
LEFT   JOIN auth_user ufrom ON ufrom.id = m.from_user_id
LEFT   JOIN auth_user uto   ON uto.id = m.to_user_id
WHERE  cu.user_id = 32;

注意事项

数据库设计

  • 查询假定 (user_id, conversation_id)UNIQUE - 你是 confirmed in the comment。请务必添加一个实际的 UNIQUE 约束,它会自动提供急需的索引。

  • message(conversation_id, send_date DESC) 上的索引也会有所帮助。详情:

  • 假设 auth_user.id 是 PK,所以它会被索引。

  • message.to_user_uid 可能应该是 to_user_id - 就像 from_user_id

  • 您可能希望添加另一个 FK 以保持一致:

    "message_to_user_id_fkey" FOREIGN KEY (to_user_id) REFERENCES auth_user(id)
    

    不知道为什么你认为你需要DEFERRABLE INITIALLY DEFERRED。如果您不知道自己需要这个,请将其删除。它用于特殊目的,使常规操作更加昂贵。

  • 如果only two users can take part in the same conversation删除 conversation_user 并添加user1user2 或类似于@987654347 会更有效@ - 除非用户/对话的每个组合都有更多属性。也可能简化message。您只需要布尔信息而不是 from_userto_user
    根据关系理论,conversation可以看作是表auth_user与自身之间多对多关系的实现。

【讨论】:

  • 欧文,非常感谢。这就是我想要做的几乎。我正在尝试选择 user32 正在进行的三个对话中的 每个 中的最后一条消息(与用户 3、4 和 5)。这只是给了我 user32 和 user5 之间最后一次对话中的最后一条消息。我将继续查看此问题,看看是否可以修复它。如果您知道需要进行哪些更改,请告诉我。再次感谢!
  • 这个query实际上检索了所需的行;它只是缺少用户和消息表中的列。它源自您帮助我处理的较早的question
  • @Robert:任何数量的用户都可以参与同一个对话?但是,每个用户只有 一次 次吗? UNIQUE 约束将强制执行(并自动创建提到的索引)。
  • 哦不,每个对话只有两个用户可以参与。 user32 和 user3 可以进行对话,而 user32 和 user4 可以进行单独的对话,但用户 32、3 和 4 不能都在同一个对话中。在每次对话中,两个用户中的每个用户都可以向其他用户发送任意数量的消息。我刚刚编写了一个使用 conversation_id 选择消息字段的查询,因此几乎看起来基于 user_id 选择对话并产生对话 ID 的查询需要是消息表查询的子查询。我正在努力解决这个问题。
  • 从概念上讲,它需要像this query 一样工作,只是这个查询不会执行。子查询只能选择一列,我的子查询选择四列。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多