小提琴和查询在这里,所以更容易找到,继续阅读问题本身。
我无法使用小提琴重现该问题。
这是您可以在小提琴中使用的查询
SELECT n.*, ns.notification_id AS is_read FROM notifications n
LEFT OUTER JOIN notification_status ns
ON n.id = ns.notification_id
LEFT JOIN notification_user_role nur
ON n.id = nur.notification_id
WHERE
(
n.esb_consultant_id = 19291
OR
n.esb_consultant_id = 'role'
)
AND nur.user_role_id = 'pl_sso_regional_vice_president'
AND n.creation_date <= NOW()
AND n.expiration_date >= NOW()
ORDER BY n.creation_date DESC, (is_read IS NULL) DESC, n.priority ASC
LIMIT 0, 10
Run Code Online (Sandbox Code Playgroud)
我也在这篇文章中把它放在了较低的位置,但在这里更容易吸引眼球。
我会尽量保持简短。
我正在开发一个通知系统。我有下面描述的 3 个表。
我正在尝试以LIMIT
10 个、分页、每页 10 个(因此OFFSET
为 10 个)来获取通知。我正在使用 ajax 加载接下来的 10 个。
它们按优先级排序(从 1 到 6,首先显示 1,最后显示 6)。
所有未读通知必须首先显示(优先级仍然适用),而已读通知必须最后显示(优先级仍然适用)。
通知是每个角色。一个用户可以有多个角色(因此需要另一个表)。
下面notification_status
描述的表格用于跟踪读取了哪些通知。
notification_status
没有读取表中没有的通知。这是非常重要的。这个决定不是我做的。我只得忍受它。
为了把它放在大图中,让我们举个例子:
假设我们有 14 个通知:
其中 5 个将是优先级 1,未读。
其中 4 个优先级 > 1,未读。
其中 3 个将是优先级 1,阅读。
其中 2 个将优先级 > 1,阅读。
预期的显示顺序如下。
5 unread priority 1
4 unread priority > 1
1 read priority 1
ajax 从这里开始,因为我们每页有 10 个
2 read priority 1
2 read priority > 1
表结构如下。
notifications
+-------------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| type_id | int(10) unsigned | NO | | NULL | |
| sticky | int(10) unsigned | NO | | NULL | |
| priority | int(10) unsigned | NO | | NULL | |
| esb_consultant_id | varchar(40) | NO | | | |
| message_id | varchar(100) | NO | | | |
| esb_params | varchar(255) | YES | | | |
| creation_date | datetime | YES | | NULL | |
| expiration_date | datetime | YES | | NULL | |
+-------------------+------------------+------+-----+---------+----------------+
Run Code Online (Sandbox Code Playgroud)
notification_user_role
+-----------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+------------------+------+-----+---------+-------+
| user_role_id | varchar(150) | NO | | | |
| notification_id | int(10) unsigned | NO | MUL | NULL | |
+-----------------+------------------+------+-----+---------+-------+
Run Code Online (Sandbox Code Playgroud)
notification_status
+-------------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+------------------+------+-----+---------+-------+
| esb_consultant_id | varchar(20) | NO | | | |
| notification_id | int(10) unsigned | NO | MUL | NULL | |
+-------------------+------------------+------+-----+---------+-------+
Run Code Online (Sandbox Code Playgroud)
我用来检索结果的查询:
SELECT n.*, ns.notification_id AS is_read FROM notifications n
LEFT OUTER JOIN notification_status ns
ON n.id = ns.notification_id
LEFT JOIN notification_user_role nur
ON n.id = nur.notification_id
WHERE
(
n.esb_consultant_id = :consultant_id
OR
n.esb_consultant_id = :role_all
)
AND nur.user_role_id = :consultant_role
AND n.creation_date <= NOW()
AND n.expiration_date >= NOW()
ORDER BY n.creation_date DESC, (is_read IS NULL) DESC, n.priority ASC
LIMIT $offset, $limit
Run Code Online (Sandbox Code Playgroud)
$offset
是页面乘以 10 - 所以如果页面是 0(第一页)偏移量是 0,如果页面是 1(第一次 ajax 调用)偏移量是 10 等等
$limit
是极限,它总是 10。
:consultant_id
是用户 ID - 唯一
:role_all
是一个简单的字符串all
。它用于当某些通知适用于所有角色时(例如生日通知)。无论角色如何,所有用户都会收到此通知,因为他们都有生日。
问题:
每当我进行 ajax 调用时,我都会收到某些重复的通知。我只会发布它的屏幕截图,因为它比绘制它更容易。
请注意,ajax 本身只是我检索结果的方式的一部分,但不对它们自身的重复负责,我绝对确定。这也不是显示问题,我已经检查了两次和三次。
我注意到的是,如果我要删除这部分
ORDER BY n.creation_date DESC, (is_read IS NULL) DESC, n.priority ASC
从查询。它工作正常。没有重复。
上面查询的转储,删除了限制并删除了顺序:
对不起,图片更容易。
我正在使用 PHP 来查询数据库。
public function all($consultant_id, $consultant_role, $offset = 0) {
$limit = 10;
$offset = $offset * 10;
$query = <<<SQL
SELECT n.*, ns.notification_id AS is_read FROM notifications n
LEFT OUTER JOIN notification_status ns
ON n.id = ns.notification_id
LEFT JOIN notification_user_role nur
ON n.id = nur.notification_id
WHERE
(
n.esb_consultant_id = :consultant_id
OR
n.esb_consultant_id = :role_all
)
AND nur.user_role_id = :consultant_role
AND n.creation_date <= NOW()
AND n.expiration_date >= NOW()
ORDER BY n.creation_date DESC, (is_read IS NULL) DESC, n.priority ASC
LIMIT $offset, $limit
SQL;
$return = $this->connection
->query($query
, [
':consultant_role' => $consultant_role,
':consultant_id' => $consultant_id,
':role_all' => NotificationStatus::PL_N_ALL,
]
)->fetchAll(\PDO::FETCH_ASSOC);
foreach($return as $item) { // this is added simply for display purposes
echo $item['id'] . '<br>';
}
return $return;
}
Run Code Online (Sandbox Code Playgroud)
以上是用于检索结果的代码的复制+粘贴。该函数只返回显示结果,没有其他魔法。
将foreach
被添加到简单地显示在浏览器中的结果。
这是输出的图像。通知 10 重复。
这是完全相同的代码,只是
ORDER BY n.creation_date DESC, (is_read IS NULL) DESC, n.priority ASC
删除了。限制和偏移在这里仍然适用。
我一般不太擅长 mysql 或 sql,我不确定问题本身出在哪里。
任何指向正确方向的人都非常感谢。即使是变通方法或“黑客”我也能接受。
mysql> SELECT * FROM notification_status WHERE notification_id = 10;
Empty set (0.00 sec)
mysql> SELECT * FROM notification_user_role WHERE notification_id = 10;
+--------------------------------+-----------------+
| user_role_id | notification_id |
+--------------------------------+-----------------+
| pl_sso_regional_vice_president | 10 |
+--------------------------------+-----------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM notifications WHERE id = 10;
+----+---------+--------+----------+-------------------+-------------------------------------+------------+---------------------+---------------------+
| id | type_id | sticky | priority | esb_consultant_id | message_id | esb_params | creation_date | expiration_date |
+----+---------+--------+----------+-------------------+-------------------------------------+------------+---------------------+---------------------+
| 10 | 2 | 0 | 4 | role | pl_n_325f1676e8a263c86432edc7b9f09c | NULL | 2018-02-01 00:00:00 | 2018-02-28 00:00:00 |
+----+---------+--------+----------+-------------------+-------------------------------------+------------+---------------------+---------------------+
1 row in set (0.00 sec)
mysql>
Run Code Online (Sandbox Code Playgroud)
ype*_*eᵀᴹ 12
您是否有与 2 个或更多角色相关联的通知?这将解释重复的结果。
从模式中我们可以推断出一个通知可以与许多雕像相关联,也可以与许多角色相关联。因此,如果一个通知与许多(比如 3 个)角色相关联,那么它的每个状态都会出现很多 (3) 次。与 1 个角色关联的通知不显示任何重复。
我们还注意到该SELECT
列表不包括角色表中的任何列。这有助于解决方案:将JOIN
to table 角色转换为EXISTS
子查询。查询变为:
SELECT
n.*, ns.notification_id AS is_read
FROM
notifications n
LEFT OUTER JOIN notification_status ns
ON n.id = ns.notification_id
WHERE EXISTS
( SELECT 1
FROM notification_user_role nur
WHERE n.id = nur.notification_id
AND nur.user_role_id = :consultant_role
)
AND n.esb_consultant_id IN (:consultant_id, :role_all)
AND n.creation_date <= NOW()
AND n.expiration_date >= NOW()
ORDER BY
n.creation_date DESC, (is_read IS NULL) DESC, n.priority ASC
LIMIT
$offset, $limit
Run Code Online (Sandbox Code Playgroud)
另一个原因可能是您有许多通知状态。在这种情况下,解决方案取决于您想对此做什么。(即通知具有 2 个或更多状态意味着什么,它是否应该在结果中出现一次或两次等)。
(但在这种情况下这不是问题)
您可能会得到“重复”的第三个原因是您没有明确的(完整的)ORDER BY
子句。也就是说,当ORDER BY
无法解决所有关系时。在这种情况下,当有关系时(比如在位置 8 到 28),LIMIT 10 OFFSET 0
意思是“给我 10 行,按那个排序”。但是“那个”仅足以指定前 7 行。最后的第 8 个、第 9 个和第 10 个并列(还有 18 个!)因此 MySQL 决定并任意选择其中的 3 个。(仍然没有重复)
但是在下一个查询中,当您使用 请求第 11 到 20 行时OFFSET 10 LIMIT 10
,MySQL 会以不同的方式解决关系,并且可能会再次为您提供您之前看到的 3 行(现在显示为第 11、14 和 15 行!)。
为什么这样做?因为它可以,而且你还没有告诉她一个明确而完整的方法来做ORDER BY
.
顺便说一句,我不会称这些为“重复项”,因为它们出现在不同的结果集/调用中。稍有误导性的术语可能是无法轻易确定原因的原因。
然而,解决方案很简单。只需在子句中再添加一列(即unique)ORDER BY
:
ORDER BY
n.creation_date DESC, (is_read IS NULL) DESC, n.priority ASC,
n.id -- to resolve ties
Run Code Online (Sandbox Code Playgroud)
(或者,正如 OP 在评论和聊天室的讨论中澄清的那样,需要不同的顺序,最后仍然n.id
是 ):
ORDER BY
(is_read IS NULL) DESC, n.priority ASC, n.creation_date DESC,
n.id DESC
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
3431 次 |
最近记录: |