2014-06-26 3 views
5

Sto cercando di capire la funzione array_agg in Postgresql 9.3. Ho creato un esempio divertente per tutti coloro che potrebbero essere interessati a partecipare.Postgresql 9.3 - array_agg challenge

Qualsiasi fan dei film americani degli anni '80 potrebbe avere familiarità con il "branco" che è apparso in molti film di successo insieme. Usando le informazioni sui film di brat pack su wikipedia, ho creato delle tabelle che, una volta riunite, possono dirci chi ha lavorato tra loro - se abbiamo la query giusta!

/* 
See: http://en.wikipedia.org/wiki/Brat_Pack_(actors) 
*/ 

CREATE TABLE actor(
    id SERIAL PRIMARY KEY, 
    name VARCHAR(50) 
); 
insert into actor(name) values ('Emilio Estevez'),('Anthony Michael Hall'),('Rob Lowe'),('Andrew McCarthy'),('Demi Moore'),('Judd Nelson'),('Molly Ringwald'),('Ally Sheedy') 

CREATE TABLE movie(
    id SERIAL PRIMARY KEY, 
    title VARCHAR(200) 
); 
insert into movie(title) values ('The Outsiders'),('Class'),('Sixteen Candles'),('Oxford Blues'),('The Breakfast Club'),('St. Elmos Fire'), 
('Pretty in Pink'),('Blue City'),('About Last Night'),('Wisdom'), ('Fresh Horses'),('Betsys Wedding'),('Hail Caesar'); 

CREATE TABLE movie_brats(
    id SERIAL PRIMARY KEY, 
    movie_id INT REFERENCES movie(id), 
    actor_id INT REFERENCES actor(id) 
); 
insert into movie_brats(movie_id, actor_id) values (1,1),(1,3),(2,3),(2,4),(3,2),(3,7),(4,3),(4,8),(5,1),(5,2),(5,6), 
(5,7),(5,8),(6,1),(6,3),(6,4),(6,5),(6,6),(6,8),(7,4),(7,7),(8,6),(8,8),(9,3),(9,5),(10,1),(10,5),(11,4),(11,7), 
(12,7),(12,8),(13,2),(13,6); 

Query: Mostra una lista distinta di che ogni membro del branco moccioso ha lavorato con, ordinati per nome in entrambe le colonne

Name      Worked With 
---------------------------------------------------------------------------------------------------------------- 
Emelio Estevez  | Emilio Estevez, Anthony Michael Hall, Rob Lowe, Andrew McCarthy, Demi Moore, Judd Nelson, Molly Ringwald, Ally Sheedy 
*/ 

La mia domanda rotto:

select a1.name, array_to_string(array_agg(a2.name),', ') as Co_Stars 
from actor a1, actor a2, movie m, movie_brats mb 
where 
    m.id = mb.movie_id 
    and a1.id = mb.actor_id 
    and a2.id = mb.actor_id 
group by a1.id 
+1

Una nota a margine: probabilmente vuole 'string_agg()' invece di una combinazione di 'array_to_string()' e 'array_agg() ' –

+0

I Ho aggiornato la seconda query con un output più conciso –

risposta

1

SQL Fiddle

with v as (
    select 
     a.id as actor_id, 
     a.name as actor_name, 
     m.id as m_id 
    from 
     actor a 
     inner join 
     movie_brats mb on a.id = mb.actor_id 
     inner join 
     movie m on m.id = mb.movie_id 
) 
select 
    v1.actor_name as "Name", 
    string_agg(
     distinct v2.actor_name, ', ' order by v2.actor_name 
    ) as "Worked With" 
from 
    v v1 
    left join 
    v v2 on v1.m_id = v2.m_id and v1.actor_id != v2.actor_id 
group by 1 
order by 1 

L'aggregazione distinto sopra è necessario per non mostrare nomi ripetuti nel caso in cui lavoravano insieme in più di un film.

L'left join è necessario per non sopprimere un attore che non ha funzionato con nessuno degli altri nell'elenco come accadrebbe con uno inner join.

Se si desidera mostrare in quale film hanno lavorato insieme: SQL Fiddle

with v as (
    select 
     a.id as actor_id, 
     a.name as actor_name, 
     m.id as m_id, 
     m.title as title 
    from 
     actor a 
     inner join 
     movie_brats mb on a.id = mb.actor_id 
     inner join 
     movie m on m.id = mb.movie_id 
) 
select 
    a1 as "Name", 
    string_agg(
     format('%s (in %s)', a2, title), ', ' 
     order by format('%s (in %s)', a2, title) 
    ) as "Worked With" 
from (
    select 
     v1.actor_name as a1, 
     v2.actor_name as a2, 
     string_agg(v1.title, ', ' order by v1.title) as title  
    from 
     v v1 
     left join 
     v v2 on v1.m_id = v2.m_id and v1.actor_id != v2.actor_id 
    group by 1, 2 
) s 
group by 1 
order by 1 
+1

@Dowwie Ho aggiornato la domanda con dei chiarimenti. –

1

Il problema principale della tua query è che tu (incrocia) si unisca a movie_brats una sola volta, quindi ogni attore verrà stampato da ogni film (dove ha giocato) - questo è più obvio noi, se cambi la tua richiesta, per usare inner joins (invece di cross joins + where).

Punte:

  • non c'è bisogno di aderire al tavolo movie, a meno che non si desidera stampare tutti i titoli di film da attore
  • uso distinct per evitare nomi duplicati
  • filtro a1.id <> a2.id per evitare un attore essere elencato come ha lavorato con se stesso/lei.

Here is un esempio di funzionamento:

select a1.name, string_agg(distinct a2.name, ', ') as co_names 
from actor a1 
inner join movie_brats mb1 on a1.id = mb1.actor_id 
inner join movie_brats mb2 on mb1.movie_id = mb2.movie_id 
inner join actor a2 on a2.id = mb2.actor_id 
where a1.id <> a2.id 
group by a1.id 
+1

@Dowwie se non ti unisci contro se stesso, avrai solo righe, che hanno una sola coppia' movie_id' e 'actor_id'. Se ti unisci a se stesso (usando il campo 'movie_id') otterrai risultati con tutte le coppie' actor_id', dove 'movie_id' è lo stesso - solo pura logica di join. – pozs