2017-01-05 1 views
1

Mit über 1800 getaggten Fragen zu greatest-n-per-group und einigen exzellenten Antworten dachte ich, ich würde eine Lösung für dieses Problem finden - aber ich habe es entweder verpasst Die Lösung oder ich brauche einen neuen Ansatz.JOIN mit `greatest-n-pro-group` und SUM in Unterabfrage

Ich habe eine Tabelle photo_types, um Stimmen von user zu speichern, die abstimmen (oben oder unten) auf, was bestimmte photo_type sie denken, dass ein bestimmtes Foto ist. Fototypen sind 1-10 und jede Stimme wird 1 oder -1 sein.

+----+-----+-----------+------------+------+ 
| id | user | photo_id | photo_type | vote | 
+----+------+----------+------------+------+ 
| 1 | jane | photo1 |   1 | 1 | 
| 2 | jane | photo2 |   2 | 1 | 
| 3 | jane | photo3 |   4 | -1 | 
| 4 | ben | photo1 |   1 | 1 | 
| 5 | ben | photo2 |   3 | -1 | 
| 6 | ben | photo2 |   2 | 1 | 
| 7 | mary | photo1 |   1 | -1 | 
| 8 | mary | photo3 |   10 | 1 | 
| 9 | mary | photo2 |   1 | 1 | 
| 10 | mary | photo1 |   2 | -1 | 
+----+------+----------+------------+------+ 

Ich muss links auf diese Tabelle zu einem photos Tisch kommen zurück (das alle anderen Details eines bestimmten Foto hat) - aber sind nur die Besten 2 Arten gewählt für jedes Foto.

Die photos Tabelle, auf die ich brauche die photo_types Tabelle sieht LEFT JOIN wie:

+----+----------+------------+----------------+---------------+------------+ 
| id | photo_id | photo_name | photographer |  location |  date | 
+----+----------+------------+----------------+---------------+------------+ 
| 1 | photo1 | the bridge | Bill Murray | Brooklyn, NY | 2012-10-11 | 
| 2 | photo2 | the cat | Jacques Chirac | Paris, France | 2013-01-03 | 
| 3 | photo3 |  a car |  the Grinch | London, UK | 2016-09-01 | 
+----+----------+------------+----------------+---------------+------------+ 

ich natürlich bin die beiden Tabellen über photo_id verbinden.

die oben stimmten Typen für jedes Foto, um erhalte ich eine Unter Abfrage wie folgt versucht:

SELECT photo_id, photo_type, sum(vote) AS votes 
FROM photo_types 
GROUP BY photo_type, photo_id 
HAVING votes>0 
ORDER BY votes DESC 

welche Gruppen die Summe der Stimmen durch photo_type sowie photo_id.
Dies funktioniert gut, aber enthält alle Typen mit sum(vote) > 0 - nicht nur die Top-2-Stimmen.
SQL Fiddle here

Wenn in der Join enthalten sie wie folgt aussieht:

SELECT * 
FROM photos 
LEFT JOIN 
    (SELECT photo_id, photo_type, sum(vote) AS votes 
    FROM photo_types 
    GROUP BY photo_type, photo_id 
    HAVING votes>0 
    ORDER BY votes DESC) AS pt 
ON photos.photo_id = pt.photo_id 
WHERE photos.date > '2010-01-01'; 

SQL Fiddle here

ich gehofft hatte Bill Karwin's solution zu verwenden, aber ich habe Probleme mit der Tabelle mit sich selbst verbinden, basierend auf den gruppierten Werte (Das ist ein SUM in meinem Fall). Die Unterabfrage Ich habe versucht, sah aus wie:

SELECT pt1.*, SUM(pt1.vote) AS votes1, SUM(pt2.vote) AS votes2 
FROM photo_types AS pt1 
LEFT OUTER JOIN photo_types AS pt2 
    ON pt1.photo_id = pt2.photo_id 
     AND (votes1 < votes2 
     OR (votes1 = votes2 AND pt1.id < pt2.id)) 
WHERE pt2.photo_id IS NULL 

... was da nicht funktioniert, es ist ein Versuch, zwei Tabellen auf einem berechneten Wert zu verbinden (im Gegensatz zu Bill-Lösung).
SQL Fiddle here

Frage
Gibt es eine Möglichkeit, die greatest-n-per-group zu erhalten, wenn die Gruppierung auf einem berechneten Werte wie SUM(xxx) beruht?

Lösungen, die dies teilweise abdecken, sind here und here, aber keine Aggregate in den gruppierten Werten. Der andere offensichtliche Weg, dies zu tun, besteht darin, einfach die höchsten gewählten Werte jedes Mal neu zu berechnen, wenn eine Stimme platziert wird, und dies direkt in der photos Tabelle - as discussed here - zu speichern - es sei denn, es ist unmöglich - würde ich lieber innerhalb rechnen die SELECT aus verschiedenen Gründen.

+0

Wo ist das gewünschte Ergebnis? – Strawberry

+0

@Strawberry wie per http://sqlfiddle.com/#!9/2029d8/7, aber nur die Top-2-Stimmen für jede photo_id enthalten. – goredwards

Antwort

1

Wenn Sie eine begrenzte Liste haben, ist der einfachste Weg, die substring_index()/group_concat() Trick:

SELECT photo_id, 
     SUBSTRING_INDEX(GROUP_CONCAT(photo_type ORDER BY votes DESC), ',', 2) as top2 
FROM (SELECT photo_id, photo_type, sum(vote) AS votes 
     FROM photo_types 
     GROUP BY photo_type, photo_id 
     HAVING votes > 0 
    ) pt 
GROUP BY photo_id; 

Hinweise:

  • Die Zwischenkette für group_concat() ist etwa 1k - das ist mehr als genug für dieses Problem.
  • Die Alternativen (wie Sie festgestellt haben) verwenden Variablen für viel komplexere Abfragen.
+0

Danke - das funktioniert - http://sqlfiddle.com/#!9/2029d8/11 - aber dann wird nicht mit einem std funktionieren 'WHERE photo_type IN (Array)' wenn ich basierend auf 'Foto_type' auswählen muss - und erfordert wahrscheinlich eine "REGEXP". Irgendeine Möglichkeit, kommagetrennte Werte in den Ergebnissen zu vermeiden? – goredwards

0

Nachschlagen xxx Funktionen anwenden. Sie geben Ihnen viel mehr Flexibilität als nur Sub-Aggregat-Abfragen.

http://sqlserverplanet.com/sql-2005/cross-apply-explained 
+0

Danke, interessanter Artikel, aber 'CROSS APPLY' existiert leider nicht in MySQL - und nicht ganz sicher, wie man die Approximation wie hier beschrieben anwendet: http://stackoverflow.com/questions/36869221/cross-outer-apply-in -mysql – goredwards

0

OK so von diesen old blog post (ein paar Mal in anderen greatest-n-per-group Lösungen genannt), die folgenden Werke:

SELECT pt1.* 
FROM 
    (SELECT id, photo_id, photo_type, sum(vote) AS votes 
    FROM photo_types 
    GROUP BY photo_type, photo_id 
    HAVING votes>0) AS pt1 
WHERE (
    SELECT COUNT(*) 
    FROM 
    (SELECT id, photo_id, photo_type, sum(vote) AS votes 
    FROM photo_types 
    GROUP BY photo_type, photo_id 
    HAVING votes>0) AS pt2 
    WHERE pt1.photo_id = pt2.photo_id and pt1.votes <= pt2.votes 
) <=2 
ORDER BY photo_id, votes DESC 

see SqlFiddle here

jedoch:
- nicht sicher, wie effizient es ist da es zwei Unterabfragen verwendet
- wird nicht die korrekte Anzahl der Ergebnisse zurückgeben, wenn einer der greatest-n Ident haben ische Werte (da die Zahl außerhalb der angegebenen Grenze liegt) - wie Sie sehen können in this SqlFiddle