2014-02-27 4 views
11

Ich habe eine Tabelle wie folgt aus:Was ist SQL, um eine Eigenschaft und die maximale Anzahl der Vorkommen einer verwandten Eigenschaft auszuwählen?

Table: p 
+----------------+ 
| id  | w_id | 
+---------+------+ 
| 5  | 8 | 
| 5  | 10 | 
| 5  | 8 | 
| 5  | 10 | 
| 5  | 8 | 
| 6  | 5 | 
| 6  | 8 | 
| 6  | 10 | 
| 6  | 10 | 
| 7  | 8 | 
| 7  | 10 | 
+----------------+ 

Was ist die beste SQL das folgende Ergebnis zu bekommen? :

+-----------------------------+ 
| id  | most_used_w_id | 
+---------+-------------------+ 
| 5  | 8    | 
| 6  | 10    | 
| 7  | 8    | 
+-----------------------------+ 

Mit anderen Worten, zu bekommen, pro id, die am häufigsten verwandten w_id. Beachten Sie, dass sich id 7 im obigen Beispiel auf 8 einmal und auf 10 einmal bezieht. Also, entweder (7, 8) oder (7, 10) wird als Ergebnis tun. Wenn es nicht möglich ist, eins aufzunehmen, dann sind sowohl (7, 8) als auch (7, 10) im Ergebnissatz in Ordnung.

Ich habe mit so etwas wie kommen:

select counters2.p_id as id, counters2.w_id as most_used_w_id 
from (
    select p.id as p_id, 
     w_id, 
     count(w_id) as count_of_w_ids 
    from p 
    group by id, w_id 
) as counters2 

join (
    select p_id, max(count_of_w_ids) as max_counter_for_w_ids 
    from (
    select p.id as p_id, 
      w_id, 
      count(w_id) as count_of_w_ids 
    from p 
    group by id, w_id 
) as counters 
    group by p_id 
) as p_max 

on p_max.p_id = counters2.p_id 
    and p_max.max_counter_for_w_ids = counters2.count_of_w_ids 
; 

aber ich bin nicht sicher, ob überhaupt, ob dies der beste Weg, es zu tun ist. Und ich musste die gleiche Unterabfrage zweimal wiederholen.

Eine bessere Lösung?

+0

Ihre Tabelle hat keinen Primärschlüssel. Das ist ein Problem. Allerdings ist dies eine gute Lösung. Es ist genau so geschrieben, wie ich es schreiben würde! :-) – Strawberry

Antwort

0

Diese Abfrage

select p_id, ccc , w_id from 
(
select p.id as p_id, 
     w_id, count(w_id) ccc 
    from p 
    group by id,w_id order by id,ccc desc) xxx 
group by p_id having max(ccc) 

hier ist die sqlfidddle Link

Sie auch diesen Code verwenden können, wenn Sie auf den ersten Datensatz von Nicht-Gruppierungsspalten verlassen wollen nicht

select p_id, ccc , w_id from 
(
select p.id as p_id, 
     w_id, count(w_id) ccc 
    from p 
    group by id,w_id order by id,ccc desc) xxx 
group by p_id having ccc=max(ccc); 
+2

+1 die einfachste bisher – zencv

+4

Einfachstes, aber _incorrect_ (wie ich bereits erwähnt habe, dass). Sie können sich nicht darauf verlassen, dass MySQL _first_ record übernimmt. Es ist kostenlos _jeder_ Reihe beim Mischen nicht-Gruppe Spalten –

1

Versuchen Sie zu verwenden User defined variables

select id,w_id 
FROM 
(select T.*, 
     if(@id<>id,1,0) as row, 
     @id:=id FROM 
       (
       select id,W_id, Count(*) as cnt FROM p Group by ID,W_id 
      ) as T,(SELECT @id:=0) as T1 
    ORDER BY id,cnt DESC 
) as T2 
WHERE Row=1 

SQLFiddle demo

+0

wählen Während dieser fit OP Wunsch kann ich nicht sehen, wie es die _question_ antwortet selbst (dh wie genau ist es _better_ und _why_) –

+0

@AlmaDo: Die Leistung hängt nicht nur von eine Abfrage aber auch auf Tabellenstruktur, Indizes, Datenverteilung, ... Also OP sollte es auf seinen Daten versuchen und entscheiden, welches besser ist. – valex

1

Formal SQL

In der Tat - Ihre Lösung ist in Bezug auf den normalen SQL korrekt. Warum? Weil Sie mit Verbindungswerten von Originaldaten zu gruppierten Daten stecken müssen. Daher kann Ihre Abfrage nicht vereinfacht werden. MySQL erlaubt es, Nicht-Gruppenspalten und Gruppenfunktionen zu mischen, aber das ist völlig unzuverlässig, daher werde ich Ihnen nicht empfehlen, sich auf diesen Effekt zu verlassen.

MySQL

Da Sie MySQL verwenden, können Sie Variablen verwenden. Ich bin kein großer Fan von ihnen, aber in Ihrem Fall können sie verwendet werden, um Dinge zu vereinfachen:

SELECT 
    c.*, 
    IF(@id!=id, @i:=1, @i:[email protected]+1) AS num, 
    @id:=id AS gid 
FROM 
    (SELECT id, w_id, COUNT(w_id) AS w_count 
    FROM t 
    GROUP BY id, w_id 
    ORDER BY id DESC, w_count DESC) AS c 
    CROSS JOIN (SELECT @i:=-1, @id:=-1) AS init 
HAVING 
    num=1; 

Also für Ihre Daten führen wird wie folgt aussehen:

 
+------+------+---------+------+------+ 
| id | w_id | w_count | num | gid | 
+------+------+---------+------+------+ 
| 7 | 8 |  1 | 1 | 7 | 
| 6 | 10 |  2 | 1 | 6 | 
| 5 | 8 |  3 | 1 | 5 | 
+------+------+---------+------+------+ 

So Sie haben gefunden id und entsprechende w_id. Die Idee ist - Zeilen zählen und aufzählen, wobei darauf geachtet wird, dass wir sie in Unterabfragen anordnen. Wir brauchen also nur die erste Zeile (weil sie Daten mit der höchsten Anzahl darstellt).

Diese kann mit einzelnen GROUP BY id ersetzt - aber auch hier Server ist frei, jede Zeile in diesem Fall zu wählen (es wird Arbeit, weil es dauern wird, erste Reihe, aber Dokumentation sagt nichts über das für die gemeinsamen Fall).

Ein wenig nice Sache darüber ist - Sie können zum Beispiel, 2. nach Frequenz oder 3-rd, es ist sehr flexibel.

Leistung

Leistung zu erhöhen, Sie Index auf (id, w_id) erstellen können - natürlich, wird es für die Bestellung und Gruppieren von Datensätzen verwendet werden. Aber Variablen und HAVING werden jedoch produzieren line-by-line für Satz scannen, abgeleitet von internen GROUP BY. Es ist nicht so schlecht wie bei einem vollständigen Scan der Originaldaten, aber trotzdem ist es nicht gut, dies mit Variablen zu tun. Auf der anderen Seite, zu tun, dass mit JOIN & Unterabfrage wie in Ihrer Abfrage nicht viel anders sein wird, weil die temporery Tabelle für Unterabfrage Ergebnismenge zu erzeugen.

Aber um sicher zu sein, müssen Sie testen. Und denken Sie daran - Sie bereits gültige Lösung haben, die, nebenbei bemerkt, nicht gebunden ist, zu DBMS-spezifische Dinge und ist gut in Bezug auf gemeinsame SQL.

+0

Sie müssen "t" durch "p" in Ihrer Lösung ersetzen. –

Verwandte Themen