Mysql
 sql >> база данни >  >> RDS >> Mysql

Странно дублирано поведение от GROUP_CONCAT на две LEFT JOIN от GROUP_BY

Втората ви заявка е от вида:

q1 -- PK user_id
LEFT JOIN (...
    GROUP BY user_id, t.tag
) AS q2
ON q2.user_id = q1.user_id 
LEFT JOIN (...
    GROUP BY user_id, c.category
) AS q3
ON q3.user_id = q1.user_id
GROUP BY -- group_concats

Вътрешните GROUP BY водят до (user_id, t.tag) &(user_id, c.category) са ключове/УНИКАЛИ. Освен това няма да се обръщам към тези GROUP BYs.

TL;DR Когато се присъедините (q1 JOIN q2) към q3, той не е на ключ/УНИКАЛНО от един от тях, така че за всеки user_id получавате ред за всяка възможна комбинация от етикет и категория. Така че окончателната GROUP BY въвежда дубликати за (user_id, tag) и за (user_id, категория) и неподходящо GROUP_CONCATs дублиращи се тагове и категории за user_id. Правилно би било (q1 JOIN q2 GROUP BY) JOIN (q1 JOIN q3 GROUP BY), в което всички присъединявания са на общ ключ/УНИКАЛЕН (user_id) и няма фалшиво агрегиране. Въпреки че понякога можете да отмените такова фалшиво агрегиране.

Правилен симетричен подход INNER JOIN:LEFT JOIN q1 &q2--1:много--след това GROUP BY &GROUP_CONCAT (което направи първата ви заявка); след това отделно по подобен начин LEFT JOIN q1 &q3--1:много--след това GROUP BY &GROUP_CONCAT; след това INNER JOIN двата резултата ON user_id--1:1.

Правилен подход на симетрична скаларна подзаявка:ИЗБЕРЕТЕ GROUP_CONCAT от q1 като скаларни подзаявки всеки с GROUP BY.

Правилен кумулативен подход LEFT JOIN:LEFT JOIN q1 &q2--1:много--след това GROUP BY &GROUP_CONCAT; след това LEFT JOIN това &q3--1:много--след това GROUP BY &GROUP_CONCAT.

Правилен подход като вашата 2-ра заявка:Първо НАЛЯВЯТЕ ПРИСЪЕДИНЕТЕ q1 &q2--1:много. След това ОСТАВЯТЕ ПРИСЪЕДИНИ СЕ, че &q3--много:1:много. Той дава ред за всяка възможна комбинация от маркер и категория, които се появяват с user_id. След това, след като GROUP BY вие GROUP_CONCAT--над дублиращи се (user_id, tag) двойки и дублиращи се (user_id, category) двойки. Ето защо имате дублиращи се елементи от списъка. Но добавянето на DISTINCT към GROUP_CONCAT дава правилен резултат. (За wchiquito коментар на.)

Което предпочитате, както обикновено, е инженерен компромис, който да бъдете информирани от планове и тайминги на заявки, според действителните данни/използване/статистически данни. въвеждане и статистика за очаквано количество дублиране), време на действителните заявки и т.н. Един от проблемите е дали допълнителните редове на подхода много:1:много JOIN компенсират запазването му на GROUP BY.

-- cumulative LEFT JOIN approach
SELECT
   q1.user_id, q1.user_name, q1.score, q1.reputation,
    top_two_tags,
    substring_index(group_concat(q3.category  ORDER BY q3.category_reputation DESC SEPARATOR ','), ',', 2) AS category
FROM
    -- your 1st query (less ORDER BY) AS q1
    (SELECT
        q1.user_id, q1.user_name, q1.score, q1.reputation, 
        substring_index(group_concat(q2.tag  ORDER BY q2.tag_reputation DESC SEPARATOR ','), ',', 2) AS top_two_tags
    FROM
        (SELECT 
            u.id AS user_Id, 
            u.user_name,
            coalesce(sum(r.score), 0) as score,
            coalesce(sum(r.reputation), 0) as reputation
        FROM 
            users u
            LEFT JOIN reputations r 
                ON    r.user_id = u.id 
                  AND r.date_time > 1500584821 /* unix_timestamp(DATE_SUB(now(), INTERVAL 1 WEEK)) */
        GROUP BY 
            u.id, u.user_name
        ) AS q1
        LEFT JOIN
        (
        SELECT
            r.user_id AS user_id, t.tag, sum(r.reputation) AS tag_reputation
        FROM
            reputations r 
            JOIN post_tag pt ON pt.post_id = r.post_id
            JOIN tags t ON t.id = pt.tag_id
        WHERE
            r.date_time > 1500584821 /* unix_timestamp(DATE_SUB(now(), INTERVAL 1 WEEK)) */
        GROUP BY
            user_id, t.tag
        ) AS q2
        ON q2.user_id = q1.user_id 
        GROUP BY
            q1.user_id, q1.user_name, q1.score, q1.reputation
    ) AS q1
    -- finish like your 2nd query
    LEFT JOIN
    (
    SELECT
        r.user_id AS user_id, c.category, sum(r.reputation) AS category_reputation
    FROM
        reputations r 
        JOIN post_category ct ON ct.post_id = r.post_id
        JOIN categories c ON c.id = ct.category_id
    WHERE
        r.date_time > 1500584821 /* unix_timestamp(DATE_SUB(now(), INTERVAL 1 WEEK)) */
    GROUP BY
        user_id, c.category
    ) AS q3
    ON q3.user_id = q1.user_id 
GROUP BY
    q1.user_id, q1.user_name, q1.score, q1.reputation
ORDER BY
    q1.reputation DESC, q1.score DESC ;



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да съхранявате UTF8 знаци в MySQL

  2. Формат на датата на MySQL – това, което трябва да знаете

  3. MySQL условно вмъкване

  4. Как да получите записи за последните 15 дни в MySQL

  5. Изключване на единични кавички в PHP при вмъкване в MySQL