2011-07-27 44 views
5

Teniendo en cuenta los siguientes registros (la primera fila siendo los nombres de columna):¿Cómo seleccionar registros únicos de la columna con ActiveRecord y PostgreSQL

name    platform   other_columns  date 
Eric    Ruby    something   somedate 
Eric    Objective-C  something   somedate 
Joe    Ruby    something   somedate 

¿Cómo recupero un registro singular con todas las columnas, tales que la columna de nombre es siempre única en el conjunto de resultados? Me gustaría que la consulta en este ejemplo devuelva el primer registro Eric (w/Ruby).

Creo que lo más cercano que he conseguido es usar "seleccionar distinto en (nombre) * ...", pero eso requiere que ordene primero por nombre, cuando realmente quiero ordenar los registros por la columna de fecha .

  • registros Ordenar por fecha
  • Si hay varios registros con el mismo nombre, seleccione uno (que no importa)
  • Seleccione todas las columnas

¿Cómo lograr esto en los carriles en PostgreSQL?

Respuesta

0

Obtenga una lista de nombres y fechas mínimas, y únase a la tabla original para obtener el conjunto de filas que está buscando.

select 
    b.* 
from 
    (select name, min(date) as mindate from table group by name) a 
    inner join table b 
     on a.name = b.name and a.mindate = b.date 
+0

Esto tiene problemas de exclusividad si un par 'name, min (date)' aparece dos veces en la tabla. –

2

os no me importa para qué fila se recupera cuando varios nombres están allí (esto será cierto para todas las columnas) y la tabla tiene esa estructura puede simplemente hacer una consulta como

SELECT * FROM table_name GROUP BY `name` ORDER BY `date` 

o en rieles

TableClass.group(:name).order(:date) 
+0

Cuando hago ese método, aparece el siguiente error: la columna "games.id" debe aparecer en la cláusula GROUP BY o usarse en una función agregada –

+1

En lugar de downvoting, explique mejor su estructura, de su pregunta parece que tener una sola tabla, lo cual no es cierto debido a ese mensaje de error. Publica tu estructura completa en su lugar. – Fabio

+0

+1, para borrar este incomprensible -1 – apneadiving

7

no se puede hacer un simple .group(:name) debido a que produce una GROUP BY name en su SQL cuando se le seleccionando no agrupados y unaggregate d columnas, que deja ambigüedad en cuanto a qué fila para recoger y PostgreSQL (rightly IMHO) complains:

When GROUP BY is present, it is not valid for the SELECT list expressions to refer to ungrouped columns except within aggregate functions, since there would be more than one possible value to return for an ungrouped column.

Si usted comienza a añadir más columnas a su agrupación con algo como esto:

T.group(T.columns.collect(&:name)) 

, entonces estará la agrupación por cosas que no quieres y terminarás sacando toda la mesa y eso no es lo que quieres. Si intenta agregar para evitar el problema de agrupación, terminará mezclando diferentes filas (es decir, una columna vendrá de una fila, mientras que otra columna vendrá de otra fila) y eso tampoco es lo que desea.

ActiveRecord realmente no está diseñado para este tipo de cosas, pero puede doblarlo a su voluntad con un poco de esfuerzo.

Está utilizando AR por lo que presumiblemente tiene una columna id. Si tienes PostgreSQL 8.4 o superior, entonces podría usar window functions como una especie de GROUP BY localizado; que necesita para ventana dos veces: una vez para averiguar las name/thedate pares y otra vez para escoger sólo una id (por si acaso tiene varias filas con el mismo name y thedate el cual coincide con el primer thedate) y por lo tanto obtener una única fila :

select your_table.* 
from your_table 
where id in (
    -- You don't need DISTINCT here as the IN will take care of collapsing duplicates. 
    select min(yt.id) over (partition by yt.name) 
    from (
     select distinct name, min(thedate) over (partition by name) as thedate 
     from your_table 
    ) as dt 
    join your_table as yt 
     on yt.name = dt.name and yt.thedate = dt.thedate 
) 

Luego envuelva que en un find_by_sql y tiene sus objetos.

Si está utilizando Heroku con una base de datos compartida (o algún otro entorno sin 8.4 o superior), entonces tiene problemas con PostgreSQL 8.3 y no tendrá funciones de ventana. En ese caso, lo que probablemente desea filtrar los duplicados en Rubí-tierra:

with_dups = YourTable.find_by_sql(%Q{ 
    select yt.* 
    from your_table yt 
    join (select name, min(thedate) as thedate from your_table group by name) as dt 
     on yt.name = dt.name and yt.thedate = dt.thedate 
}); 

# Clear out the duplicates, sorting by id ensures consistent results 
unique_matches = with_dups.sort_by(&:id).group_by(&:name).map { |x| x.last.first } 

Si usted es bastante seguro de que no habrá duplicados name/min(thedate) pares continuación, la solución compatible con 8.3 podría sea ​​su mejor apuesta; pero, si habrá muchos duplicados, entonces desea que la base de datos haga tanto trabajo como sea posible para evitar la creación de miles de objetos AR que simplemente va a descartar.

Tal vez alguien más con PostgreSQL-Fu más fuerte que yo vendrá y ofrecerá algo más agradable.

+0

+1 por una gran respuesta! – apneadiving

+0

@apneadiving: Tengo que hacerlo después de su "desafío" :) –

+0

Esta respuesta finalmente me ayudó a entender lo que estaba pasando dentro de PostgreSQL para este tipo de consulta. Gracias por la respuesta detallada. –

Cuestiones relacionadas