Select 如果 RANK()+1 因 JOINS 溢出而排名第一
Select first rank if RANK()+1 overflows with JOINS
我有一个名为 loop_msg
的 table 以及 msg_id
和 content
。我有第二个 table,名为 loop_msg_status
,channel
和 msg_id
。这用于 post 消息在不同频道中作为循环,所以我需要跟踪在每个 channel
中哪个 msg_id
最后被 posted。
SELECT
a.msg_id,
b.content,
b.rank,
b.rank + 1,
c.rank,
c.content as next_content
FROM
loop_msg_status as a
LEFT JOIN (
SELECT
*,
RANK() OVER (
ORDER BY
msg_id ASC
) as rank
FROM
loop_msg
) b ON a.msg_id = b.msg_id
LEFT JOIN (
SELECT
*,
RANK() OVER (
ORDER BY
msg_id ASC
) as rank
FROM
loop_msg
) c ON b.rank + 1 = c.rank
通过这个查询,我能够从每个 channel
中获取当前的 msg_id
及其 content
。我还从 table loop_msg
得到 msg_id
的 rank
。我也得到它的 rank+1
和 rank+1
的 content
,如果这有意义的话。它有效。但是,如果 rank
是最高的,那么 rank+1
不存在,我得到 NULL
next_content
。在这种情况下,我想 SELECT
来自 loop_msg
的最低 rank
,即 1
,并将其内容改为 next_content
。我应该添加 IF()
吗?如果是,在哪里?或者有更好的方法吗?
SELECT version();
> 10.5.13-MariaDB
完整 SQL 示例:
CREATE TABLE `loop_msg` (
`msg_id` int(11) NOT NULL,
`content` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `loop_msg` (`msg_id`, `content`) VALUES
(2, 'Content 2'),
(3, 'Content 3'),
(4, 'Content 4'),
(6, 'Content 6'),
(7, 'Content 7'),
(8, 'Content 8');
CREATE TABLE `loop_msg_status` (
`channel` bigint(20) NOT NULL,
`msg_id` int(11) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `loop_msg_status` (`channel`, `msg_id`) VALUES
(316757642527768577, 4),
(384071823261696010, 6),
(939746456632438804, 8);
ALTER TABLE `loop_msg`
ADD PRIMARY KEY (`msg_id`);
ALTER TABLE `loop_msg_status`
ADD PRIMARY KEY (`channel`);
ALTER TABLE `loop_msg`
MODIFY `msg_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9;
COMMIT;
预期结果
channel | nextContent
--------------------------------
316757642527768577 | Content 6
384071823261696010 | Content 7
939746456632438804 | Content 2
最终 SQL 问题中给出的架构(和预期结果):
WITH cte AS (
SELECT loop_msg.msg_id
, channel
, COALESCE(
LEAD(content) OVER w1
, FIRST_VALUE(content) OVER w1
) AS content_next
FROM loop_msg
LEFT JOIN loop_msg_status
ON loop_msg.msg_id = loop_msg_status.msg_id
WINDOW w1 AS (ORDER BY loop_msg.msg_id)
)
SELECT channel, content_next
FROM cte
WHERE channel IS NOT NULL
;
结果:
+--------------------+--------------+
| channel | content_next |
+--------------------+--------------+
| 316757642527768577 | Content 6 |
| 384071823261696010 | Content 7 |
| 939746456632438804 | Content 2 |
+--------------------+--------------+
要同时查看当前和下一个 msg_id,这里是调整后的 SQL:
WITH cte AS (
SELECT loop_msg.msg_id
, channel
, ROW_NUMBER() OVER w1 as rankx
, COALESCE(
LEAD(loop_msg.msg_id) OVER w1
, FIRST_VALUE(loop_msg.msg_id) OVER w1
) AS msgid_next
, COALESCE(
LEAD(content) OVER w1
, FIRST_VALUE(content) OVER w1
) AS content_next
FROM loop_msg
LEFT JOIN loop_msg_status
ON loop_msg.msg_id = loop_msg_status.msg_id
WINDOW w1 AS (ORDER BY loop_msg.msg_id)
)
SELECT channel, content_next, msg_id, msgid_next
FROM cte
WHERE channel IS NOT NULL
;
结果:
+--------------------+--------------+--------+------------+
| channel | content_next | msg_id | msgid_next |
+--------------------+--------------+--------+------------+
| 316757642527768577 | Content 6 | 4 | 6 |
| 384071823261696010 | Content 7 | 6 | 7 |
| 939746456632438804 | Content 2 | 8 | 2 |
+--------------------+--------------+--------+------------+
更多详情:
你可以尝试这样的事情。在这种情况下,我们可以使用 ROW_NUMBER 而不是 RANK,因为 msg_id 是唯一的,并且没有两条消息具有相同的 msg_id 而具有相同的 RANK。如果您愿意,可以随意替换为 RANK。
稍后,我们可以按频道分区,分别为每个频道执行此操作。您的问题并未完全清楚您希望如何使用频道。
SELECT *
, ROW_NUMBER() OVER (ORDER BY msg_id ASC) as rankx
, COALESCE(
LEAD(msg_id) OVER (ORDER BY msg_id ASC)
, FIRST_VALUE(msg_id) OVER (ORDER BY msg_id ASC)
) AS msgid_next
, COALESCE(
LEAD(content) OVER (ORDER BY msg_id ASC)
, FIRST_VALUE(content) OVER (ORDER BY msg_id ASC)
) AS content_next
FROM loop_msg
;
结果:
+--------+---------+-------+------------+--------------+
| msg_id | content | rankx | msgid_next | content_next |
+--------+---------+-------+------------+--------------+
| 1 | c1 | 1 | 2 | c2 |
| 2 | c2 | 2 | 3 | c3 |
| 3 | c3 | 3 | 4 | c4 |
| 4 | c4 | 4 | 5 | c5 |
| 5 | c5 | 5 | 6 | c6 |
| 6 | c6 | 6 | 7 | c7 |
| 7 | c7 | 7 | 1 | c1 |
+--------+---------+-------+------------+--------------+
设置:
CREATE TABLE loop_msg (
msg_id int auto_increment primary key
, content varchar(20)
);
INSERT INTO loop_msg (content) VALUES
('c1'), ('c2'), ('c3'), ('c4'), ('c5'), ('c6'), ('c7')
;
测试 #2,每个通道处理:
CREATE TABLE loop_msg (
msg_id int auto_increment primary key
, chan varchar(20)
, content varchar(20)
);
INSERT INTO loop_msg (content, chan) VALUES
('c1', 'chan1')
, ('c2', 'chan1')
, ('c3', 'chan1')
, ('c4', 'chan1')
, ('c5', 'chan1')
, ('c6', 'chan1')
, ('c7', 'chan1')
, ('d2', 'chan2')
, ('d3', 'chan2')
, ('d4', 'chan2')
, ('d5', 'chan2')
, ('d6', 'chan2')
, ('d7', 'chan2')
, ('d8', 'chan2')
;
SELECT *
, ROW_NUMBER() OVER (PARTITION BY chan ORDER BY msg_id ASC) as rankx
, COALESCE(
LEAD(msg_id) OVER (PARTITION BY chan ORDER BY msg_id)
, FIRST_VALUE(msg_id) OVER (PARTITION BY chan ORDER BY msg_id)
) AS msgid_next
, COALESCE(
LEAD(content) OVER (PARTITION BY chan ORDER BY msg_id)
, FIRST_VALUE(content) OVER (PARTITION BY chan ORDER BY msg_id)
) AS content_next
FROM loop_msg
;
结果:
+--------+-------+---------+-------+------------+--------------+
| msg_id | chan | content | rankx | msgid_next | content_next |
+--------+-------+---------+-------+------------+--------------+
| 1 | chan1 | c1 | 1 | 2 | c2 |
| 2 | chan1 | c2 | 2 | 3 | c3 |
| 3 | chan1 | c3 | 3 | 4 | c4 |
| 4 | chan1 | c4 | 4 | 5 | c5 |
| 5 | chan1 | c5 | 5 | 6 | c6 |
| 6 | chan1 | c6 | 6 | 7 | c7 |
| 7 | chan1 | c7 | 7 | 1 | c1 |
| 8 | chan2 | d2 | 1 | 9 | d3 |
| 9 | chan2 | d3 | 2 | 10 | d4 |
| 10 | chan2 | d4 | 3 | 11 | d5 |
| 11 | chan2 | d5 | 4 | 12 | d6 |
| 12 | chan2 | d6 | 5 | 13 | d7 |
| 13 | chan2 | d7 | 6 | 14 | d8 |
| 14 | chan2 | d8 | 7 | 8 | d2 |
+--------+-------+---------+-------+------------+--------------+
最后:
我们也可以定义一个window子句来避免每次都重写规范:
SELECT *
, ROW_NUMBER() OVER w1 as rankx
, COALESCE(
LEAD(msg_id) OVER w1
, FIRST_VALUE(msg_id) OVER w1
) AS msgid_next
, COALESCE(
LEAD(content) OVER w1
, FIRST_VALUE(content) OVER w1
) AS content_next
FROM loop_msg
WINDOW w1 AS (PARTITION BY chan ORDER BY msg_id)
;
结果:
+--------+-------+---------+-------+------------+--------------+
| msg_id | chan | content | rankx | msgid_next | content_next |
+--------+-------+---------+-------+------------+--------------+
| 1 | chan1 | c1 | 1 | 2 | c2 |
| 2 | chan1 | c2 | 2 | 3 | c3 |
| 3 | chan1 | c3 | 3 | 4 | c4 |
| 4 | chan1 | c4 | 4 | 5 | c5 |
| 5 | chan1 | c5 | 5 | 6 | c6 |
| 6 | chan1 | c6 | 6 | 7 | c7 |
| 7 | chan1 | c7 | 7 | 1 | c1 |
| 8 | chan2 | d2 | 1 | 9 | d3 |
| 9 | chan2 | d3 | 2 | 10 | d4 |
| 10 | chan2 | d4 | 3 | 11 | d5 |
| 11 | chan2 | d5 | 4 | 12 | d6 |
| 12 | chan2 | d6 | 5 | 13 | d7 |
| 13 | chan2 | d7 | 6 | 14 | d8 |
| 14 | chan2 | d8 | 7 | 8 | d2 |
+--------+-------+---------+-------+------------+--------------+
我有一个名为 loop_msg
的 table 以及 msg_id
和 content
。我有第二个 table,名为 loop_msg_status
,channel
和 msg_id
。这用于 post 消息在不同频道中作为循环,所以我需要跟踪在每个 channel
中哪个 msg_id
最后被 posted。
SELECT
a.msg_id,
b.content,
b.rank,
b.rank + 1,
c.rank,
c.content as next_content
FROM
loop_msg_status as a
LEFT JOIN (
SELECT
*,
RANK() OVER (
ORDER BY
msg_id ASC
) as rank
FROM
loop_msg
) b ON a.msg_id = b.msg_id
LEFT JOIN (
SELECT
*,
RANK() OVER (
ORDER BY
msg_id ASC
) as rank
FROM
loop_msg
) c ON b.rank + 1 = c.rank
通过这个查询,我能够从每个 channel
中获取当前的 msg_id
及其 content
。我还从 table loop_msg
得到 msg_id
的 rank
。我也得到它的 rank+1
和 rank+1
的 content
,如果这有意义的话。它有效。但是,如果 rank
是最高的,那么 rank+1
不存在,我得到 NULL
next_content
。在这种情况下,我想 SELECT
来自 loop_msg
的最低 rank
,即 1
,并将其内容改为 next_content
。我应该添加 IF()
吗?如果是,在哪里?或者有更好的方法吗?
SELECT version();
> 10.5.13-MariaDB
完整 SQL 示例:
CREATE TABLE `loop_msg` (
`msg_id` int(11) NOT NULL,
`content` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `loop_msg` (`msg_id`, `content`) VALUES
(2, 'Content 2'),
(3, 'Content 3'),
(4, 'Content 4'),
(6, 'Content 6'),
(7, 'Content 7'),
(8, 'Content 8');
CREATE TABLE `loop_msg_status` (
`channel` bigint(20) NOT NULL,
`msg_id` int(11) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `loop_msg_status` (`channel`, `msg_id`) VALUES
(316757642527768577, 4),
(384071823261696010, 6),
(939746456632438804, 8);
ALTER TABLE `loop_msg`
ADD PRIMARY KEY (`msg_id`);
ALTER TABLE `loop_msg_status`
ADD PRIMARY KEY (`channel`);
ALTER TABLE `loop_msg`
MODIFY `msg_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9;
COMMIT;
预期结果
channel | nextContent
--------------------------------
316757642527768577 | Content 6
384071823261696010 | Content 7
939746456632438804 | Content 2
最终 SQL 问题中给出的架构(和预期结果):
WITH cte AS (
SELECT loop_msg.msg_id
, channel
, COALESCE(
LEAD(content) OVER w1
, FIRST_VALUE(content) OVER w1
) AS content_next
FROM loop_msg
LEFT JOIN loop_msg_status
ON loop_msg.msg_id = loop_msg_status.msg_id
WINDOW w1 AS (ORDER BY loop_msg.msg_id)
)
SELECT channel, content_next
FROM cte
WHERE channel IS NOT NULL
;
结果:
+--------------------+--------------+
| channel | content_next |
+--------------------+--------------+
| 316757642527768577 | Content 6 |
| 384071823261696010 | Content 7 |
| 939746456632438804 | Content 2 |
+--------------------+--------------+
要同时查看当前和下一个 msg_id,这里是调整后的 SQL:
WITH cte AS (
SELECT loop_msg.msg_id
, channel
, ROW_NUMBER() OVER w1 as rankx
, COALESCE(
LEAD(loop_msg.msg_id) OVER w1
, FIRST_VALUE(loop_msg.msg_id) OVER w1
) AS msgid_next
, COALESCE(
LEAD(content) OVER w1
, FIRST_VALUE(content) OVER w1
) AS content_next
FROM loop_msg
LEFT JOIN loop_msg_status
ON loop_msg.msg_id = loop_msg_status.msg_id
WINDOW w1 AS (ORDER BY loop_msg.msg_id)
)
SELECT channel, content_next, msg_id, msgid_next
FROM cte
WHERE channel IS NOT NULL
;
结果:
+--------------------+--------------+--------+------------+
| channel | content_next | msg_id | msgid_next |
+--------------------+--------------+--------+------------+
| 316757642527768577 | Content 6 | 4 | 6 |
| 384071823261696010 | Content 7 | 6 | 7 |
| 939746456632438804 | Content 2 | 8 | 2 |
+--------------------+--------------+--------+------------+
更多详情:
你可以尝试这样的事情。在这种情况下,我们可以使用 ROW_NUMBER 而不是 RANK,因为 msg_id 是唯一的,并且没有两条消息具有相同的 msg_id 而具有相同的 RANK。如果您愿意,可以随意替换为 RANK。
稍后,我们可以按频道分区,分别为每个频道执行此操作。您的问题并未完全清楚您希望如何使用频道。
SELECT *
, ROW_NUMBER() OVER (ORDER BY msg_id ASC) as rankx
, COALESCE(
LEAD(msg_id) OVER (ORDER BY msg_id ASC)
, FIRST_VALUE(msg_id) OVER (ORDER BY msg_id ASC)
) AS msgid_next
, COALESCE(
LEAD(content) OVER (ORDER BY msg_id ASC)
, FIRST_VALUE(content) OVER (ORDER BY msg_id ASC)
) AS content_next
FROM loop_msg
;
结果:
+--------+---------+-------+------------+--------------+
| msg_id | content | rankx | msgid_next | content_next |
+--------+---------+-------+------------+--------------+
| 1 | c1 | 1 | 2 | c2 |
| 2 | c2 | 2 | 3 | c3 |
| 3 | c3 | 3 | 4 | c4 |
| 4 | c4 | 4 | 5 | c5 |
| 5 | c5 | 5 | 6 | c6 |
| 6 | c6 | 6 | 7 | c7 |
| 7 | c7 | 7 | 1 | c1 |
+--------+---------+-------+------------+--------------+
设置:
CREATE TABLE loop_msg (
msg_id int auto_increment primary key
, content varchar(20)
);
INSERT INTO loop_msg (content) VALUES
('c1'), ('c2'), ('c3'), ('c4'), ('c5'), ('c6'), ('c7')
;
测试 #2,每个通道处理:
CREATE TABLE loop_msg (
msg_id int auto_increment primary key
, chan varchar(20)
, content varchar(20)
);
INSERT INTO loop_msg (content, chan) VALUES
('c1', 'chan1')
, ('c2', 'chan1')
, ('c3', 'chan1')
, ('c4', 'chan1')
, ('c5', 'chan1')
, ('c6', 'chan1')
, ('c7', 'chan1')
, ('d2', 'chan2')
, ('d3', 'chan2')
, ('d4', 'chan2')
, ('d5', 'chan2')
, ('d6', 'chan2')
, ('d7', 'chan2')
, ('d8', 'chan2')
;
SELECT *
, ROW_NUMBER() OVER (PARTITION BY chan ORDER BY msg_id ASC) as rankx
, COALESCE(
LEAD(msg_id) OVER (PARTITION BY chan ORDER BY msg_id)
, FIRST_VALUE(msg_id) OVER (PARTITION BY chan ORDER BY msg_id)
) AS msgid_next
, COALESCE(
LEAD(content) OVER (PARTITION BY chan ORDER BY msg_id)
, FIRST_VALUE(content) OVER (PARTITION BY chan ORDER BY msg_id)
) AS content_next
FROM loop_msg
;
结果:
+--------+-------+---------+-------+------------+--------------+
| msg_id | chan | content | rankx | msgid_next | content_next |
+--------+-------+---------+-------+------------+--------------+
| 1 | chan1 | c1 | 1 | 2 | c2 |
| 2 | chan1 | c2 | 2 | 3 | c3 |
| 3 | chan1 | c3 | 3 | 4 | c4 |
| 4 | chan1 | c4 | 4 | 5 | c5 |
| 5 | chan1 | c5 | 5 | 6 | c6 |
| 6 | chan1 | c6 | 6 | 7 | c7 |
| 7 | chan1 | c7 | 7 | 1 | c1 |
| 8 | chan2 | d2 | 1 | 9 | d3 |
| 9 | chan2 | d3 | 2 | 10 | d4 |
| 10 | chan2 | d4 | 3 | 11 | d5 |
| 11 | chan2 | d5 | 4 | 12 | d6 |
| 12 | chan2 | d6 | 5 | 13 | d7 |
| 13 | chan2 | d7 | 6 | 14 | d8 |
| 14 | chan2 | d8 | 7 | 8 | d2 |
+--------+-------+---------+-------+------------+--------------+
最后:
我们也可以定义一个window子句来避免每次都重写规范:
SELECT *
, ROW_NUMBER() OVER w1 as rankx
, COALESCE(
LEAD(msg_id) OVER w1
, FIRST_VALUE(msg_id) OVER w1
) AS msgid_next
, COALESCE(
LEAD(content) OVER w1
, FIRST_VALUE(content) OVER w1
) AS content_next
FROM loop_msg
WINDOW w1 AS (PARTITION BY chan ORDER BY msg_id)
;
结果:
+--------+-------+---------+-------+------------+--------------+
| msg_id | chan | content | rankx | msgid_next | content_next |
+--------+-------+---------+-------+------------+--------------+
| 1 | chan1 | c1 | 1 | 2 | c2 |
| 2 | chan1 | c2 | 2 | 3 | c3 |
| 3 | chan1 | c3 | 3 | 4 | c4 |
| 4 | chan1 | c4 | 4 | 5 | c5 |
| 5 | chan1 | c5 | 5 | 6 | c6 |
| 6 | chan1 | c6 | 6 | 7 | c7 |
| 7 | chan1 | c7 | 7 | 1 | c1 |
| 8 | chan2 | d2 | 1 | 9 | d3 |
| 9 | chan2 | d3 | 2 | 10 | d4 |
| 10 | chan2 | d4 | 3 | 11 | d5 |
| 11 | chan2 | d5 | 4 | 12 | d6 |
| 12 | chan2 | d6 | 5 | 13 | d7 |
| 13 | chan2 | d7 | 6 | 14 | d8 |
| 14 | chan2 | d8 | 7 | 8 | d2 |
+--------+-------+---------+-------+------------+--------------+