如何在MySQL中进行动态限制?
How to make a dynamic limit in MySQL?
我有一个 table 这样的:
// notifications
+----+--------------+------+---------+------------+
| id | event | seen | id_user | time_stamp |
+----+--------------+------+---------+------------+
| 1 | vote | 1 | 123 | 1464174617 |
| 2 | comment | 1 | 456 | 1464174664 |
| 3 | vote | 1 | 123 | 1464174725 |
| 4 | answer | 1 | 123 | 1464174813 |
| 5 | comment | NULL | 456 | 1464174928 |
| 6 | comment | 1 | 123 | 1464175114 |
| 7 | vote | NULL | 456 | 1464175317 |
| 8 | answer | NULL | 123 | 1464175279 |
| 9 | vote | NULL | 123 | 1464176618 |
+----+--------------+------+---------+------------+
我正在尝试为特定用户 select 至少 15 行。只是有两个条件:
始终应匹配所有未读行 (seen = NULL
),即使它们超过 15 行。
如果未读行数大于15,那么也应该select 2个已读行(seen = 1
).
例子: read
是已读行数,unread
是未读行数 notifications
table.
read | unread | output should be
------|--------|-------------------------------------
3 | 8 | 11 rows
12 | 5 | 15 rows (5 unread, 10 read)
20 | 30 | 32 rows (30 unread, 2 read)
10 | 0 | 10 rows (0 unread, 10 read)
10 | 1 | 11 rows (1 unread, 10 read)
10 | 6 | 15 rows (6 unread, 9 read)
100 | 3 | 15 rows (3 unread, 12 read)
3 | 100 | 102 rows (100 unread, 2 read)
这是我当前的查询,不支持第二个条件。
SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id AND seen IS NULL
) UNION
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
ORDER BY (seen IS NULL) desc, time_stamp desc
LIMIT 15
)
ORDER BY (seen IS NULL) desc, time_stamp desc;
我可能会简化查询并在应用程序中使用一些 post 处理逻辑来处理有 14 或 15 行未读的边缘情况。只需 select 最多 17 行而不是 15 行,并且当您在客户端应用程序中循环遍历结果集时,只要不费心检索第 16 行和第 17 行,除非第 14 行和/或第 15 行未读。
该查询可以很简单:
SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
ORDER BY seen DESC, time_stamp DESC
LIMIT 17
SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = 123 AND seen IS NULL
UNION
(SELECT id, event, seen, time_stamp
FROM (
SELECT id, event, seen, n.id_user, time_stamp, un.CNT
FROM notifications n
JOIN (
SELECT COUNT(1) CNT, id_user
FROM notifications
WHERE id_user = 123 and seen is NULL
group by id_user
) un
ON n.id_user = un.id_user
WHERE CNT > 15
) t1
WHERE t1.SEEN is not NULL
LIMIT 2)
UNION
SELECT id, event, seen, time_stamp
FROM (
SELECT id, event, seen, n.id_user, time_stamp, un.CNT
FROM notifications n
JOIN (
SELECT COUNT(1) CNT, id_user
FROM notifications
WHERE id_user = 123 and seen is NULL
group by id_user
) un
ON n.id_user = un.id_user
WHERE CNT < 15
) t1
WHERE t1.SEEN is not NULL
只有 select 所有未见过的和(与)15 个见过的。
SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id AND seen IS NULL
UNION ALL
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id AND seen IS NOT NULL
LIMIT 15)
因此,您现在拥有所有未读通知和最多 15 条已读通知。
之后,如果看不见的少于 15 个,您可以将(客户端)截断为 15 个。
我认为最好的地方是获取循环。
只需计数 seen/unseen 并在达到足够行数时打破循环。
一些伪代码php:
$read = $unread = 0;
while($row = $db->fetch()) {
if ($row['seen']) $read++;
if (!$row['seen']) $unread++;
// ...
if ($weHaveEnoughRows) break;
}
我找到了解决办法。要添加第二个条件 (如果有超过 15 个未读行,则选择两个已读行),我必须再使用一个 UNION
。像这样:
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id AND seen IS NULL
)UNION
(SELECT id, event, seen, time_stamp
FROM notification n
WHERE id_user = :id AND seen IS NOT NULL
LIMIT 2
)UNION
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
ORDER BY (seen IS NULL) desc, time_stamp desc
LIMIT 15
)
ORDER BY (seen IS NULL) desc, time_stamp desc;
第一个子查询获取所有看不见的行。第二个得到两个可见的行。第三个有十五行。 UNION
删除重复项,但不应用其他限制。
请试试这个,
table T returns 读取行号按 time_stamp desc.
排序的通知
然后你 select 从 T 行 <= GREATEST(15-Count() of unread,2).
然后将所有与未读合并
SELECT id,event,seen,time_stamp
FROM
(SELECT id, event, seen, time_stamp,@row:=@row+1 as row
FROM notifications n,(SELECT @row := 0)r
WHERE id_user = :id AND seen IS NOT NULL
ORDER BY time_stamp desc
)T
WHERE T.row <= GREATEST(15-
(SELECT COUNT(*) FROM notifications n
WHERE id_user = :id AND seen IS NULL),2)
UNION ALL
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
AND seen is NULL
)
ORDER BY (seen IS NULL) desc,time_stamp desc
尝试:
SET @`id_user` := 123;
SELECT `id`, `event`, `seen`, `time_stamp`
FROM (SELECT `id`, `event`, `seen`, `time_stamp`, @`unread` := @`unread` + 1
FROM `notifications`, (SELECT @`unread` := 0) `unr`
WHERE `id_user` = @`id_user` AND `seen` IS NULL
UNION ALL
SELECT `id`, `event`, `seen`, `time_stamp`, @`read` := @`read` + 1
FROM `notifications`, (SELECT @`read` := 0) `r`
WHERE `id_user` = @`id_user` AND `seen` IS NOT NULL
AND (
@`read` < (15 - @`unread`) OR
((15 - @`unread`) < 0 AND @`read` < 2)
)
) `source`;
我有一个 table 这样的:
// notifications
+----+--------------+------+---------+------------+
| id | event | seen | id_user | time_stamp |
+----+--------------+------+---------+------------+
| 1 | vote | 1 | 123 | 1464174617 |
| 2 | comment | 1 | 456 | 1464174664 |
| 3 | vote | 1 | 123 | 1464174725 |
| 4 | answer | 1 | 123 | 1464174813 |
| 5 | comment | NULL | 456 | 1464174928 |
| 6 | comment | 1 | 123 | 1464175114 |
| 7 | vote | NULL | 456 | 1464175317 |
| 8 | answer | NULL | 123 | 1464175279 |
| 9 | vote | NULL | 123 | 1464176618 |
+----+--------------+------+---------+------------+
我正在尝试为特定用户 select 至少 15 行。只是有两个条件:
始终应匹配所有未读行 (
seen = NULL
),即使它们超过 15 行。如果未读行数大于15,那么也应该select 2个已读行(
seen = 1
).
例子: read
是已读行数,unread
是未读行数 notifications
table.
read | unread | output should be
------|--------|-------------------------------------
3 | 8 | 11 rows
12 | 5 | 15 rows (5 unread, 10 read)
20 | 30 | 32 rows (30 unread, 2 read)
10 | 0 | 10 rows (0 unread, 10 read)
10 | 1 | 11 rows (1 unread, 10 read)
10 | 6 | 15 rows (6 unread, 9 read)
100 | 3 | 15 rows (3 unread, 12 read)
3 | 100 | 102 rows (100 unread, 2 read)
这是我当前的查询,不支持第二个条件。
SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id AND seen IS NULL
) UNION
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
ORDER BY (seen IS NULL) desc, time_stamp desc
LIMIT 15
)
ORDER BY (seen IS NULL) desc, time_stamp desc;
我可能会简化查询并在应用程序中使用一些 post 处理逻辑来处理有 14 或 15 行未读的边缘情况。只需 select 最多 17 行而不是 15 行,并且当您在客户端应用程序中循环遍历结果集时,只要不费心检索第 16 行和第 17 行,除非第 14 行和/或第 15 行未读。
该查询可以很简单:
SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
ORDER BY seen DESC, time_stamp DESC
LIMIT 17
SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = 123 AND seen IS NULL
UNION
(SELECT id, event, seen, time_stamp
FROM (
SELECT id, event, seen, n.id_user, time_stamp, un.CNT
FROM notifications n
JOIN (
SELECT COUNT(1) CNT, id_user
FROM notifications
WHERE id_user = 123 and seen is NULL
group by id_user
) un
ON n.id_user = un.id_user
WHERE CNT > 15
) t1
WHERE t1.SEEN is not NULL
LIMIT 2)
UNION
SELECT id, event, seen, time_stamp
FROM (
SELECT id, event, seen, n.id_user, time_stamp, un.CNT
FROM notifications n
JOIN (
SELECT COUNT(1) CNT, id_user
FROM notifications
WHERE id_user = 123 and seen is NULL
group by id_user
) un
ON n.id_user = un.id_user
WHERE CNT < 15
) t1
WHERE t1.SEEN is not NULL
只有 select 所有未见过的和(与)15 个见过的。
SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id AND seen IS NULL
UNION ALL
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id AND seen IS NOT NULL
LIMIT 15)
因此,您现在拥有所有未读通知和最多 15 条已读通知。
之后,如果看不见的少于 15 个,您可以将(客户端)截断为 15 个。
我认为最好的地方是获取循环。
只需计数 seen/unseen 并在达到足够行数时打破循环。
一些伪代码php:
$read = $unread = 0;
while($row = $db->fetch()) {
if ($row['seen']) $read++;
if (!$row['seen']) $unread++;
// ...
if ($weHaveEnoughRows) break;
}
我找到了解决办法。要添加第二个条件 (如果有超过 15 个未读行,则选择两个已读行),我必须再使用一个 UNION
。像这样:
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id AND seen IS NULL
)UNION
(SELECT id, event, seen, time_stamp
FROM notification n
WHERE id_user = :id AND seen IS NOT NULL
LIMIT 2
)UNION
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
ORDER BY (seen IS NULL) desc, time_stamp desc
LIMIT 15
)
ORDER BY (seen IS NULL) desc, time_stamp desc;
第一个子查询获取所有看不见的行。第二个得到两个可见的行。第三个有十五行。 UNION
删除重复项,但不应用其他限制。
请试试这个,
table T returns 读取行号按 time_stamp desc.
排序的通知然后你 select 从 T 行 <= GREATEST(15-Count() of unread,2).
然后将所有与未读合并
SELECT id,event,seen,time_stamp
FROM
(SELECT id, event, seen, time_stamp,@row:=@row+1 as row
FROM notifications n,(SELECT @row := 0)r
WHERE id_user = :id AND seen IS NOT NULL
ORDER BY time_stamp desc
)T
WHERE T.row <= GREATEST(15-
(SELECT COUNT(*) FROM notifications n
WHERE id_user = :id AND seen IS NULL),2)
UNION ALL
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
AND seen is NULL
)
ORDER BY (seen IS NULL) desc,time_stamp desc
尝试:
SET @`id_user` := 123;
SELECT `id`, `event`, `seen`, `time_stamp`
FROM (SELECT `id`, `event`, `seen`, `time_stamp`, @`unread` := @`unread` + 1
FROM `notifications`, (SELECT @`unread` := 0) `unr`
WHERE `id_user` = @`id_user` AND `seen` IS NULL
UNION ALL
SELECT `id`, `event`, `seen`, `time_stamp`, @`read` := @`read` + 1
FROM `notifications`, (SELECT @`read` := 0) `r`
WHERE `id_user` = @`id_user` AND `seen` IS NOT NULL
AND (
@`read` < (15 - @`unread`) OR
((15 - @`unread`) < 0 AND @`read` < 2)
)
) `source`;