【问题标题】:Better/Right way to write a complex query编写复杂查询的更好/正确方法
【发布时间】:2015-02-13 21:35:29
【问题描述】:


这是我的桌子。

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 只是一个唯一的数字,而不是一个有序的标识列,在实际场景中可能是任何生成的唯一字母数字代码。

谢谢。

【问题讨论】:

  • 您的语法是 SQL Server。为什么这个问题被标记为 SQLite?
  • 你为什么用sqlite标记SQL Server代码?
  • SQLite 被标记是因为我更喜欢 SQL Server 或 SQLite 中的建议。

标签: sql sql-server sqlite


【解决方案1】:

这应该可以解决问题。

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 语句以典型方式解决。

但是,我一直喜欢通过关系而不是程序来解决此类问题,因此我通过(基本上)将数据加倍来简化问题。对于表中的每个源行,我们生成两行,一是发送者先出现,另一行是接收者先出现。我们不在乎哪个是发送者或接收者,我们可以说我们正在寻找 Party1 为 id 101,然后想要找到他交换消息的每个 Party2与(与他是发送者还是接收者无关),最近的一个。

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

不管怎样,Message_id 似乎并不是选择最新消息的可靠方法。您应该有一个日期列并按此排序! (只需将datetimedatetime2 列添加到您的表中,并更改ORDER BY 以使用它。您永远不知道消息是否可以从实际发生时无序地插入到表中——它们被排除在外实际上应该预料到顺序。如果您必须重新插入丢失的消息怎么办?根据我的经验,标识列并不是保证插入顺序的好方法。

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

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)
;

【讨论】:

  • 。 .另一种情况,我完全理解答案,但不是赞成票。请注意,MessageId 1 在所需的输出中是 not。您的方法会复制用户,将另一行作为发送者,另一行作为接收者。
  • 这就是为什么我编辑我的答案说它没有回答问题,但更新的答案很快就会出现!新查询是否满足?
  • 不幸的是 Erik 没有。
  • @BenisonSam 很抱歉,现在试试这个(我加了一个小提琴)。
  • 我还做了一个小调整,使查询更加美观并消除了烦人的条件——不知道你是否看到了。
【解决方案2】:

如果您想要发送/接收其他用户的最新消息,请计算 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;

【讨论】:

  • 如果我们认为消息 id 只是唯一编号而不是有序标识列...?
  • @BenisonSam 。 . .您需要一些方法来确定最新的。您可以放入任何用于该目的的列。您的示例代码和数据似乎为此目的使用了message_id
【解决方案3】:

希望对你有帮助

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

【讨论】:

  • 认为 MessasgeID 是递增的,并且总是最高的 messageID 是最新的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-01
  • 1970-01-01
相关资源
最近更新 更多