Mysql
 sql >> Teknologi Basis Data >  >> RDS >> Mysql

Perilaku duplikat yang aneh dari GROUP_CONCAT dari dua LEFT JOINs dari GROUP_BYs

Permintaan kedua Anda dalam bentuk:

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 bagian dalam menghasilkan (user_id, t.tag) &(user_id, c.category) menjadi kunci/UNIK. Selain itu, saya tidak akan membahas GROUP BY tersebut.

TL;DR Ketika Anda bergabung (q1 GABUNG q2) ke q3, itu bukan pada kunci/UNIK salah satunya sehingga untuk setiap user_id Anda mendapatkan baris untuk setiap kemungkinan kombinasi tag &kategori. Jadi GROUP BY terakhir memasukkan duplikat per (user_id, tag) &per (user_id, kategori) dan secara tidak tepat GROUP_CONCAT menduplikasi tag &kategori per user_id. Yang benar adalah (q1 JOIN q2 GROUP BY) JOIN (q1 JOIN q3 GROUP BY) di mana semua join berada pada kunci yang sama/UNIQUE (user_id) &tidak ada agregasi palsu. Meskipun terkadang Anda dapat membatalkan agregasi palsu tersebut.

Pendekatan INNER JOIN simetris yang benar:LEFT JOIN q1 &q2--1:many--then GROUP BY &GROUP_CONCAT (yang dilakukan oleh kueri pertama Anda); kemudian secara terpisah sama LEFT JOIN q1 &q3--1:many--lalu GROUP BY &GROUP_CONCAT; lalu INNER JOIN dua hasil ON user_id--1:1.

Pendekatan subkueri skalar simetris yang benar:PILIH GROUP_CONCAT dari q1 sebagai subkueri skalar masing-masing dengan GROUP BY.

Pendekatan LEFT JOIN kumulatif yang benar:LEFT JOIN q1 &q2--1:many--then GROUP BY &GROUP_CONCAT; lalu KIRI GABUNG itu &q3--1:banyak--lalu GROUP BY &GROUP_CONCAT.

Pendekatan yang benar seperti kueri ke-2 Anda:Anda pertama-tama LEFT JOIN q1 &q2--1:many. Kemudian Anda KIRI GABUNG itu &q3--banyak:1:banyak. Ini memberikan baris untuk setiap kemungkinan kombinasi tag &kategori yang muncul dengan user_id. Kemudian setelah Anda GROUP BY Anda GROUP_CONCAT--over duplikat (user_id, tag) pasangan dan duplikat (user_id, kategori) pasangan. Itulah mengapa Anda memiliki elemen daftar duplikat. Tetapi menambahkan DISTINCT ke GROUP_CONCAT memberikan hasil yang benar. (Per wchiquito komentar.)

Yang Anda sukai adalah seperti biasa tradeoff teknik untuk diinformasikan oleh rencana &waktu kueri, per data/penggunaan/statistik aktual. masukan &statistik untuk jumlah duplikasi yang diharapkan), waktu kueri aktual, dll. Satu masalah adalah apakah baris tambahan dari pendekatan many:1:many JOIN mengimbangi penghematannya dari 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. Cara Memeriksa Ukuran Semua Tabel dalam Database di MySQL

  2. Membandingkan Percona XtraBackup dengan MySQL Enterprise Backup:Bagian Satu

  3. Perubahan zona waktu MySQL?

  4. Subkueri vs bergabung

  5. Kesalahan MySQL 1064 sintaks tetapi semuanya tampak baik-baik saja