2010-07-22 9 views
5

Ok, so ist der Titel ein wenig verschachtelt. Dies ist im Grunde genommen ein Problem der größten Art, aber ich kann es nicht für das Leben von mir herausfinden.Postgres, table1 Links Join Tabelle2 mit nur 1 Zeile pro ID in Tabelle1

Ich habe eine Tabelle, user_stats:

------------------+---------+--------------------------------------------------------- 
id    | bigint | not null default nextval('user_stats_id_seq'::regclass) 
user_id   | bigint | not null 
datestamp  | integer | not null 
post_count  | integer | 
friends_count | integer | 
favourites_count | integer | 
Indexes: 
    "user_stats_pk" PRIMARY KEY, btree (id) 
    "user_stats_datestamp_index" btree (datestamp) 
    "user_stats_user_id_index" btree (user_id) 
Foreign-key constraints: 
    "user_user_stats_fk" FOREIGN KEY (user_id) REFERENCES user_info(id) 

ich für jede id die Statistiken erhalten möchten neueste Datumsstempel. Dies ist ein biggish Tisch, irgendwo in der Nachbarschaft von 41m Reihen, also habe ich eine temporäre Tabelle von User_id erstellt, last_date mit:

CREATE TEMP TABLE id_max_date AS 
    (SELECT user_id, MAX(datestamp) AS date FROM user_stats GROUP BY user_id); 

Das Problem ist, dass Datumsstempel nicht eindeutig ist, kann, da es mehr als 1 Stat-Update an einem Tag (sollte ein echter Zeitstempel gewesen sein, aber der Typ, der das entworfen hat, war ein Idiot und es gibt zu viele Daten, um im Moment zurück zu gehen). So haben einige IDs mehrere Zeilen, wenn ich die JOIN:

SELECT user_stats.user_id, user_stats.datestamp, user_stats.post_count, 
     user_stats.friends_count, user_stats.favorites_count 
    FROM id_max_date JOIN user_stats 
    ON id_max_date.user_id=user_stats.user_id AND date=datestamp; 

Wenn ich dies als Subselects tat ich, ich denke, 1 LIMIT könnte, aber ich habe immer gehört, die sind schrecklich ineffizient. Gedanken?

+0

"... Ich habe immer gehört, dass diese fürchterlich ineffizient sind." Lass dich nicht in den Cargo-Kult hineinziehen! 'EXPLAIN' ist dein Freund! Probieren Sie es aus und finden Sie heraus, was der Abfrageoptimierer für Sie tun kann. – Charles

Antwort

23

DISTINCT ON dein Freund.

select distinct on (user_id) * from user_stats order by datestamp desc; 
+0

Das ist genau das, was ich will, es ist spezifisch für Postgres, also nicht ideal, aber ich mache mir eine Notiz darum und gehe weiter. Vielen Dank! – Peck

+0

@Peck - Ich denke, DISTINCT ON ist einer der handlichsten Postgres-Ismen. Ich wünschte, mehr SQL-Implementierungen hätten etwas ähnliches! – rfusca

+0

Das permissive Verhalten von GROUP BY in MySQL und SQLite ist ähnlich. Aber die Ergebnisse können willkürlich sein. Diese Funktionen werden vom SQL-Standard nicht unterstützt. –

3

Grundsätzlich müssen Sie entscheiden, wie Sie Verbindungen lösen, und Sie benötigen eine andere Spalte neben datestamp, die garantiert eindeutig (mindestens über einen bestimmten Benutzer) ist, so dass es als Tiebreaker verwendet werden kann. Wenn nichts anderes, können Sie die Primärschlüsselspalte id verwenden.

Eine andere Lösung, wenn Sie mit PostgreSQL 8.4 ist Windowing-Funktionen:

WITH numbered_user_stats AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY datestamp DESC) AS RowNum 
    FROM user_stats) AS numbered_user_stats 
) SELECT u.user_id, u.datestamp, u.post_count, u.friends_count, u.favorites_count 
FROM numbered_user_stats AS u 
WHERE u.RowNum = 1; 
+0

Ich nehme an, dass diese ID-Spalten eine Verwendung haben müssen; Ich bin mir nicht sicher, ob diese Verwendung geplant war. –

0

die bestehende Infrastruktur verwenden, können Sie verwenden:

SELECT u.user_id, u.datestamp, 
     MAX(u.post_count)  AS post_count, 
     MAX(u.friends_count) AS friends_count, 
     MAX(u.favorites_count) AS favorites_count 
    FROM id_max_date AS m JOIN user_stats AS u 
    ON m.user_id = u.user_id AND m.date = u.datestamp 
GROUP BY u.user_id, u.datestamp; 

Dies Sie einen einzelnen Wert für jeden der gibt "nicht unbedingt eindeutige" Spalten. Es garantiert jedoch nicht absolut, dass die drei Maxima alle in derselben Reihe erschienen sind (obwohl es zumindest eine mäßige Chance gibt, dass sie dies tun werden - und dass sie alle aus den letzten an dem gegebenen Tag erzeugten Einträgen stammen werden).

Für diese Abfrage ist der Index für Datumsstempel allein keine Hilfe; Ein Index für Benutzer-ID und Datumsstempel könnte diese Abfrage erheblich beschleunigen - oder, genauer gesagt, könnte sie die Abfrage beschleunigen, die die Tabelle id_max_date generiert.

Natürlich können Sie auch die id_max_date Ausdruck als Unterabfrage in der FROM-Klausel schreiben:

SELECT u.user_id, u.datestamp, 
     MAX(u.post_count)  AS post_count, 
     MAX(u.friends_count) AS friends_count, 
     MAX(u.favorites_count) AS favorites_count 
    FROM (SELECT u2.user_id, MAX(u2.datestamp) AS date 
      FROM user_stats AS u2 
     GROUP BY u2.user_id) AS m 
    JOIN user_stats AS u ON m.user_id = u.user_id AND m.date = u.datestamp 
GROUP BY u.user_id, u.datestamp; 
Verwandte Themen