消息应用程序的复杂SQL查询

时间:2015-01-14 20:34:23

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

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

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

当我对下面显示的数据运行查询时,我试图找回的是用户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

1 个答案:

答案 0 :(得分:2)

修复语法

基本上,您只需要将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并添加user1和{{}会更有效率1}}或类似于user2 - 除非每个用户/会话组合都有更多属性。也可以简化conversation。您只需要一个布尔信息,而不是messagefrom_user 根据关系理论,to_user可以被视为表conversation与其自身之间多对多关系的实现。