2010-07-22 7 views
5

Ok, więc tytuł jest nieco zawiłowany. Jest to w zasadzie największy problem typu "n-per-group", ale nie mogę tego zrozumieć.Postgres, table1 left join table2 z tylko jednym rzędem na ID w tabeli1

Mam tabeli, 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) 

Chcę uzyskać statystyki dla każdego identyfikatora przez najnowszej DATESTAMP. Jest to spory stół, gdzieś w sąsiedztwie rzędów 41m, więc stworzyłem tabelę temp z user_id, last_date używając:

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

Problemem jest to, że Sygnatura daty nie jest unikalna, ponieważ nie może zawierać więcej niż 1 aktualizacja statystyk w ciągu dnia (powinien być prawdziwym znacznikiem czasu, ale facet, który to zaprojektował, był swego rodzaju idiotą i jest za dużo danych, aby wrócić w tym momencie). Więc niektórzy identyfikatory mają wiele wierszy kiedy robię społeczności:

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; 

Gdybym robił to za obsługę żądań Chyba mogę graniczna 1, ale zawsze słyszałem te są strasznie niewydajne. Myśli?

+0

"... Zawsze słyszałem, że są okropnie nieefektywne." Nie daj się wciągnąć w kult ładunku! 'EXPLAIN' jest twoim przyjacielem! Wypróbuj go i dowiedz się, co może zrobić dla ciebie optymalizator zapytań. – Charles

Odpowiedz

23

DISTINCT ON jest twoim przyjacielem.

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

To jest dokładnie to, czego chcę, to jest specyficzne dla postgresu, więc nie jest to idealne, ale Ill zanotuję to i poruszam się dalej. Dziękuję Ci! – Peck

+0

@Peck - Myślę, że DISTINCT ON jest jednym z najbardziej poręcznych postgres-izms. Chciałbym, żeby więcej implementacji SQL miało coś podobnego! – rfusca

+0

Przyzwalające zachowanie 'GROUP BY' w MySQL i SQLite jest podobne. Ale wyniki mogą być arbitralne. Te funkcje nie są obsługiwane przez standard SQL. –

3

Zasadniczo musisz zdecydować, jak rozwiązać powiązania i potrzebujesz innej kolumny oprócz datestamp, która jest gwarantowana jako unikalna (przynajmniej dla danego użytkownika), więc może być używana jako rozstrzygający. Jeśli nic więcej, możesz użyć kolumny klucza podstawowego id.

Innym rozwiązaniem jeśli używasz PostgreSQL 8.4 jest z oknami funkcje:

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

Przypuszczam, że te kolumny id muszą mimo wszystko mieć zastosowanie; Nie jestem pewien, czy to było zaplanowane. –

0

wykorzystaniem istniejącej infrastruktury, można użyć:

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; 

To daje jedną wartość dla każdego z kolumny "niekoniecznie unikalne". Nie gwarantuje to jednak, że wszystkie trzy maksima pojawiły się w tym samym wierszu (choć istnieje przynajmniej umiarkowana szansa, że ​​będą - i że wszystkie będą pochodziły z ostatnich wpisów utworzonych w danym dniu).

W tym zapytaniu sam indeks datownika nie jest pomocny; indeks na ID użytkownika i datownik może znacznie przyspieszyć tę kwerendę - lub, być może, dokładniej, może przyspieszyć zapytanie generujące tabelę id_max_date.

Oczywiście, można również napisać id_max_date ekspresji jako sub-zapytania w klauzuli FROM:

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;