如何在MySQL中进行动态限制?

How to make a dynamic limit in MySQL?

提问人:Martin AJ 提问时间:5/27/2016 最后编辑:Martin AJ 更新时间:5/28/2016 访问量:6591

问:

我有一张这样的表格:

// 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 |
+----+--------------+------+---------+------------+ 

我正在尝试为特定用户选择至少 15 行。只是有两个条件:

  1. 始终应匹配所有未读行 (),即使它们超过 15 行。seen = NULL

  2. 如果未读行数超过 15,则还应选择 2 行读取行 ()。seen = 1


示例:是读取行数,是表中未读行数。readunreadnotifications

 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;
mysql sql-limit

评论

2赞 vp_arth 5/27/2016
只需选择所有看不见的和(与)15 看到的。之后,如果看不见的少于 15 个,则截断(客户端)为 15。
0赞 vp_arth 5/27/2016
第二个子查询未命中条件seen is not null
0赞 Martin AJ 5/27/2016
@vp_arth 您的想法包含一些有用的要点。谢谢。
0赞 Martin AJ 5/27/2016
@vp_arth 你为什么不写答案?我会接受它作为接受的。请注意,我使用PHP作为我的服务器端语言。

答:

0赞 Mike Brant 5/27/2016 #1

我可能会简化查询,并在应用程序中使用一些后处理逻辑来处理有 14 或 15 行未读的边缘情况。只需选择最多 17 行而不是 15 行,当您在客户端应用程序中循环访问结果集时,除非第 14 行和/或第 15 行未读取,否则无需费心检索第 16 行和第 17 行。

该查询可以像以下几点一样简单:

SELECT id, event, seen, time_stamp 
FROM notifications n
WHERE id_user = :id
ORDER BY seen DESC, time_stamp DESC
LIMIT 17

评论

0赞 Martin AJ 5/27/2016
谢谢你的努力,但我相信你不明白我的意思。 将输出限制为最多 17 行。如果有 20 个未读行会怎样?正如我所说,我总是想选择所有未读行。LIMIT 17
1赞 msheikh25 5/27/2016 #2
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

评论

0赞 Martin AJ 5/27/2016
哦。。这是一个非常复杂的查询。我不知道它是否有效,但我宁愿不要使用它。我觉得有一种更简单的方法。无论如何,谢谢你,+1 的尝试。
1赞 vp_arth 5/27/2016 #3

只需选择所有看不见的和(与)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。

我认为,最好的地方是获取循环。
只需计算可见/看不见的次数,并在达到足够多的行时打破循环。

一些伪代码php:

$read = $unread = 0;

while($row = $db->fetch()) {
  if ($row['seen']) $read++;
  if (!$row['seen']) $unread++;
  // ...
  if ($weHaveEnoughRows) break;
}
1赞 Martin AJ 5/27/2016 #4

我找到了解决方案。要添加第二个条件(如果未读行超过 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

评论

1赞 vp_arth 5/27/2016
现在我知道了和:)之间的区别谢谢。unionunion all
1赞 Tin Tran 5/27/2016 #5

请试试这个,

表 T 返回按time_stamp描述按行号顺序排列的已读通知。

然后,从 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
1赞 wchiquito 5/27/2016 #6

尝试:

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

SQL Fiddle 演示

评论

0赞 Martin AJ 5/27/2016
但是,在您的小提琴中,有 18 个未读行 (),输出为 18 行..!预期结果为 20 行。seen = null:-)
0赞 Martin AJ 5/27/2016
哦对不起,你是对的.这 18 个未读行中有 2 个属于另一个用户。
0赞 Martin AJ 5/27/2016
您的查询只有一个小问题.它没有任何排序..我想要这个订单:,你能把它添加到你的查询中吗?ORDER BY (seen IS NULL) desc, time_stamp desc