有没有办法强制 MySQL select 查询解析 where 子句之前的 having 子句?

Is there a way to force a MySQL select query to resolve the having clause before the where clause?

我有一个有点复杂的 MySQL Select 查询,它将多个 table 与几个 left/outer 连接、一些 COUNT 和 HAVING 子句中的表达式以及几个WHERE 子句中的表达式,每次我在我的 web-app 中加载 web-page 时调用一次,并再次使用 web-page 中的 AJAX 自动分页来自每 15 秒检查一次数据库。根据用户分页的方式,web-page 中 table 的值,无论是向后分页的第一行还是向前分页的最后一行,都在 AJAX 调用中发送加载 previous/next 页面。查询表达式是使用 php HereDoc 创建的,它使用变量替换来设置控制 WHERE 和 HAVING 子句的值,并首先按投票对 return 数据进行排序,然后是标题和艺术家姓名,如下所示:

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 输出列为:

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

问题是,为了控制数据 return 由查询计数,上面,由于 COUNT,需要使用 HAVING 子句,但这是在 WHERE 子句之后执行的。因此,而不是 'picking up' 来自某些 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都会被错误淘汰

这里的目标是检索 web-page 上显示的最后一首歌曲之后的下两首歌曲*,使用投票值、歌曲标题和艺术家姓名,这些都需要在那个使用 WHERE 子句排序。正如我在上面所说的,WHERE 子句首先只考虑歌曲名称和艺术家姓名,然后票数较低但标题和名称较高的歌曲被错误地排除。

请注意,我希望从查询中 return 编辑大量项目,因此我想先让查询正确删除尽可能多的行,然后再使用 php逻辑。另请注意,web-page 通过使 AJAX 每隔 15 秒自动 re-queries 数据库,并且同一页面可以显示给多个用户,但不一定所有人都看到与某些用户相同的数据可能正在通过数据向后分页或播放歌曲并且现在不再分页,所以我希望这个查询非常有效而不是 re-query 整个数据库每次都是 运行,而是让它独立地从每个用户的最后一个结果中提取。

FOLLOW-UP

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} ) ) )

在 SQL 查询提交到数据库引擎之前,title0、name0 到 title9、$name9 变量被 paged-out 页面上显示的十段标题和名称替换.

但问题是,如果超过十首歌曲(其中一首 web-page 的歌曲)的相同票数相同,那么之前页面的歌曲将再次显示并且分页将停止工作。

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

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

谢谢

存在HAVING的主要原因是在WHERE条件和所有聚合之后应用一些过滤器。所以答案是否定的,你不能在 WHERE.

之前做到 运行

但是,您可以:

  • 使用临时 table。将一个 select 的结果放入临时 table,添加来自其他 select 的更多数据,然后 运行 最终查询。
  • 使用嵌套的 selects,类似于 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 添加到非分组字段的内部查询中以处理更少的数据。只需使用不严格的条件 - >=<= 而不是 ><。进一步的过滤将在外部 select 中完成。

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