Есть ли способ заставить запрос MySQL select разрешить предложение has перед предложением where? - PullRequest
0 голосов
/ 20 апреля 2020

У меня есть несколько сложный MySQL запрос выбора, который объединяет несколько таблиц с несколькими левыми / внешними объединениями, некоторыми СЧЕТАМИ и выражениями в предложении HAVING и несколькими выражениями в предложении WHERE, которое вызывается один раз при каждой загрузке веб-страницу в моем веб-приложении, и снова с помощью AJAX с веб-страницы для автоматического просмотра данных из базы данных каждые 15 секунд. В зависимости от того, каким образом пользователь выполняет пейджинг, значения из таблицы на веб-странице, будь то первая строка для обратной страницы или последняя строка для прямой страницы, отправляются в вызове AJAX для загрузки предыдущей / следующей страницы. Выражение запроса создается с использованием php HereDo c, который использует подстановки переменных для установки значений, которые управляют предложением 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:

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

Проблема заключается в том, что для управления порядком данных, возвращаемых запросом, в подсчетах, приведенных выше, необходимо использовать предложение HAVING из-за COUNT , но это выполняется после предложения WHERE. Таким образом, вместо того, чтобы «забрать» из элемента, что определенные total_song_votes, track_votes, track_sales и track_listens, а затем имя песни и имя исполнителя в предложении WHERE, запрос сначала использует предложение WHERE, которое не может использовать голоса, поэтому только имеет заголовок и имя исполнителя, поэтому удаляет слишком много строк до использования предложения HAVING.

Как заставить запрос выполнить фильтрацию предложения HAVING в предложении WHERE?

Вывод запроса выбора выглядит примерно так:

<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 logi c. Также обратите внимание, что веб-страница автоматически повторно запрашивает базу данных, вызывая AJAX причину каждые 15 секунд, и что одна и та же страница может отображаться для нескольких пользователей, но не обязательно все видят одни и те же данные, так как некоторые могут перемещаться в обратном направлении через данные или воспроизведение песни, и в данный момент больше не выполняется подкачка страниц, поэтому я хочу, чтобы этот запрос был очень эффективным и не перезапрашивал всю базу данных при каждом запуске, а вместо этого выполнял выбор после последних результатов для каждого пользователя , независимо.

FOLLOW-UP

Астакс предложил мне заключить свой запрос в запрос SELECT * FROM (...), а затем выполните фильтрацию там.

Я оставил предложение HAVING во внутреннем SELECT, чтобы он сначала выполнял фильтрацию голосования и больше не извлекал данные во внешний SELECT * FROM ... затем он тоже. Я действительно не хочу пробираться по всему набору данных больше, чем нужно.

На самом деле, я переместил название дорожки и имя исполнителя во внешнее предложение SELECT WHERE, но затем обнаружил, что это по-прежнему приводит к удалению слишком большого числа строк:

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

Поэтому я изменил выражение this на :

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

Где переменные title0, name0 - title9, $ name9 заменяются десятью частями заголовков и имен, отображаемых на странице, которая выстраивается, прежде чем запрос SQL будет отправлен в базу данных engine.

Проблема, однако, заключается в том, что если одни и те же голоса совпадают по более чем десяти песням (одна веб-страница песен), то песни с предыдущих страниц будут отображаться снова, и пейджинг перестанет работать .

Эта проблема делает использование голосов, названий песен и имени исполнителя недостаточным в качестве реального решения. Мне нужно что-то еще, что я могу использовать для отслеживания разрывов страниц, которые по-прежнему позволяют видеть изменения, внесенные в базу данных сервера, без больших накладных расходов с точки зрения временных таблиц, которые либо живут на устройстве пользователя, либо на сервере.

Любые идеи о том, как я могу управлять подкачкой и по-прежнему поддерживать динамические c данные, как я описал?

Спасибо

1 Ответ

0 голосов
/ 20 апреля 2020

Основная причина существования 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}

You может все еще добавить WHERE к внутреннему запросу для не сгруппированных полей для обработки меньшего количества данных. Просто используйте не строгие условия - >= или <= вместо > и <. Дальнейшая фильтрация будет выполнена во внешнем выборе.

Но я повторюсь еще раз - проверьте вывод EXPLAIN для вашего запроса. Очень вероятно, что вы ничего не сохраняете, используя условия вместо LIMIT.

...