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
似乎不是选择最新邮件的可靠方法。您应该有一个日期列并按它排序! (只需将 datetime
或 datetime2
列添加到您的 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
这是我的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
似乎不是选择最新邮件的可靠方法。您应该有一个日期列并按它排序! (只需将 datetime
或 datetime2
列添加到您的 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