2010-07-22 7 views
5

Ok, quindi il titolo è un po 'contorto. Questo è fondamentalmente un problema di tipo "n-per-gruppo", ma non riesco a capirlo.Postgres, tabella1 left join table2 con solo 1 riga per ID in table1

Ho una tabella, 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) 

voglio ottenere le statistiche per ogni ID da ultima datestamp. Questo è un tavolo biggish, da qualche parte nel quartiere di righe 41m, così ho creato una tabella temporanea di user_id, LAST_DATE utilizza:

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

Il problema è che datestamp non riguardano solo in quanto ci possono essere più di 1 aggiornamento delle statistiche in un giorno (avrebbe dovuto essere un vero timestamp ma il ragazzo che lo ha progettato è stato un po 'idiota e ci sono troppi dati per tornare al momento). Così alcuni ID hanno più righe quando faccio la registrazione:

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; 

Se stavo facendo questo come subselect Credo che potrebbe limitare 1, ma ho sempre sentito dire quelli sono terribilmente inefficiente. Pensieri?

+0

"... Ho sempre sentito dire che sono orribilmente inefficienti." Non farti risucchiare dal culto del carico! 'SPIEGAZIONE' è tuo amico! Provalo e scopri cosa può fare l'ottimizzatore di query per te. – Charles

risposta

23

DISTINCT ON è tuo amico.

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

Questo è esattamente ciò che voglio, è specifico per i postgres, quindi non è l'ideale, ma ne faccio una nota intorno e procedo. Grazie! – Peck

+0

@Peck - Penso che DISTINCT ON sia uno dei postgres-ismo più handiest. Vorrei che più implementazioni SQL avessero qualcosa di simile! – rfusca

+0

Il comportamento permissivo di 'GROUP BY' in MySQL e SQLite è simile. Ma i risultati potrebbero essere arbitrari. Queste funzionalità non sono supportate dallo standard SQL. –

3

Fondamentalmente è necessario decidere come risolvere i legami e sono necessarie altre colonne oltre allo datestamp che è garantito essere univoco (almeno su un determinato utente) in modo che possa essere utilizzato come tie-break. Se non altro, è possibile utilizzare la colonna chiave primaria id.

funzioni

Un'altra soluzione, se si sta utilizzando PostgreSQL 8.4 è a finestre:

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

Suppongo che quelle colonne id debbano essere usate dopo tutto; Non sono sicuro che l'uso fosse previsto, però. –

0

utilizzando l'infrastruttura esistente, è possibile utilizzare:

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; 

Questo vi dà un singolo valore per ciascuno dei colonne "non necessariamente uniche". Tuttavia, non garantisce assolutamente che i tre massimi siano comparsi tutti nella stessa riga (sebbene ci sia almeno una possibilità moderata che lo facciano - e che verranno tutti dall'ultima delle voci create in un determinato giorno).

Per questa query, l'indice sul timbro data non è di aiuto; un indice su ID utente e timbro data potrebbe accelerare considerevolmente questa query - o, forse più precisamente, potrebbe velocizzare la query che genera la tabella id_max_date.

Chiaramente, è possibile anche scrivere l'espressione id_max_date come un sub-query nella clausola 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; 
Problemi correlati