2011-12-13 34 views
5

Sto cercando di estrarre i dati da una tabella per un sondaggio in un formato particolare. Comunque tutti i miei tentativi sembrano passare il DB a causa di troppi join/troppo pesanti sul DB.Trasposizione di un risultato sql in modo che una colonna vada su più colonne

miei dati simile a questa:

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 

ci sono circa 250.000 righe e ogni utente ha circa 30 righe. Voglio che il risultato a guardare come:

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

In modo che ogni utente ha una riga nel risultato, ciascuno con una colonna separata per ogni risposta.

Sto utilizzando Postgres ma le risposte in qualsiasi linguaggio SQL potrebbero essere apprezzate poiché potrei tradurre in Postgres.

EDIT: ho anche bisogno di essere in grado di trattare con gli utenti non rispondere alle domande, cioè nell'esempio di cui sopra q2 per l'utente 1.

+1

Ricerca una parola incrociato qui ad esempio http: //stackoverflow.com/questions/3002499/postgresql-crosstab-query –

risposta

6

Si consideri il seguente demo:

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

Risultato:

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

user è un reserved word. Non usarlo come nome della colonna! L'ho rinominato in usr.

È necessario installare il modulo aggiuntivo tablefunc che fornisce la funzione crosstab(). Si noti che questa operazione è rigorosamente per database. in PostgreSQL 9.1 si può semplicemente:

CREATE EXTENSION tablefunc; 

Per versione precedente si eseguirà uno script di shell fornito nella directory contrib. In Debian, per PostgreSQL 8.4 , che sarebbe:

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

Questo è il punto in cui sono andato inizialmente, ma ho difficoltà a gestire i dati mancanti, la domanda modificata. Quindi se un utente non ha una riga per la domanda 2, dovrebbe comunque emettere un valore null (o 0). Avere una spinta per lo sforzo in ogni caso :) – Yule

+0

La sintassi della Crosstab può essere un po 'impegnativa ... ti occorrerà una riga restituita per ogni risposta per ciascun utente affinché possa essere compilata correttamente. All'interno dell'area citata nel report a campi incrociati, crea una tabella (la sottoquery può gestirla ... qualcosa come da domande di inner inner user su 1 = 1. Ricorda l'ordine!). Quindi a sinistra torna ai tuoi dati per popolare i valori. In caso contrario, le risposte mancanti per un utente compenseranno i campi in modo errato. – Twelfth

+1

@Twelfth: C'è la variante con due parametri ['crosstab (text, text)'] (http://www.postgresql.org/docs/current/interactive/tablefunc.html#AEN138286) che si occupa delle categorie mancanti fornendo un elenco esplicito delle categorie nel secondo parametro. Maggiori dettagli in [questa recente risposta] (http://stackoverflow.com/questions/11074489/postgres-buckets-always-filled-from-left-in-crosstab-query/11075727#11075727). –

3

Erwins risposta è buona, fino a quando la risposta mancante per un utente si presenta. Ho intenzione di fare una supposizione su di te .... hai una tabella utenti che ha una riga per utente e hai una tabella delle domande che ha una riga per ogni domanda.

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

Questa istruzione creerà una riga per ogni utente/domanda e sarà nello stesso ordine. Trasformarlo in un sottoquery e lasciato unirlo ai dati ...

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 

Plug che nella dichiarazione incrociato Erwins e dargli credito per la risposta: P

+0

Solo per aggiungere ... sarà necessario definire i risultati della tabella a campi incrociati per ogni campo restituito. Se hai 3 risposte, AS ct ( usr text , q1 int , q2 int , q3 int); lavori. Se ne hai 30, preparati a definire ognuno come questo. Se hai una nuova domanda aggiunta, l'istruzione select crosstab la raccoglierà, ma dovrai assicurarti di aggiungere il campo a questo elenco ct() – Twelfth

1

ho implementato una funzione veramente dinamico per gestire questa situazione problema senza dover codificare un numero specifico di domande o utilizzare moduli/estensioni esterni. È anche molto più semplice da usare rispetto allo crosstab().

Lo si può trovare qui: https://github.com/jumpstarter-io/colpivot

Esempio che risolve questo problema particolare:

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; 

Risultato:

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