[英]Complex SQL query for messaging app
I'm building a messaging feature in a Django 1.6.2 application using PostgreSQL 9.3.4. 我正在使用PostgreSQL 9.3.4在Django 1.6.2应用程序中构建消息传递功能。 On a user's "Messages" home page, I'll show a list of conversations that a user has with other users.
在用户的“消息”主页上,我将显示一个用户与其他用户的对话列表。 Each conversation "tile" or block will show the picture and name of the other user in that conversation, the date the last message in that conversation was sent, and the first 25 characters in that last message.
每个对话“平铺”或块将显示该对话中另一个用户的图片和名称,该对话中最后一条消息的发送日期以及该最后一条消息中的前25个字符。 I'll also show a small "reply" icon if the last message was sent by user who is viewing these conversations.
如果最后一条消息是由正在查看这些对话的用户发送的,我还将显示一个小的“答复”图标。 I've got my query to the point where I can identify all of the conversations between the viewer and all the other users but I'm having trouble pulling in the fields I need from the User and Message tables.
我已经查询到可以识别查看器与所有其他用户之间的所有对话的地步,但是我在从“用户”和“消息”表中提取所需字段时遇到了麻烦。
My tables (shown at the bottom) are User, Message, and Conversation. 我的表格(显示在底部)是用户,消息和对话。 Although I've implemented my table schemas so that there is a many-to-many relationship between users and conversations, in the beginning I'm going to create my interface so that a user can only send a message to one other user instead of multiple users.
尽管我已经实现了表模式,以便用户和会话之间存在多对多关系,但一开始我将创建我的界面,以便用户只能向其他用户发送消息,而不能多个用户。
When I run my query on the data shown below, what I'm trying to get back is the conversation and user IDs for users 3, 4, 5 along with their associated usernames, the last message in that conversation, who sent it, and the date it was sent. 当我对下面显示的数据运行查询时,我要获取的是用户3、4、5的对话和用户ID以及相关的用户名,对话中的最后一条消息,发送者以及发送日期。 Instead, I'm getting the error:
相反,我得到了错误:
ERROR: syntax error at or near "WHERE"
Can anyone help me fix this query? 谁能帮我解决这个问题? I'm more interested in speed than elegance.
我对速度比对优雅更感兴趣。
Data in conversation_user linking table: 对话用户链接表中的数据:
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
Rows I want to return. 我要返回的行。 Each message is last message in that conversation.
每个消息是该会话中的最后一条消息。
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
Basically, you just need to move the WHERE condition to its proper place, like @Lamak commented : 基本上,您只需要将WHERE条件移动到适当的位置,如@Lamak注释 :
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;
According to comment: 根据评论:
I'm trying to select the last message in each of the [...] conversations user 32 is having.
我正在尝试选择用户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;
A join is typically faster than an IN
construct on a subquery, especially with big sets. 联接通常比子查询上的
IN
构造更快,尤其是对于大集合而言。 But you don't need either. 但是您也不需要。 You have been overcomplicating things.
您已经使事情复杂化了。
You could have a simpler query with DISTINCT ON
, but I expect this one to be faster. 您可以使用
DISTINCT ON
进行更简单的查询,但我希望此查询会更快。
Details: 细节:
The query assumes that (user_id, conversation_id)
is UNIQUE
- which you confirmed in the comment . 该查询假定
(user_id, conversation_id)
为UNIQUE
您已在注释中确认了这一点 。 Be sure to add an actual UNIQUE constraint, which provides the much needed index automatically. 确保添加实际的UNIQUE约束,该约束会自动提供急需的索引。
An index on message
on (conversation_id, send_date DESC)
would help, too. message
上的索引(conversation_id, send_date DESC)
也将有所帮助。 Details: 细节:
Assuming auth_user.id
is the PK, so it would be indexed. 假设
auth_user.id
是PK,那么它将被索引。
message.to_user_uid
is probably supposed to be to_user_id
- like from_user_id
. message.to_user_uid
可能应该是to_user_id
,例如from_user_id
。
You probably want to add another FK to stay consistent: 您可能想要添加另一个FK来保持一致:
"message_to_user_id_fkey" FOREIGN KEY (to_user_id) REFERENCES auth_user(id)
Not sure why you think you need DEFERRABLE INITIALLY DEFERRED
. 不知道为什么您认为自己需要
DEFERRABLE INITIALLY DEFERRED
。 If you don't know you need this, remove it. 如果您不知道需要此功能,请将其删除。 It's for special purposes and makes regular operations more expensive.
这是出于特殊目的,使常规操作的成本更高。
If only two users can take part in the same conversation , it would be more efficient to remove conversation_user
altogether and add user1
and user2
or similar to conversation
- unless there are more attributes for each combination of user/conversation. 如果只有两个用户可以参加同一对话 ,则完全删除
conversation_user
user1
并添加user1
和user2
或类似于conversation
将更为有效-除非用户/对话的每种组合都有更多的属性。 Potentially simplify message
, too. 也可能简化
message
。 You only need a boolean information instead of from_user
and to_user
. 您只需要布尔信息,而不是
from_user
和to_user
。
According to relational theory, conversation
can be seen as a the implementation of many-to-many relationship between table auth_user
and itself. 根据关系理论,
conversation
可以看作是表auth_user
与自身之间多对多关系的一种实现。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.