2016-04-06 6 views
1

Ich habe zwei Tabellen mit einer Viele-zu-Viele-Zuordnung in Postgresql. Die erste Tabelle enthält Aktivitäten, die keine oder mehrere Gründe zählen kann:Abfrage, um die Häufigkeit von Viele-zu-Viele-Verknüpfungen zu zählen

CREATE TABLE activity (
    id integer NOT NULL, 
    -- other fields removed for readability 
); 

CREATE TABLE reason (
    id varchar(1) NOT NULL, 
    -- other fields here 
); 

für den Verband durchgeführt wird, eine Join-Tabelle existiert zwischen diese beiden Tabellen:

CREATE TABLE activity_reason (
    activity_id integer NOT NULL, -- refers to activity.id 
    reason_id varchar(1) NOT NULL, -- refers to reason.id 
    CONSTRAINT activity_reason_activity FOREIGN KEY (activity_id) REFERENCES activity (id), 
    CONSTRAINT activity_reason_reason FOREIGN KEY (reason_id) REFERENCES reason (id) 
); 

Gerne möchte ich zählen möglicher Zusammenhang zwischen Aktivitäten und Gründen.

+--------------+------------+ 
| activity_id | reason_id | 
+--------------+------------+ 
|   1 |   A | 
|   1 |   B | 
|   2 |   A | 
|   2 |   B | 
|   3 |   A | 
|   4 |   C | 
|   4 |   D | 
|   4 |   E | 
+--------------+------------+ 

Ich sollte so etwas wie haben:

+-------+---+------+-------+ 
| count | |  |  | 
+-------+---+------+-------+ 
|  2 | A | B | NULL | 
|  1 | A | NULL | NULL | 
|  1 | C | D | E  | 
+-------+---+------+-------+ 

Oder schließlich, so etwas wie: Ich habe diese Datensätze in der Tabelle activity_reason Gesetzt

+-------+-------+ 
| count |  | 
+-------+-------+ 
|  2 | A,B | 
|  1 | A  | 
|  1 | C,D,E | 
+-------+-------+ 

kann ich nicht finden, die SQL-Abfrage, um dies zu tun.

+0

Was zählen Sie? Ich bin verwirrt, weil Ihr Beispielergebnis "C, D, E" darin enthält, aber Ihre Beispieldaten haben nirgendwo ein "D". Versuchen Sie, alle Ursachen-Ursachen zu erhalten, die an eine Aktivitäts-ID gebunden sind? – rdubya

+0

'Ich entferne die Einschränkung für Lesbarkeit. Das ist ein Missverständnis. *** Entfernen Sie niemals Einschränkungen, die für die Klarheit notwendig sind. Angenommen, eine typische Implementierung mit einem PK auf '(activity_id, reason_id)'. –

+0

@rdubya: Ich habe die fehlenden Buchstaben repariert. –

Antwort

1

Wir müssen sortiert Listen von Gründen vergleichen, um gleiche Sätze zu identifizieren.

SELECT count(*) AS ct, reason_list 
FROM (
    SELECT array_agg(reason_id) AS reason_list 
    FROM (SELECT * FROM activity_reason ORDER BY activity_id, reason_id) ar1 
    GROUP BY activity_id 
    ) ar2 
GROUP BY reason_list 
ORDER BY ct DESC, reason_list; 

ORDER BY reason_id in der innersten Unterabfrage würde auch funktionieren, aber activity_id Zugabe ist in der Regel schneller.

Und wir brauchen nicht unbedingt die innerste Unterabfrage. Dies funktioniert auch:

SELECT count(*) AS ct, reason_list 
FROM (
    SELECT array_agg(reason_id ORDER BY reason_id) AS reason_list 
    FROM activity_reason 
    GROUP BY activity_id 
    ) ar2 
GROUP BY reason_list 
ORDER BY ct DESC, reason_list; 

Aber es ist in der Regel langsamer für die Verarbeitung aller oder die meisten der Tabelle. Quoting the manual:

Alternativ funktioniert die Eingabe der Eingabewerte aus einer sortierten Unterabfrage normalerweise.

Wir konnten verwenden string_agg() statt array_agg(), und das mit varchar(1) für Ihr Beispiel funktionieren würde (die mit dem Datentyp effizienter sein könnten "char", btw). Es kann jedoch für längere Zeichenfolgen fehlschlagen. Der aggregierte Wert kann nicht eindeutig sein.


Wenn reason_id wäre ein integer (wie es in der Regel ist), gibt es eine andere, schnellere Lösung mit sort() aus dem Zusatzmodul intarray:

SELECT count(*) AS ct, reason_list 
FROM (
    SELECT sort(array_agg(reason_id)) AS reason_list 
    FROM activity_reason2 
    GROUP BY activity_id 
    ) ar2 
GROUP BY reason_list 
ORDER BY ct DESC, reason_list; 

Verwandte, mit mehr Erklärung:

0

Sie können diese string_agg() mit tun:

select reasons, count(*) 
from (select activity_id, string_agg(reason_id, ',' order by reason_id) as reasons 
     from activity_reason 
     group by activity_id 
    ) a 
group by reasons 
order by count(*) desc; 
+0

Gruppierung * unsortierte * Aggregate können keine identischen Mengen identifizieren. –

+0

Ich hatte das gleiche Problem. B, A und A, B werden als zwei verschiedene Paare betrachtet. –

+0

@ErwinBrandstetter. . . Na sicher. Vielen Dank. –

2

Ich denke, können Sie bekommen, was Sie diese Abfrage wollen mit:

SELECT count(*) as count, reasons 
FROM (
    SELECT activity_id, array_agg(reason_id) AS reasons 
    FROM (
    SELECT A.activity_id, AR.reason_id 
    FROM activity A 
    LEFT JOIN activity_reason AR ON AR.activity_id = A.activity_id 
    ORDER BY activity_id, reason_id 
) AS ordered_reasons 
    GROUP BY activity_id 
) reason_arrays 
GROUP BY reasons 

Zuerst aggregieren alle Gründe für eine Tätigkeit in einem Array für jede Aktivität. Sie müssen die Assoziationen zuerst bestellen, andernfalls werden ['a', 'b'] und ['b', 'a'] als unterschiedliche Sätze betrachtet und haben individuelle Zählungen. Sie müssen auch den Join einfügen oder jede Aktivität, die keine Gründe hat, wird nicht in der Ergebnismenge angezeigt. Ich bin nicht sicher, ob das wünschenswert ist oder nicht, ich kann es wieder herausnehmen, wenn Sie Aktivitäten wollen, die keinen Grund haben, nicht eingeschlossen zu werden. Dann zählen Sie die Anzahl der Aktivitäten, die die gleichen Gründe haben.

Hier ist ein sqlfiddle zu

demonstrieren

Wie Gordon Linoff Sie erwähnt auch eine Zeichenfolge anstelle eines Arrays verwenden könnte. Ich bin nicht sicher, was für die Leistung besser wäre.

+0

müssen Sie den linken Join nicht verwenden. Dies sollte das gleiche tun: SELECT COUNT (*) als Graf, Gründe FROM ( SELECT activity_id, array_agg (reason_id) AS Gründe VON activity_reason GROUP BY aktivitäts ) reason_arrays GROUP BY reasons' –

+0

@ZhiliangTakutoXing wie ich bereits erwähnt In der Antwort wird die linke Verknüpfung benötigt, wenn Sie auch Aktivitäten zählen möchten, denen keine Gründe zugeordnet sind. Wenn diese nicht gezählt werden sollen, wird der linke Join nicht benötigt. Auch, wie ich in meiner Antwort angegeben habe, müssen Sie die Gründe bestellen oder die Arrays werden unterschiedliche Ordnungen haben und werden nicht richtig gruppiert. – rdubya