2010-01-20 8 views
36

J'ai une énigme intéressante qui, je crois, peut être résolue en langage SQL. J'ai des tableaux semblables à ce qui suit:Lignes de transposition SQL en tant que colonnes

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? 

et je voudrais obtenir la sortie suivante

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

Je ne sais pas combien de questions il y aura, et ils seront dynamiques, de sorte Je ne peux pas simplement coder pour chaque question. J'utilise PostgreSQL et je crois que c'est ce que l'on appelle la transposition, mais je n'arrive pas à trouver quoi que ce soit qui dit la façon standard de le faire en SQL. Je me souviens d'avoir fait ça dans ma classe de base de données à l'université, mais c'était en MySQL et je ne me souviens vraiment pas comment nous l'avons fait.

Je suppose que ce sera une combinaison de jointures et une instruction GROUP BY, mais je ne peux même pas comprendre comment démarrer.

Quelqu'un sait-il comment faire? Merci beaucoup!

Edit 1: J'ai trouvé quelques informations sur l'utilisation d'un crosstab qui semble être ce que je veux, mais je vais avoir du mal à faire sens. Des liens vers de meilleurs articles seraient grandement appréciés!

Répondre

46

Utilisation:

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 

Ceci est une requête de pivot standard, parce que vous « shunté » les données de lignes à des données en colonnes.

+0

Donc vous dites que je dois construire une requête dynamique basée sur le nombre de questions que j'ai? Je suppose que je pouvais le faire, mais j'espérais une solution plus simple. –

+0

@Topher: Oracle & SQL Server ont 'PIVOT' et' UNPIVOT', mais si vous vérifiez la balise pivot, vous verrez que les requêtes dynamiques sont courantes même avec la fonction. –

+1

Merci pour la réponse. On dirait que ce sera le plus facile à implémenter même si je dois générer la requête à l'exécution. –

0

Il existe un exemple de ceci dans contrib/tablefunc/.

+1

Umm, où est 'contrib/tablefunc'? Parlez-vous d'un répertoire sur le serveur de doc? –

+0

Il se trouve dans ce répertoire dans l'arborescence source, ou vous pouvez trouver un paquet binaire 'postgresql-contrib' que vous devez installer et qui le contient. –

+1

Diminué pour une réponse de mauvaise qualité. Vous n'avez fourni aucun contexte, n'avez pas fourni une section du matériel source (au cas où cela changerait) et n'avez fait aucun effort pour le relier à la question. Référez-vous à cela pour de meilleures réponses http://stackoverflow.com/help/how-to-answer –

6

Vous pouvez résoudre cet exemple avec la fonction crosstab de cette manière

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); 

D'abord, vous devez installer l'extension tablefunc. Depuis la version 9.1, vous pouvez le faire en utilisant l'extension create:

CREATE EXTENSION tablefunc; 
2

J'ai écrit une fonction pour générer la requête dynamique. Il génère le sql pour le tableau croisé et crée une vue (le supprime d'abord s'il existe). Vous pouvez sélectionner dans la vue pour obtenir vos résultats.

Voici la fonction:

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; 

Et voici comment je l'utilise:

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

Exemple: Fist vous exécutez:

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

Than:

Select * from ct_view; 
10

J'ai implémenté une fonction vraiment dynamique pour gérer ce problème sans avoir à coder en dur une classe spécifique de réponses ou utiliser des modules/extensions externes. Il donne également un contrôle total sur l'ordre des colonnes et prend en charge plusieurs colonnes de clé et de classe/attribut.

Vous pouvez le trouver ici: https://github.com/jumpstarter-io/colpivot

exemple qui permet de résoudre ce problème particulier:

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; 

Ce sorties:

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

Très bien! Merci pour le partage et pour le rendre open source! Je serais ravi de voir quelques points de repère sur ses performances (en particulier ici sur SO car les nouveaux chercheurs voudront avoir confiance en ses capacités). –

+0

comment puis-je me débarrasser de la citation? dans les noms de colonnes – Diego

+0

Super!, merci. +1 – John

Questions connexes