Агрегированные результаты из подзапроса в MySQL не сохраняют одно из моих полей - PullRequest
0 голосов
/ 11 октября 2019

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

У меня есть три таблицы: одна - набор user_actions, который можно отслеживать в веб-приложении, другая содержит user_events экземпляров указанного действия и третья - player_keys, содержит идентификаторы сеанса, которые используются для привязки нескольких user_events к одному сеансу.

Из-за необычного способа определения user_actions определенный экземпляр user_action иногда идентифицируется с помощью quantity значение, которое передается вместе с ним, и иногда каждый возможный результат имеет свой собственный user_action.

Так, учитывая серию user_actions ...

+----+-------------------+-------------+
| id |       NAME        | campaign_id |
+----+-------------------+-------------+
| 15 | Theme Vote Age    |         301 |
| 18 | Theme Vote Gender |         301 |
| 20 | Theme 5 Selected  |         301 |
+----+-------------------+-------------+

... Iиметь user_events вот так

+---------------+----------------+----------+---------------------+
| player_key_id | user_action_id | quantity |      created_at     |
+---------------+----------------+----------+---------------------+
|           123 |             15 |       50 | 2019-10-11 12:34:56 |
|           123 |             18 |        2 | 2019-10-11 12:34:57 |
|           123 |             20 |        1 | 2019-10-11 12:34:58 |
+---------------+----------------+----------+---------------------+

Мой запрос представляет собой двухэтапный процесс с помощью подзапроса: сначала я запрашиваю идентификаторы ключей игрока и номер голоса темы в подзапросе, а затем соединяю его с другим запросом, которыйищет другие строки FИз этих идентификаторов ключей игрока (т.е. пользовательских сессий) добавить в два других поля, которые я хочу (переписать, чтобы использовать синтаксис INNER JOIN, как я думаю, Гордон предложил в [своем комментарии], ( Агрегированные результаты подзапроса в MySQL don ')t сохраните одно из моих полей ), используйте оператор CASE согласно Эрику и удалите лишнее соединение в таблице player_keys, для tcadidot ):

SELECT ue.player_key_id, vd.theme_vote,
    max(if(ua.name = 'Theme Vote Age', quantity, 0)) as theme_age,
    max(if(ua.name = 'Theme Vote Gender', quantity, 0)) as theme_gender
FROM user_events AS ue
INNER JOIN user_actions ua
    ON ua.id = ue.user_action_id
INNER JOIN (
    SELECT ue.player_key_id AS player_key_id, 
        max(CASE ua.name
            WHEN 'Theme 1 Selected' THEN 1
            WHEN 'Theme 2 Selected' THEN 2
            WHEN 'Theme 3 Selected' THEN 3
            WHEN 'Theme 4 Selected' THEN 4
            WHEN 'Theme 5 Selected' THEN 5
            ELSE 6
        END) as theme_vote
    FROM user_events ue
    INNER JOIN user_actions ua
        ON ue.user_action_id = ua.id
    WHERE ua.campaign_id = 301
        AND ua.name LIKE 'Theme % Selected'
        AND date(ue.created_at) = current_date
    GROUP BY ue.player_key_id
    ) vd
    ON ue.player_key_id = vd.player_key_id
WHERE (ua.name = 'Theme Vote Age' OR ua.name = 'Theme Vote Gender')
GROUP BY ue.player_key_id
HAVING theme_age > 1 AND theme_age < 100 AND theme_gender != 3;

Я думаю, что внутренний запрос GROUP BY необходим, потому что каждый сеанс, сгруппированный по player_key_id, содержит один набор данных голосования по теме (включая тот, который соответствует выбранной теме%). Внешний запрос снова группируется по player_key_id, потому что каждая сессия содержит один «Возраст голосования по темам» и «Пол голоса по темам» user_action, который я просмотрел и получил две строки для каждой сессии.

То, что я ожидаю получить обратно, это что-то вроде

+---------------+------------+-----------+--------------+
| player_key_id | theme_vote | theme_age | theme_gender |
+---------------+------------+-----------+--------------+
|           123 |          5 |        50 |            2 |
+---------------+------------+-----------+--------------+
|           163 |          1 |        37 |            1 |
+---------------+------------+-----------+--------------+
|           748 |          2 |        28 |            1 |
+---------------+------------+-----------+--------------+

Но я получаю обратно

+---------------+------------+-----------+--------------+
| player_key_id | theme_vote | theme_age | theme_gender |
+---------------+------------+-----------+--------------+
|           123 |          6 |        50 |            2 |
+---------------+------------+-----------+--------------+
|           163 |          6 |        37 |            1 |
+---------------+------------+-----------+--------------+
|           748 |          6 |        28 |            1 |
+---------------+------------+-----------+--------------+

Так что, в общем-то, что-то превращается theme_vote в 6 черезвсе группировки. Мой подзапрос сам по себе работает нормально. Он возвращает строки, подобные ожидаемым, где theme_vote охватывает 1-6:

+---------------+------------+
| player_key_id | theme_vote |
+---------------+------------+
|           123 |          5 |
+---------------+------------+
|           724 |          2 |
+---------------+------------+
|           833 |          3 |
+---------------+------------+
|           298 |          2 |
+---------------+------------+
|           529 |          6 |
+---------------+------------+

Выполнение этого в разные дни возвращает разные объемы данных, предполагая, что работает фильтр даты, и фильтр campaign_idтакже успешен.

Количество строк, возвращаемых подзапросом, также значительно меньше, чем весь запрос.

См. здесь: fiddle: https://www.db -fiddle.com / f /8U2WoHG7tzimFbEZP956kq / 0

Для дальнейшего пояснения я использую GROUP BY во внешнем запросе, чтобы превратить это:

+---------------+------------+-----------+--------------+
| player_key_id | theme_vote | theme_age | theme_gender |
+---------------+------------+-----------+--------------+
|      12160443 |          1 |        33 |            0 |
|      12160443 |          1 |         0 |            2 |
+---------------+------------+-----------+--------------+

... в это:

+---------------+------------+-----------+--------------+
| player_key_id | theme_vote | theme_age | theme_gender |
+---------------+------------+-----------+--------------+
|      12160443 |          1 |        33 |            2 |
+---------------+------------+-----------+--------------+

Ответы [ 2 ]

1 голос
/ 11 октября 2019

Согласно собственной документации MySQL относительно GROUP BY в стандартах SQL-1992 и SQL-1999:

SQL-92 и более ранние версии не разрешают запросы, для которыхСписок SELECT, условие HAVING или список ORDER BY относятся к неагрегированным столбцам, которые не названы в предложении GROUP BY.

SQL: 1999 и более поздние версии разрешают такие неагрегаты для дополнительной функции T301, если онифункционально зависят от столбцов GROUP BY:

Поэтому ваш внешний запрос не соответствует обоим стандартам SQL по двум причинам: (1) неагрегированный столбец, vd.theme_vote, не указан в спискев предложении GROUP BY и (2) этот же столбец не является функционально зависимым от включенного столбца ue.player_key_id, основанного на источнике и имени таблицы.

SELECT ue.player_key_id, vd.theme_vote,
...
GROUP BY ue.player_key_id                                          -- MISSING COLUMN
HAVING theme_age > 1 AND theme_age < 100 AND theme_gender != 3;

К сожалению, в MySQL *ONLY_FULL_GROUP_ON режим отключен позволяет вышеуказанному запросу работать без ошибок и даже допускает, что он не придерживается функциональной зависимости, как описано ниже (выделение добавлено), поэтому вы получаетеассортимент, неожиданные результаты.

Если ONLY_FULL_GROUP_BY отключено, расширение MySQL для стандартного использования SQL GROUP BY позволяет списку SELECT, условию HAVING или ORDER BY ссылаться на неагрегированные столбцы, даже если столбцы НЕ функционально зависят от GROUP BY столбцов . Это заставляет MySQL принять предыдущий запрос. В этом случае сервер может свободно выбирать ЛЮБОЕ значение из каждой группы , поэтому, если они не совпадают, выбранные значения являются недетерминированными, что, вероятно, не то, что вам нужно.


Поэтому рассмотрите возможность соответствия стандарту SQL и правильно запустите агрегированный запрос. Если вы собираетесь использовать одно theme_vote значение на ue.player_key_id, запустите агрегацию MAX на внешнем уровне.

-- CONVERT SELECT COLUMN TO AGGREGATE CALCULATION
SELECT ue.player_key_id, 
       MAX(vd.theme_vote) as theme_vote,
       MAX(if(ua.name = 'Theme Vote Age', quantity, 0)) as theme_age,
       MAX(if(ua.name = 'Theme Vote Gender', quantity, 0)) as theme_gender
FROM user_events AS ue
INNER JOIN user_actions ua
    ON ua.id = ue.user_action_id
INNER JOIN (
    -- NO AGGREGATION IN SUBQUERY
    SELECT ue.player_key_id AS player_key_id, 
           CASE ua.name
               WHEN 'Theme 1 Selected' THEN 1
               WHEN 'Theme 2 Selected' THEN 2
               WHEN 'Theme 3 Selected' THEN 3
               WHEN 'Theme 4 Selected' THEN 4
               WHEN 'Theme 5 Selected' THEN 5
               ELSE 6
           END) as theme_vote
    FROM user_events ue
    INNER JOIN user_actions ua
        ON ue.user_action_id = ua.id
    WHERE ua.campaign_id = 301
        AND ua.name LIKE 'Theme % Selected'
        AND date(ue.created_at) = current_date
    ) vd
    ON ue.player_key_id = vd.player_key_id
WHERE (ua.name = 'Theme Vote Age' OR ua.name = 'Theme Vote Gender')
GROUP BY ue.player_key_id
HAVING theme_age > 1 
   AND theme_age < 100 
   AND theme_gender != 3;

В качестве альтернативы использование CTE с предложением WITH , новым дополнением к MySQL 8:

WITH vd AS (
  -- NO AGGREGATION IN SUBQUERY
  ...same subquery...
)

-- CONVERT SELECT COLUMN TO AGGREGATE CALCULATION
SELECT ue.player_key_id, 
       MAX(vd.theme_vote) as theme_vote,
       MAX(if(ua.name = 'Theme Vote Age', quantity, 0)) as theme_age,
       MAX(if(ua.name = 'Theme Vote Gender', quantity, 0)) as theme_gender
FROM user_events AS ue
INNER JOIN user_actions ua
    ON ua.id = ue.user_action_id
INNER JOIN vd
    ON ue.player_key_id = vd.player_key_id
WHERE (ua.name = 'Theme Vote Age' OR ua.name = 'Theme Vote Gender')
GROUP BY ue.player_key_id
HAVING theme_age > 1 
   AND theme_age < 100 
   AND theme_gender != 3;
1 голос
/ 11 октября 2019

Это не ответ, но его слишком долго комментировать. Что произойдет, если вы измените свой внутренний запрос на это?

SELECT ue.player_key_id
    , (
        CASE ua.name
            WHEN 'Theme 1 Selected' THEN 1
            WHEN 'Theme 2 Selected' THEN 2
            WHEN 'Theme 3 Selected' THEN 3
            WHEN 'Theme 4 Selected' THEN 4
            WHEN 'Theme 5 Selected' THEN 5
            ELSE 6
        END
) AS theme_vote
FROM user_events ue
INNER JOIN user_actions ua ON ue.user_action_id = ua.id
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...