Better/Right 编写复杂查询的方法

Better/Right way to write a complex query


这是我的table。

SELECT * FROM [Message]

现在我想要的是,我只想要 Id 为 101 的用户从任何其他用户发送或接收的最后一条消息的列表。我为它写的查询如下

SELECT
(SELECT TOP 1 [Message_id]
 FROM [Message]
 WHERE
 ([Sender_id] = REC.[Sender_id] AND [Receiver_id] = REC.[Receiver_id])
 OR
 ([Sender_id] = REC.[Receiver_id] AND [Receiver_id] = REC.[Sender_id])
 ORDER BY 1 DESC) AS [Message Id],
REC.[Sender_id] AS [Sender Id],
REC.[Receiver_id] AS [Receiver Id],
(SELECT TOP 1 [Message]
 FROM [Message]
 WHERE
 ([Sender_id] = REC.[Sender_id] AND [Receiver_id] = REC.[Receiver_id])
 OR
 ([Sender_id] = REC.[Receiver_id] AND [Receiver_id] = REC.[Sender_id])
 ORDER BY 1 DESC) AS [Message]
FROM
(SELECT DISTINCT [Sender_id], [Receiver_id]
 FROM [Message]
 WHERE [Sender_id] = '101') REC

我得到了以下看起来不错的结果。

我是数据库查询的新手,看来我的查询效率很低而且很长。谁能建议一种更好的方法来编写此查询?此外,如果使用 JOINS 可能是编写此查询的更好方法。

注意:请考虑Message_id只是一个唯一的数字,而不是一个有序的标识列,在实际情况下可能是任何生成的唯一字母数字代码。

谢谢。

这应该可以解决问题。

WITH Priorities AS (
   SELECT
      Priority = Row_Number() OVER (PARTITION BY X.Party2 ORDER BY Message_id DESC),
      M.*
   FROM
      dbo.Message M
      OUTER APPLY (VALUES
         (M.Sender_id, M.Receiver_Id),
         (M.Receiver_id, M.Sender_Id)
      ) X (Party1, Party2)
   WHERE
      Party1 = '101'
)
SELECT *
FROM Priorities
WHERE Priority = 1     
;

See this working in a Sql Fiddle

解释:

真正的问题是你不关心所选的人是发送者还是接收者。这导致处理从一列或另一列中提取值的复杂化,例如可以使用 CASE 语句以典型方式解决。

但是,我一直热衷于通过关系而不是程序来解决此类问题,因此我通过(基本上)将数据加倍来简化问题。对于 table 中的每个源行,我们生成两行,其中发送方排在第一位,接收方排在第一位。我们不关心哪个是发送者或接收者,我们可以只说我们正在寻找 Party1 是 id 101,然后想要找到,对于每个 Party2 他与之交换了一条消息(无论他是发件人还是收件人都无关紧要),最近的一条。

OUTER APPLY只是我们避免做更多CTE或嵌套查询(另一种写法)的技巧。它类似于 LEFT JOIN,但允许使用外部引用(它指的是 table M 中的列)。

就其价值而言,Message_id 似乎不是选择最新邮件的可靠方法。您应该有一个日期列并按它排序! (只需将 datetimedatetime2 列添加到您的 table,然后更改 ORDER BY 即可使用它。您永远不知道消息是否可以插入到 table 从它们实际发生的时候起就乱序了——实际上应该预料到它们乱序了。如果你必须向后插入丢失的消息怎么办?根据我的经验,标识列不是保证插入顺序的好方法。

P.S。我最初的看法是,您希望每个发送者和接收者都发送 最近收到的消息。但是,这不是您要求的。我想我会把它留给后代,因为它也可能是对某人有用的答案:

WITH Priorities AS (
   SELECT
      SNum = Row_Number() OVER (PARTITION BY Sender_id ORDER BY Message_id DESC),
      PNum = Row_Number() OVER (PARTITION BY Receiver_id ORDER BY Message_id DESC),
      *
   FROM
      Message
   WHERE
      '101' IN (Sender_id, Receiver_id)
)
SELECT *
FROM Priorities
WHERE 1 IN (SNum, PNum)
;

如果您想要 to/from 另一个用户的最新消息,请计算 other 用户作为发件人和用户的新近度(基于 message_id)接收者。诀窍是使用 other 用户作为分区键进行分区。

然后在外查询中选择第一个:

select m.*
from (select m.*,
             row_number() over (partition by (case when sender_id = 101 then receiver_id else sender_id end)
                                order by message_id desc) as seqnum
      from message m
      where 101 in (sender_id, receiver_id)
     ) m
where seqnum = 1;

希望对您有所帮助

SELECT * FROM (
SELECT Message_id, Sender_id, Receiver_id, Message, RANK() OVER(PARTITION BY Sender_id, Receiver_id ORDER BY Message_id DESC) OBD FROM Message
WHERE Sender_id = 101 OR Receiver_id = 101) A WHERE A.OBD = 1