2010-07-22 6 views
5

Ok, entonces el título es un poco intrincado. Este es básicamente un problema de tipo "n-por-grupo", pero no puedo por la vida de mi entenderlo.Postgres, table1 left join table2 con solo 1 fila por ID en table1

Tengo una tabla, 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) 

quiero conseguir las estadísticas para cada ID de marca de fecha más reciente. Esta es una tabla bastante grande, en algún lugar en el barrio de filas 41m, así que he creado una tabla temporal de user_id, last_date usando:

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

El problema es que la marca de fecha no es única ya que puede haber más de 1 actualización de estadísticas en un día (debería haber sido una marca de tiempo real, pero el tipo que diseñó esto era un poco idiota y hay demasiados datos para regresar en este momento). Por lo que algunos identificadores tienen varias filas cuando lo haga el Ingreso:

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; 

Si yo estaba haciendo esto como subselects supongo que podría limitar 1, pero siempre he oído esas son terriblemente ineficiente. ¿Pensamientos?

+0

"... Siempre escuché que son horriblemente ineficientes". ¡No te dejes absorber por el culto a la carga! 'EXPLAIN' es tu amigo! Pruébelo y descubra lo que el optimizador de consultas puede hacer por usted. – Charles

Respuesta

23

DISTINCT ON es su amigo.

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

Eso es exactamente lo que quiero, es específico para postgres, por lo que no es ideal, pero escribiré una nota a su alrededor y avanzaré. ¡Gracias! – Peck

+0

@Peck - Creo que DISTINCT ON es uno de los postgresismos más prácticos. ¡Ojalá más implementaciones SQL tuvieran algo similar! – rfusca

+0

El comportamiento permisivo de 'GROUP BY' en MySQL y SQLite es similar. Pero los resultados pueden ser arbitrarios. Estas características no son compatibles con el estándar SQL. –

3

Básicamente debe decidir cómo resolver los vínculos, y necesita alguna otra columna además de datestamp que se garantiza que es única (al menos para un usuario determinado) por lo que se puede utilizar como desempate. Si nada más, puede usar la columna de clave principal id.

funciones

Otra solución si está usando PostgreSQL 8.4 está de ventanas:

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

Supongo que esas columnas de identificación deben tener un uso después de todo; Aunque no estoy seguro de si este uso fue planeado. –

0

utilizando la infraestructura existente, se pueden utilizar:

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; 

Esto le da un valor único para cada una de las columnas 'no necesariamente únicas'. Sin embargo, no garantiza en absoluto que los tres máximos aparezcan todos en la misma fila (aunque existe al menos una posibilidad moderada de que así sea, y que todos ellos vendrán de la última de las entradas creadas en un día determinado).

Para esta consulta, el índice en el sello de fecha por sí solo no es de ayuda; un índice de identificación de usuario y sello de fecha podría acelerar esta consulta considerablemente, o, quizás más exactamente, podría acelerar la consulta que genera la tabla id_max_date.

Evidentemente, también se puede escribir la expresión id_max_date como una sub-consulta en la cláusula 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; 
Cuestiones relacionadas