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

时间:2020-04-20 09:51:08

标签: php mysql pagination where-clause having-clause

我有一个稍微复杂的MySQL Select查询,它通过多个左/外部联接,HAVING子句中的一些COUNT和表达式以及WHERE子句中的多个表达式来联接多个表,每次加载Web-时都会调用一次我的网络应用程序中的“页面”,然后再次使用该页面中的AJAX来每15秒自动浏览一次数据库中的数据。根据用户进行分页的方式,网页表格中的值(用于后向分页的第一行或用于正向分页的最后一行)将在AJAX调用中发送以加载上一页/下一页。查询表达式是使用php HereDoc创建的,该PHP使用变量替换来设置控制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子句过滤?

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逻辑删除其余的行。还要注意,该网页通过使AJAX每15秒引起一次来自动重新查询数据库,并且同一页面可以显示给多个用户,但不一定所有人都看到相同的数据,因为有些页面可能会向后翻阅数据或播放歌曲并且此刻不再播放歌曲,因此我希望此查询非常有效,并且不要在每次运行时都重新查询整个数据库,而是让每个用户独立地从最后一个结果之后进行查询

跟进

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变量替换为页面上显示的十个标题和名称对。

但是,问题是,如果十首以上的歌曲(一首歌的网页)上的票数相同,那么前几页中的歌曲将再次显示,并且分页将停止工作。

此问题导致无法将投票,歌曲标题和艺术家的名字用作现实解决方案。我还需要其他东西来跟踪我的分页符,这些分页符仍然可以显示在服务器数据库中所做的更改,而无需占用用户设备或服务器上的临时表的大量开销。

关于我如何管理分页并仍然支持动态数据的任何想法?

谢谢

1 个答案:

答案 0 :(得分:0)

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来保存任何内容。