【发布时间】:2020-04-20 21:43:25
【问题描述】:
我有一个有点复杂的 MySQL Select 查询,它用几个左/外连接连接多个表,HAVING 子句中的一些 COUNT 和表达式,以及 WHERE 子句中的几个表达式,每次加载 web 时都会调用一次-我的网络应用程序中的页面,并再次使用网页中的 AJAX 以每 15 秒自动翻阅数据库中的数据。根据用户分页的方式,来自网页中表格的值(用于向后分页的第一行或用于正向分页的最后一行)在 AJAX 调用中发送以加载上一页/下一页。查询表达式是使用 php HereDoc 创建的,它使用变量替换来设置控制 WHERE 和 HAVING 子句的值,并首先按投票排序返回数据,然后是标题和艺术家姓名,如下所示:
WHERE ...
AND ( ( `tracks`.`title`, `artists`.`name` ) > ( ${title}, ${name} ) )
-- This line is not included in the initial query when the page is first created/loaded, but is
-- used in the AJAX called.
ORDER BY
-- Total_Songs_Votes, Track_Sales, Track_Votes, Track_Listens, Tracks_Title, Artists_Name
3 DESC, 5 DESC, 4 DESC, 6 DESC, `tracks`.`title`, `artists`.`name`
HAVING COUNT( `track_votes`.`id` ) <= ${votes}
AND COUNT( `track_sales`.`id` ) <= ${sales}
AND COUNT( `track_listens`.`id` ) <= ${listens}
选择输出列是:
SELECT `artists`.`name` AS artists_name,
`tracks`.`title` AS tracks_title,
COUNT( `track_votes`.`id` ) +
COUNT( `track_sales`.`id` ) +
COUNT( `track_listens`.`id` ) AS 'total_song_votes', -- Order By column 3
COUNT( `track_votes`.`id` ) AS 'track_votes', -- Order By column 4
COUNT( `track_sales`.`id` ) AS 'track_sales', -- Order By column 5
COUNT( `track_listens`.`id` ) AS 'track_listens' -- Order By column 6
问题在于,为了控制查询返回的数据,上面的计数由于 COUNT 需要使用 HAVING 子句,但这是在 WHERE 子句之后执行的。因此,不是从某些 total_song_votes、track_votes、track_sales 和 track_listens 的项目中“挑选”,然后是 WHERE 子句中的歌曲名和艺术家名,而是首先使用不能使用投票的 WHERE 子句,所以只有有标题和艺术家的名字,所以在使用的 HAVING 子句之前消除了太多行。
如何强制查询在 WHERE 子句中执行 HAVING 子句过滤?
选择查询的输出类似于以下内容:
<table border=1>
<tr>
<td bgcolor=silver class='medium'>artists_name</td>
<td bgcolor=silver class='medium'>tracks_title</td>
<td bgcolor=silver class='medium'>total_song_votes</td>
<td bgcolor=silver class='medium'>track_votes</td>
<td bgcolor=silver class='medium'>track_sales</td>
<td bgcolor=silver class='medium'>track_listens</td>
</tr>
<tr>
<td class='normal' valign='top'>DHF</td>
<td class='normal' valign='top'>T Song</td>
<td class='normal' valign='top'>4</td>
<td class='normal' valign='top'>2</td>
<td class='normal' valign='top'>2</td>
<td class='normal' valign='top'>0</td>
</tr>
<tr>
<td class='normal' valign='top'>DHF</td>
<td class='normal' valign='top'>H Song</td>
<td class='normal' valign='top'>2</td>
<td class='normal' valign='top'>1</td>
<td class='normal' valign='top'>1</td>
<td class='normal' valign='top'>0</td>
</tr>
<tr>
<td class='normal' valign='top'>DHF</td>
<td class='normal' valign='top'>A Song 2</td>
<td class='normal' valign='top'>1</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>1</td>
<td class='normal' valign='top'>0</td>
</tr>
<tr>
<td class='normal' valign='top'>DHF</td>
<td class='normal' valign='top'>A Song 1</td>
<td class='normal' valign='top'>1</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>1</td>
<td class='normal' valign='top'>0</td>
</tr>
<tr>
<td class='normal' valign='top'>DB</td>
<td class='normal' valign='top'>killer song</td>
<td class='normal' valign='top'>1</td>
<td class='normal' valign='top'>1</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
</tr>
<tr>
<td class='normal' valign='top'>DB</td>
<td class='normal' valign='top'>Kills it</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
</tr>
<tr>
<td class='normal' valign='top'>DB</td>
<td class='normal' valign='top'>scarry song</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
</tr>
<tr>
<td class='normal' valign='top'>TB</td>
<td class='normal' valign='top'>Reggae All Day</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
</tr>
<tr>
<td class='normal' valign='top'>TB</td>
<td class='normal' valign='top'>Reggae Just Today</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
</tr>
<tr>
<td class='normal' valign='top'>Howard's Band</td>
<td class='normal' valign='top'>test</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
<td class='normal' valign='top'>0</td>
</tr>
</table>
所以如果第一页只显示前两行,那么检索接下来两首歌曲的 AJAX 调用将发送 'DHF', 'H Song', 2, 1, 1, 0 回服务器和 WHERE和 HAVING 子句将被更改为包括以下内容:
WHERE ...
AND ( ( `tracks`.`title`, `artists`.`name` ) > ( "H Song", "DHF" ) )
HAVING COUNT( `track_votes`.`id` ) <= 1
AND COUNT( `track_sales`.`id` ) <= 1
AND COUNT( `track_listens`.`id` ) <= 0
所以 DHF 歌曲 A Song 1 和 A Song 2 都会被错误地淘汰。
这里的目标是检索网页上显示的最后一首歌曲之后的下两首歌曲*,使用投票值、歌曲标题和艺术家姓名,需要使用 WHERE 按顺序获取条款。正如我在上面所说的,WHERE 子句首先只考虑歌曲名称和艺术家的姓名,然后将票数较低但标题和名称较高的歌曲错误地排除在外。
- 我在这里只使用两行,因为更容易显示此示例的数据,在实际页面中,一次显示 10 个项目,但这并不会真正影响任何内容,除了数据之间的中断位置页面。
请注意,我希望从查询中返回大量项目,因此我希望首先让查询正确删除尽可能多的行,然后再使用 php 逻辑删除剩余的行。另请注意,网页通过每 15 秒触发一次 AJAX 自动重新查询数据库,并且同一页面可以显示给多个用户,但不一定所有人都看到相同的数据,因为有些人可能会向后翻页数据或播放一首歌曲,此时不再分页,所以我希望这个查询非常有效,而不是每次运行时都重新查询整个数据库,而是让它在每个用户的最后一个结果之后独立地提取.
跟进
Astax 建议我将查询包含在 SELECT * FROM (...) 查询中,然后在那里进行过滤。
我将 HAVING 子句留在了内部 SELECT 中,以便它首先执行投票过滤,并且不再将任何数据检索到外部 SELECT * FROM ... 然后它也有。我真的不想过多地浏览整个数据集。
其实我把曲目标题和艺术家的名字移到了外层SELECT的WHERE子句中,但是后来发现还是导致了太多行被淘汰:
WHERE ( ( `tracks`.`title`, `artists`.`name` ) > ( ${title}, ${name} ) )
所以我把这个表达式改成:
WHERE ( ( `tracks`.`title`, `artists`.`name` ) not in
( ( ${title0}, ${name0} ), ... ( ${title9}, ${name9} ) ) )
在将 SQL 查询提交给数据库引擎之前,title0、name0 到 title9、$name9 变量被替换为页面上显示的十组标题和名称。
但问题是,如果超过十首歌曲(一个歌曲网页)的相同投票数相同,那么之前页面中的歌曲将再次显示并且分页将停止工作。
这个问题使得使用投票、歌曲标题和艺术家的名字作为现实世界的解决方案是不够的。我需要其他可以用来跟踪我的分页符的东西,它仍然允许查看服务器数据库中所做的更改,而不会在用户设备或服务器上的临时表方面产生大量开销。
关于如何管理分页并仍然支持我所描述的动态数据的任何想法?
谢谢
【问题讨论】:
-
问题是
HAVING对聚合结果起作用,聚合结果是从应用WHERE后剩下的内容生成的;如果您可以将它们结合起来,您将不再需要使用HAVING。相反,您能否将HAVING中的所有内容从 GROUP 中移出并放入专用列?这样您就可以对这些列应用索引,并将它们作为常规过滤器移动到您的WHERE语句中。
标签: php mysql pagination where-clause having-clause