2012-09-26 52 views
17

Tengo una situación bastante interesante. Tengo una base de datos SQLite llena de direcciones y mensajes (las direcciones no son únicas, los mensajes son). Cada mensaje también tiene una fecha asociada. Lo que quiero hacer es seleccionar la primera dirección del mensaje, mensaje, fecha y cuántos mensajes están asociados con la dirección.¿Cómo construir una consulta SQLite a GROUP por ORDEN?

Entonces, pensé: "Puedo simplemente AGRUPAR por la dirección para obtener solo un mensaje por dirección, luego ORDENAR por la fecha y también obtener el COUNT de la columna de la dirección".

Lo hice, y funciona ... un poco. Busca el recuento correcto, busca solo un mensaje por dirección y los ordena por fecha, pero no selecciona el más reciente mensaje para la dirección. Parece ser arbitrario.

Como un ejemplo, tengo tres mensajes (más temprana a la última) A, B, C de la dirección Y, y tres mensajes D, E, F desde la dirección Z. La consulta pueden obtener los mensajes B y E, entonces ordenarlos por fecha. Es debería buscar los mensajes C y F, y ordenarlos por fecha.

Esto es lo que tengo hasta ahora:

// Expanded version: 
Cursor cursor = db.query(
    /* FROM */ "messages_database", 
    /* SELECT */ new String[]{ "*", "COUNT(address) AS count" }, 
    /* WHERE */ null, 
    /* WHERE args */ null, 
    /* GROUP BY */ "address", 
    /* HAVING */ null, 
    /* ORDER BY */ "date DESC" 
); 

// Or, same code on one line: 
Cursor cursor = db.query("messages_database", new String[]{ "*", "COUNT(address) AS count" }, null, null, "address", null, "date DESC"); 

siento que esto puede tener que ver con la cláusula HAVING, pero realmente no lo sé. He usado mucho MySQL con PHP, pero nunca tuve que tocar HAVING antes. Intenté configurar mi cláusula HAVING en "MAX(date)", pero no tuvo ningún efecto. Si configuro mi GROUP BY cláusula para que sea "address, date", entonces están ordenados por fecha, pero por supuesto son todos individuales en lugar de agrupados (ya que las fechas difieren).

Las búsquedas en Google han resultado infructuosas; consultas como "android sqlite antes del grupo" y "android sqlite group por orden" no arrojan resultados relacionados.

¿Cómo puedo seleccionar el mensaje una última para cada dirección sin tener que quitar la cláusula GROUP (como COUNT() basa en esto)? ¿Necesito dos consultas?

Editar: Basado en the answer @Adrian me unía a en los comentarios, me ocurrió con dos consultas, que tanto produjo el mismo resultado; una fila, en la que el recuento fue 7 (que es el número total de direcciones, no los mensajes por dirección), y la dirección que se muestra no era la del último mensaje.

Las dos consultas fueron:

Cursor cursor = db.rawQuery(
     "SELECT t.*, COUNT(t.message_content) AS count " 
    + "FROM messages_database t " 
    + "INNER JOIN (" 
    + " SELECT address, MAX(date) AS maxdate " 
    + " FROM messages_database " 
    + " GROUP BY address " 
    + ") ss ON t.address = ss.address AND t.date = ss.maxdate", 
    null 
); 
Cursor cursor = db.rawQuery(
     "SELECT t1.*, COUNT(t1.message_content) AS count " 
    + "FROM messages_database t1 " 
    + "LEFT OUTER JOIN messages_database t2 " 
    + "ON (t1.address = t2.address AND t1.date < t2.date) " 
    + "WHERE t2.address IS NULL", 
    null 
); 
+3

Parece que tiene un problema [tag: greatest-n-per-group] en sus manos. Lea aquí, lo más probable es que lo ayude: http://stackoverflow.com/a/7745635/570191 –

+0

@Adrian He comprobado esa respuesta e intenté incorporar esas ideas. Sin embargo, simplemente parecen proporcionar una fila, que contiene un recuento de una columna diferente. He publicado mis consultas anteriormente, así como un resultado más detallado de lo que no funcionó. – Eric

Respuesta

14

SQLite tiene una extensión que hace problemas mucho más fácil:
Si está utilizando los MAX() o MIN() funciones de agregación, y si va a seleccionar otras columnas al mismo tiempo sin usarlos en una función agregada o agrupamiento por ellos, entonces los valores resultantes para esas columnas están garantizados para salir del mismo registro que tiene el valor máximo/mínimo. (Esto no está permitido en otros dialectos SQL, y se introdujo en SQLite 3.7.11.)

Por lo tanto, para su problema, puede utilizar una consulta como esta:

SELECT *, COUNT(address) AS count, MAX(date) 
FROM messages_database 
GROUP BY address 

Si usted no tiene SQLite 3.7.11 (que es probable que en la mayoría de las versiones de Android) o el uso de otro motor SQL, la siguiente consulta funcionará:

SELECT *, 
     (SELECT COUNT(address) AS count 
     FROM messages_database m2 
     WHERE m1.address = m2.address) 
FROM messages_database m1 
WHERE date = (SELECT MAX(date) 
       FROM messages_database m3 
       WHERE m1.address = m3.address) 
GROUP BY address 
+0

Me temo que esto tampoco parece funcionar. Parece tener el mismo resultado que mi intento inicial (aunque las filas salen en un orden diferente). – Eric

+0

Ah, con su edición y la contribución de @Adrián, logré que funcionase. Acepté tu respuesta mientras me dirigías a la solución y publiqué mi solución de trabajo a continuación. ¡Gracias! – Eric

+0

¿Qué sucede si ordena en múltiples campos ... si usa 'MAX()' en cada campo de clasificación, ¿se garantiza que obtendrá el resultado correcto? – Michael

5

¡Solucionado! Terminé usando una combinación del método @CL. y los métodos que describí en mi publicación editada (aclarada en this answer, publicada por @Adrian).

Debido a que no quería utilizar 3 SELECT declaraciones (como @ respuesta de CL. Descrito), que utiliza el mismo concepto INNER JOIN como en las otras declaraciones, al tiempo que conserva su metodología.

El resultado es este:

Cursor cursor = db.rawQuery(
     "SELECT t.*, ss.count AS count " 
    + "FROM messages_database t " 
    + "INNER JOIN (" 
    + " SELECT address, MAX(date) AS maxdate, COUNT(address) AS count " 
    + " FROM messages_database " 
    + " GROUP BY address " 
    + ") ss ON t.address = ss.address AND t.date = ss.maxdate " 
    + "GROUP BY t.address " 
    + "ORDER BY t.date DESC ", 
    null 
); 

Y está funcionando perfectamente!