valida una tiene que programa problemas presentes present not los instalar instalacion incluidos included funcionar error dejo compatibilidad coinciden carpeta atributos atributo sql ruby-on-rails postgresql plpgsql window-functions

una - que es un atributo en sql



Grupo por repeticiĆ³n de atributo (4)

La cláusula GROUP BY colapsará la respuesta en 2 registros: uno con user_id 1 y otro con user_id 2 sin importar la cláusula ORDER BY por lo que te recomendamos que envíes solo el ORDER BY created_at

prev_id = -1 messages.each do |m| if ! m.user_id == prev_id do prev_id = m.user_id #do whatever you want with a new message group end end

Básicamente tengo una tabla de messages , con el campo user_id que identifica a un usuario que creó el mensaje.

Cuando visualizo una conversación (conjunto de mensajes) entre dos usuarios, quiero poder agrupar los mensajes por user_id , pero de una manera complicada:

Digamos que hay algunos mensajes (ordenados por created_at desc ):

id: 1, user_id: 1 id: 2, user_id: 1 id: 3, user_id: 2 id: 4, user_id: 2 id: 5, user_id: 1

Quiero obtener 3 grupos de mensajes en el siguiente orden: [1,2], [3,4], [5]

Debe agrupar por * user_id * hasta que vea una diferente y luego agrupe por esa.

Estoy usando PostgreSQL y me complacería usar algo específico para él, cualquiera que sea el mejor rendimiento.


Pruebe algo como esto:

SELECT user_id, array_agg(id) FROM ( SELECT id, user_id, row_number() OVER (ORDER BY created_at)- row_number() OVER (PARTITION BY user_id ORDER BY created_at) conv_id FROM table1 ) t GROUP BY user_id, conv_id;

La expresion:

row_number() OVER (ORDER BY created_at)- row_number() OVER (PARTITION BY user_id ORDER BY created_at) conv_id

Le dará una identificación especial para cada grupo de mensajes (este conv_id se puede repetir para otro user_id , pero user_id, conv_id le dará todos los grupos de mensajes distintos)

Mi SQLFiddle con el ejemplo.

Detalles: row_number() , OVER (PARTITION BY ... ORDER BY ...)


Puedes usar chunk :

Message = Struct.new :id, :user_id messages = [] messages << Message.new(1, 1) messages << Message.new(2, 1) messages << Message.new(3, 2) messages << Message.new(4, 2) messages << Message.new(5, 1) messages.chunk(&:user_id).each do |user_id, records| p "#{user_id} - #{records.inspect}" end

La salida:

"1 - [#<struct Message id=1, user_id=1>, #<struct Message id=2, user_id=1>]" "2 - [#<struct Message id=3, user_id=2>, #<struct Message id=4, user_id=2>]" "1 - [#<struct Message id=5, user_id=1>]"


SQL apropiado

@Igor presenta una buena técnica de SQL puro con funciones de ventana.
Sin embargo:

Quiero obtener 3 grupos de mensajes en el siguiente orden: [1,2], [3,4], [5]

Para obtener el pedido solicitado, agregue ORDER BY min(id) :

SELECT array_agg(id) AS ids FROM ( SELECT id ,user_id ,row_number() OVER (ORDER BY id) - row_number() OVER (PARTITION BY user_id ORDER BY id) AS grp FROM messages ORDER BY id) t -- for ordered arrays in result GROUP BY grp, user_id ORDER BY min(id);

SQL Fiddle.

La adición apenas justificaría otra respuesta. El problema más importante es esto:

Más rápido con PL / pgSQL

Estoy usando PostgreSQL y me complacería usar algo específico para él, cualquiera que sea el mejor rendimiento .

El SQL puro es agradable y brillante, pero una función procesal del lado del servidor es mucho más rápida para esta tarea. Si bien procesar las filas procesalmente es generalmente más lento , plpgsql gana esta competencia a lo grande, ya que puede hacerlo con una única exploración de tabla y una sola operación ORDER BY :

CREATE OR REPLACE FUNCTION f_msg_groups() RETURNS TABLE (ids int[]) AS $func$ DECLARE _id int; _uid int; _id0 int; -- id of last row _uid0 int; -- user_id of last row BEGIN FOR _id, _uid IN SELECT id, user_id FROM messages ORDER BY id LOOP IF _uid <> _uid0 THEN RETURN QUERY VALUES (ids); -- output row (never happens after 1 row) ids := ARRAY[_id]; -- start new array ELSE ids := ids || _id; -- add to array END IF; _id0 := _id; _uid0 := _uid; -- remember last row END LOOP; RETURN QUERY VALUES (ids); -- output last iteration END $func$ LANGUAGE plpgsql;

Llamada:

SELECT * FROM f_msg_groups();

Benchmark y enlaces

Ejecuté una prueba rápida con EXPLAIN ANALYZE en una tabla similar de la vida real con 60k filas (ejecute varias veces, elija el resultado más rápido para excluir los efectos de cambio):

SQL:
Tiempo de ejecución total: 1009.549 ms
Pl / pgSQL:
Tiempo de ejecución total: 336.971 ms

También considere estas preguntas estrechamente relacionadas: