2010-01-20 49 views
36

Tengo un enigma interesante que creo que se puede resolver en puramente SQL. Tengo tablas similares a las siguientes:Filas de transposición SQL como columnas

responses: 

user_id | question_id | body 
---------------------------- 
1  | 1   | Yes 
2  | 1   | Yes 
1  | 2   | Yes 
2  | 2   | No 
1  | 3   | No 
2  | 3   | No 


questions: 

id | body 
------------------------- 
1 | Do you like apples? 
2 | Do you like oranges? 
3 | Do you like carrots? 

y me gustaría obtener la siguiente salida

user_id | Do you like apples? | Do you like oranges? | Do you like carrots? 
--------------------------------------------------------------------------- 
1  | Yes     | Yes     | No 
2  | Yes     | No     | No 

No sé cuántas preguntas habrá, y ellos serán dinámico, por lo No puedo codificar todas las preguntas. Estoy usando PostgreSQL y creo que esto se llama transposición, pero parece que no puedo encontrar nada que diga la forma estándar de hacerlo en SQL. Recuerdo haberlo hecho en mi clase de base de datos en la universidad, pero estaba en MySQL y sinceramente no recuerdo cómo lo hicimos.

Supongo que será una combinación de combinaciones y una declaración GROUP BY, pero ni siquiera puedo averiguar cómo empezar.

¿Alguien sabe cómo hacer esto? ¡Muchas gracias!

Edit 1: Encontré algo de información sobre el uso de un crosstab que parece ser lo que quiero, pero me cuesta trabajo darle sentido. ¡Los enlaces a mejores artículos serán muy apreciados!

Respuesta

46

Uso:

SELECT r.user_id, 
     MAX(CASE WHEN r.question_id = 1 THEN r.body ELSE NULL END) AS "Do you like apples?", 
     MAX(CASE WHEN r.question_id = 2 THEN r.body ELSE NULL END) AS "Do you like oranges?", 
     MAX(CASE WHEN r.question_id = 3 THEN r.body ELSE NULL END) AS "Do you like carrots?" 
    FROM RESPONSES r 
    JOIN QUESTIONS q ON q.id = r.question_id 
GROUP BY r.user_id 

Esta es una consulta de pivote estándar, ya que están "pivotante" los datos de las filas de datos en columnas.

+0

¿Entonces está diciendo que tengo que crear una consulta dinámica en función del número de preguntas que tengo? Creo que podría hacer eso, pero esperaba una solución más simple. –

+0

@Topher: Oracle y SQL Server tienen 'PIVOT' y' UNPIVOT', pero si comprueba la etiqueta pivote verá que las consultas dinámicas son comunes incluso con la función. –

+1

Gracias por la respuesta. Parece que este será el más fácil de implementar incluso si tengo que generar la consulta en tiempo de ejecución. –

0

Hay un ejemplo de esto en contrib/tablefunc/.

+1

Umm, ¿dónde está 'contrib/tablefunc'? ¿Estás hablando de un directorio en el servidor doc? –

+0

Está en ese directorio en el árbol fuente, o puede encontrar un paquete binario 'postgresql-contrib' que necesite instalar que lo contenga. –

+1

Downvoted para la respuesta de mala calidad. No ha proporcionado ningún contexto, no ha proporcionado una sección del material de origen (en caso de que cambie) y no ha hecho ningún esfuerzo para relacionarlo con la pregunta. Consulte esto para obtener mejores respuestas http://stackoverflow.com/help/how-to-answer –

6

Puede resolver este ejemplo con la función crosstab de esta manera

drop table if exists responses; 
create table responses (
user_id integer, 
question_id integer, 
body text 
); 

drop table if exists questions; 
create table questions (
id integer, 
body text 
); 

insert into responses values (1,1,'Yes'), (2,1,'Yes'), (1,2,'Yes'), (2,2,'No'), (1,3,'No'), (2,3,'No'); 
insert into questions values (1, 'Do you like apples?'), (2, 'Do you like oranges?'), (3, 'Do you like carrots?'); 

select * from crosstab('select responses.user_id, questions.body, responses.body from responses, questions where questions.id = responses.question_id order by user_id') as ct(userid integer, "Do you like apples?" text, "Do you like oranges?" text, "Do you like carrots?" text); 

En primer lugar, debe instalar la extensión tablefunc. Desde la versión 9.1, puede hacerlo utilizando la extensión create:

CREATE EXTENSION tablefunc; 
2

Escribí una función para generar la consulta dinámica. Genera el sql para la tabla de referencias cruzadas y crea una vista (lo deja caer primero si existe). Puede seleccionar de la vista para obtener sus resultados.

Aquí es la función:

CREATE OR REPLACE FUNCTION public.c_crosstab (
    eavsql_inarg varchar, 
    resview varchar, 
    rowid varchar, 
    colid varchar, 
    val varchar, 
    agr varchar 
) 
RETURNS void AS 
$body$ 
DECLARE 
    casesql varchar; 
    dynsql varchar;  
    r record; 
BEGIN 
dynsql=''; 

for r in 
     select * from pg_views where lower(viewname) = lower(resview) 
    loop 
     execute 'DROP VIEW ' || resview; 
    end loop; 

casesql='SELECT DISTINCT ' || colid || ' AS v from (' || eavsql_inarg || ') eav ORDER BY ' || colid; 
FOR r IN EXECUTE casesql Loop 
    dynsql = dynsql || ', ' || agr || '(CASE WHEN ' || colid || '=''' || r.v || ''' THEN ' || val || ' ELSE NULL END) AS ' || agr || '_' || r.v; 
END LOOP; 
dynsql = 'CREATE VIEW ' || resview || ' AS SELECT ' || rowid || dynsql || ' from (' || eavsql_inarg || ') eav GROUP BY ' || rowid; 
RAISE NOTICE 'dynsql %1', dynsql; 
EXECUTE dynsql; 
END 

$body$ 
LANGUAGE 'plpgsql' 
VOLATILE 
CALLED ON NULL INPUT 
SECURITY INVOKER 
COST 100; 

Y aquí es cómo lo uso:

SELECT c_crosstab('query_txt', 'view_name', 'entity_column_name', 'attribute_column_name', 'value_column_name', 'first'); 

Ejemplo: Puño ejecutar:

SELECT c_crosstab('Select * from table', 'ct_view', 'usr_id', 'question_id', 'response_value', 'first'); 

de: trabajo

Select * from ct_view; 
10

Implementé una función verdaderamente dinámica para manejar este problema sin tener que codificar ninguna clase específica de respuestas o usar módulos/extensiones externas. También brinda control total sobre el orden de columnas y admite múltiples claves y columnas de clase/atributo.

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

ejemplo que soluciona este problema en particular:

begin; 

create temporary table responses (
    user_id integer, 
    question_id integer, 
    body text 
) on commit drop; 

create temporary table questions (
    id integer, 
    body text 
) on commit drop; 

insert into responses values (1,1,'Yes'), (2,1,'Yes'), (1,2,'Yes'), (2,2,'No'), (1,3,'No'), (2,3,'No'); 
insert into questions values (1, 'Do you like apples?'), (2, 'Do you like oranges?'), (3, 'Do you like carrots?'); 

select colpivot('_output', $$ 
    select r.user_id, q.body q, r.body a from responses r 
     join questions q on q.id = r.question_id 
$$, array['user_id'], array['q'], '#.a', null); 

select * from _output; 

rollback; 

Este salidas:

user_id | 'Do you like apples?' | 'Do you like carrots?' | 'Do you like oranges?' 
---------+-----------------------+------------------------+------------------------ 
     1 | Yes     | No      | Yes 
     2 | Yes     | No      | No 
+0

¡Muy bien! ¡Gracias por compartir y por hacerlo de código abierto! Sin embargo, me encantaría ver algunos puntos de referencia sobre su rendimiento (particularmente aquí en SO, ya que los nuevos buscadores querrán tener confianza en sus capacidades). –

+0

¿cómo puedo deshacerme de la cotización? en los nombres de columna – Diego

+0

¡Genial!, gracias. +1 – John

Cuestiones relacionadas