Wie kann ich die maximale Anzahl von Vorkommen einer Zeichenfolge beim Gruppieren nach einem anderen Feld richtig auswählen?

7

Ich benutze Postgresql 9.0. Ich habe die folgenden Felder in einer Tabelle : id, name.

 id    name 
 1     John
 1     Mary
 1     Mary
 1     Mary
 1     John
 1     Mary
 3     Paul
 3     Paul
 3     George
 .     .
 .     .

Für jeden idmöchte ich den Namen auswählen, der am häufigsten vorkommt. Wie kann ich das machen?

Ich habe es mit der folgenden Abfrage versucht, aber es funktioniert nicht:

select id, max(name) 
from table 
group by id;
Tudor
quelle
2
maxfindet den Namen, der zuletzt lexikalisch sortiert wird, nicht den Namen, der am häufigsten vorkommt. Sie wollen wirklich ein mode(im statistischen Sinne) Aggregat dafür, und ich glaube nicht, dass eines angeboten wird. Jemand hat einen im Wiki geschrieben, aber ich bezweifle, dass er sehr effizient ist. Probieren Sie es aus: wiki.postgresql.org/wiki/Aggregate_Mode und scottrbailey.wordpress.com/2009/05/22/…
Craig Ringer

Antworten:

9

Das ist nicht trivial. Zunächst möchten Sie nach ID und Name gruppieren und die Zeilen zählen:

SELECT COUNT(*)
...
GROUP BY id, name

Wählen Sie dann die maximale Anzahl für jede ID. Eine Möglichkeit, dies zu erreichen, sind Fensterfunktionen. Die RANK()Funktion:

RANK() OVER (PARTITION BY id ORDER BY COUNT(*) DESC)

Weist jeder Zeile des Ergebnisses eine Nummer zu (nachdem die Gruppierung abgeschlossen ist), ordnet sie (die Zeilen) in Partitionen mit derselben an idund sortiert nach COUNT(*) DESC, sodass für jede (Partition von) iddie Zeile (n) mit der maximalen Anzahl sind zugewiesen einen Rang von 1. Daher müssen wir das Obige in eine abgeleitete Tabelle einfügen und eine WHEREBedingung verwenden, um nur diese Zeilen zu behalten:

WHERE rnk = 1

Die letzte Abfrage lautet wie folgt:

SELECT
    id, name, cnt
FROM
    ( SELECT id, name, COUNT(*) AS cnt,
             RANK() OVER (PARTITION BY id ORDER BY COUNT(*) DESC) AS rnk
      FROM tableX
      GROUP BY id, name
    ) AS tg 
WHERE
    rnk = 1 ;

Getestet bei SQL-Fiddle


Beachten Sie, dass alle diese zurückgegeben werden, wenn Sie in erster Linie Verbindungen haben (zwei oder mehr Namen mit derselben maximalen Anzahl). Wenn Sie in den Endergebnissen nur eine Zeile pro ID wünschen, müssen Sie ROW_NUMBER()anstelle RANK()der ORDER BYKlausel die Klausel verwenden und möglicherweise ändern , um explizit auszuwählen, wie die Bindungen aufgelöst werden sollen:

ROW_NUMBER() OVER (PARTITION BY id ORDER BY COUNT(*) DESC, name ASC) AS rnk

Getestet: SQL-Fiddle Test-2 .

ypercubeᵀᴹ
quelle
Wäre das nicht einfacher mit einigen Zwischen-CTEs? Oder würde das die Leistung stark beeinträchtigen?
Wildcard
1
@ Wildcard-CTEs werden in Postgres materialisiert, während abgeleitete Tabellen dem Optimierer mehr Optionen bieten.
Ypercubeᵀᴹ
Vielen Dank! Aber was meinst du genau mit "abgeleiteten Tabellen"? Ich habe diesen Begriff noch nie erlebt.
Wildcard
1
Die abgeleitete Tabelle ist eine benannte / aliasierte Unterabfrage, die in einer FROMKlausel angezeigt wird . Was steht in den Klammern in meinem Code (benannt tg)
ypercubeᵀᴹ