2011-12-13 24 views
5

Estoy tratando de obtener datos de una tabla para una encuesta en un formato particular. Sin embargo, todos mis intentos parecen entregar el DB debido a demasiadas uniones/demasiado pesadas en el DB.Transposición de un resultado sql para que una columna pase a varias columnas

Mis datos es el siguiente:

id, user, question_id, answer_id, 
1, 1, 1,   1 
3, 1, 3,   15 
4, 2, 1,   2 
5, 2, 2,   12 
6, 2, 3,   20 

hay aproximadamente 250.000 filas y cada usuario tiene alrededor de 30 filas. Quiero que el resultado se vea como:

user0, q1, q2, q3 
1,  1, NULL, 15 
2,  2, 12, 20 

Para que cada usuario tiene una fila en el resultado, cada uno con una columna separada para cada respuesta.

Estoy usando Postgres, pero las respuestas en cualquier lenguaje SQL se apreciarán ya que podría traducir a Postgres.

EDIT: También tiene que ser capaz de hacer frente a los usuarios no responder a las preguntas, es decir, en el ejemplo anterior Q2 para el usuario 1.

+1

buscar una palabra clave de referencias cruzadas aquí por ejemplo http: //stackoverflow.com/questions/3002499/postgresql-crosstab-query –

Respuesta

6

Considere la siguiente demostración:

CREATE TEMP TABLE qa (id int, usr int, question_id int, answer_id int); 
INSERT INTO qa VALUES 
(1,1,1,1) 
,(2,1,2,9) 
,(3,1,3,15) 
,(4,2,1,2) 
,(5,2,2,12) 
,(6,2,3,20); 

SELECT * 
FROM crosstab(' 
    SELECT usr::text 
      ,question_id 
      ,answer_id 
    FROM qa 
    ORDER BY 1,2') 
AS ct (
    usr text 
    ,q1 int 
    ,q2 int 
    ,q3 int); 

Resultado:

usr | q1 | q2 | q3 
-----+----+----+---- 
1 | 1 | 9 | 15 
2 | 2 | 12 | 20 
(2 rows) 

user es un reserved word. ¡No lo use como nombre de columna! Lo renombré a usr.

Necesita instalar el módulo adicional tablefunc que proporciona la función crosstab(). Tenga en cuenta que esta operación es estrictamente por base de datos. En PostgreSQL 9.1 puede simplemente:

CREATE EXTENSION tablefunc; 

Para versión anterior tienes que ejecutar un script de shell suministrado en el directorio de contrib. En Debian, para PostgreSQL 8.4 , que sería:

psql mydb -f /usr/share/postgresql/8.4/contrib/tablefunc.sql 
+0

Aquí es donde fui inicialmente, pero estoy luchando para hacer frente a los datos faltantes, pregunta editada. Por lo tanto, si un usuario no tiene una fila para la pregunta 2, aún debe emitir un nulo (o 0). De todos modos, tenga un voto positivo para el esfuerzo :) – Yule

+0

La sintaxis de la tabla cruzada puede ser un poco desafiante ... necesitará una fila devuelta por cada respuesta para cada usuario a fin de que se complete correctamente. Dentro del área citada en el informe de la tabla cruzada, construya una tabla (la subconsulta puede manejarlo ... algo así como las preguntas de la combinación interna del usuario en 1 = 1. Recuerde el orden por!). A continuación, vuelva a unir esto con sus datos para completar los valores. De lo contrario, las respuestas faltantes para un usuario compensarán los campos incorrectamente. – Twelfth

+1

@Twelfth: Hay una variante con dos parámetros ['tabla de referencias cruzadas (texto, texto)'] (http://www.postgresql.org/docs/current/interactive/tablefunc.html#AEN138286) que se ocupa de las categorías que faltan proporcionando una lista explícita de las categorías en el segundo parámetro. Más detalles en [esta respuesta reciente] (http://stackoverflow.com/questions/11074489/postgres-buckets-always-filled-from-left-in-crosstab-query/11075727#11075727). –

3

Erwins respuesta es buena, hasta que la respuesta que faltan para un usuario aparece. Voy a hacer una suposición sobre ti ... tienes una tabla de usuarios que tiene una fila por usuario y tienes una tabla de preguntas que tiene una fila por pregunta.

select usr, question_id 
from users u inner join questions q on 1=1 
order by 1, 

Esta declaración creará una fila para cada usuario/pregunta, y estará en el mismo orden. Convertirlo en una sub consulta e izquierda unirlo a sus datos ...

select usr,question_id,qa.answer_id 
from 
(select usr, question_id 
from users u inner join questions q on 1=1 
)a 
left join qa on qa.usr = a.usr and qa.question_id = a.usr 
order by 1,2 

Enchufe de que en la declaración de referencias cruzadas Erwins y le dan crédito por la respuesta: P

+0

Solo para agregar ... deberá definir los resultados de la tabla cruzada para cada campo devuelto. Si tiene 3 respuestas, AS ct ( usr text , q1 int , q2 int , q3 int); trabajos. Si tienes 30, prepárate para definir cada uno así. Si tiene una nueva pregunta agregada, la declaración de selección de la tabla de referencias cruzadas la recogerá, pero deberá asegurarse de agregar el campo a esta lista de ct() – Twelfth

1

que implementa una función verdaderamente dinámico para manejar esto problema sin tener que codificar ningún número específico de preguntas o usar módulos externos/extensiones. También es mucho más fácil de usar que crosstab().

Se puede encontrar aquí: https://github.com/jumpstarter-io/colpivot

ejemplo que soluciona este problema en particular:

begin; 

create temp table qa (id int, usr int, question_id int, answer_id int); 
insert into qa values 
(1,1,1,1) 
,(2,1,2,9) 
,(3,1,3,15) 
,(4,2,1,2) 
,(5,2,2,12) 
,(6,2,3,20); 

select colpivot('_output', $$ 
    select usr, ('q' || question_id::text) question_id, answer_id from qa 
$$, array['usr'], array['question_id'], '#.answer_id', null); 

select * from _output; 

rollback; 

Resultado:

usr | 'q1' | 'q2' | 'q3' 
-----+------+------+------ 
    1 | 1 | 9 | 15 
    2 | 2 | 12 | 20 
(2 rows) 
Cuestiones relacionadas