有没有一种方法可以强制MySQL选择查询解析where子句之前的have子句



我有一个有点复杂的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

问题是,为了控制查询返回的数据,由于counts,上面的计数需要使用HAVING子句,但这是在WHERE子句之后执行的。因此,查询不是从WHERE子句中的某个项目中"提取"特定的total_song_votes、track_votes,track_sales和track_listens,然后是歌曲名称和艺术家名称,而是首先使用不能使用投票的WHERE子句,因此只有标题和艺术家的名称,因此在使用HAVING子句之前删除了太多行。

如何强制查询在WHERE子句中执行HAVING子句筛选

select查询的输出看起来有点像以下内容:

<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(…)查询中,然后在那里进行筛选。

我在内部SELECT中保留了HAVING子句,这样它就可以先执行投票筛选,而不会再向外部SELECT*FROM检索任何数据。。。那么它也有。我真的不想费力地浏览整个数据集。

事实上,我把曲目标题和艺术家的名字移到了外部SELECT的WHERE子句中,但后来发现它仍然会导致太多的行被省略:

WHERE ( ( `tracks`.`title`,  `artists`.`name` ) > ( ${title}, ${name} ) )

所以我把这个表达式改成:

WHERE ( ( `tracks`.`title`,  `artists`.`name` ) not in
( ( ${title0}, ${name0} ), ... ( ${title9}, ${name9} ) ) )

其中,title0、name0到title9、$name9变量被替换为SQL查询提交到数据库引擎之前页面上显示的十组标题和名称。

然而,问题是,如果在十首以上的歌曲(一个歌曲网页)中相同的投票相同,那么前几页的歌曲将再次显示,页面将停止工作。

这个问题使得使用投票、歌曲标题和艺术家姓名不足以作为现实世界的解决方案。我需要一些可以用来跟踪分页符的其他东西,它仍然允许查看服务器数据库中所做的更改,而不会在用户设备或服务器上的临时表方面产生大量开销。

关于如何管理分页并仍然支持我所描述的动态数据,有什么想法吗

感谢

HAVING存在的主要原因是在WHERE条件和所有聚合之后应用一些筛选器。所以答案是否定的,你不能让它在WHERE之前运行。

但是,您可以:

  • 使用临时表。将一个选择的结果放入临时表,添加其他选择的更多数据,然后运行最终查询
  • 使用嵌套选择,类似于SELECT ... FROM (SELECT .... WHERE ... HAVING ....) as t WHERE ... HAVING ...

更新:

您不需要使用条件来实现分页。使用LIMIT语句——这就是它的用途。

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
LIMIT {$page_size} OFFSET {$page_start}

或者,如果你有一些使用条件的理由,你可以使用嵌套查询:

SELECT * FROM 
(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
) AS t
WHERE (t.track_title, t.artist_name, t.total_song_votes...) > ({$title}, {$name}, {$votes}...)
LIMIT {$page_size}

您仍然可以将WHERE添加到未分组字段的内部查询中,以处理较少的数据。只是使用不严格的条件——>=<=,而不是><。将在外部选择中进行进一步筛选。

但我再次重复一遍——请检查EXPLAIN的输出以了解您的查询。很可能你没有通过使用条件而不是LIMIT来保存任何东西。

最新更新