FAQ: Zeilen zu Spalten

Da dieses Thema regelmäßig gefragt wird, alleine in den letzten 3 Tagen jeden Tag durch einen anderen:

http://www.pg-forum.de/sql/4056-abfrage-soll-aus-join-neue-spalten-erzeugen-post22200.html#post22200
http://www.pg-forum.de/sql/4058-menge-ermitteln.html
http://www.pg-forum.de/sql/4065-kein-ergebnis-bei-abfrage-mit-unterabfragen-post22277.html#post22277


erkläre ich mal ganz kurz, wie sowas geht.

Vorweg: eine Datenbank ist keine Tabellenkalkulation. Damit meine ich, daß solche Operationen eigentlich nicht typisch für eine Datenbank sind. Tabellen haben typischerweise wenig Spalten, aber können potenziell sehr, sehr viele Zeilen enthalten. Jeder, der versucht, Zeilen zu Spalten zu wandeln, sollte sich vorher fragen, ob dies wirklich notwendig ist.

Was tun, wenn man es dennoch will?

Nun, zuerst ist zu prüfen, ob die Aggregation von Zeilen zu einer vorher definierten Anzahl von Spalten führt. Dies ist z.B. dann der Fall, wenn die Zeilen, die später die definierenden Spalten darstellen, exakt bekannt sind. Beispiel: Datumsangaben, daraus sollen 12 Spalten für die einzelnen Monate entstehen.
Es funktioniert nicht mehr (bzw. anders), wenn die Anzahl der entstehenden Spalten nicht definierbar ist. Dies sollte gegebenenfalls mit Hilfe von Referenztabellen, Check-Constraints oder ENUM-Typen sichergestellt werden, ansonsten wird das Resultat u.U. fehlerhaft.

Zur Abfrage selber: der ‘Trick’ basiert auf Anwendung von PostgreSQL: Documentation: Manuals: PostgreSQL 8.4: Conditional Expressions .

Nachfolgend 2 Beispiele, die das gut demonstrieren sollten:

begin;
create table category (category text, member text);
copy category from stdin delimiter ';';
fruit;apple
fruit;pear
fruit;banana
vegetable;potato
vegetable;beet
vegetable;cucumber
\.

select
  array_to_string(array_agg(case when category = 'fruit' then member else NULL end),',') as "Fruit",
  array_to_string(array_agg(case when category = 'vegetable' then member else NULL end),',') as "Vegetable"
from category;

rollback;

begin;
create table rain (town text, date date, amount int);
copy rain from stdin delimiter ';';
town a;2009-07-01;10
town a;2009-07-10;5
town a;2009-08-12;15
town b;2009-07-05;3
town c;2009-07-04;10
town c;2009-07-06;12
town c;2009-07-08;8
town c;2009-08-15;22
\.

select
  town,
  sum(case when extract(month from date) = 7 then amount else 0 end) as "July",
  sum(case when extract(month from date) = 8 then amount else 0 end) as "August"
from rain
group by town
order by town;

rollback;

In beiden Beispielen sind die entstehenden Spalten mir vorher bekannt. Ist dies nicht der Fall, wird es schwerer. Entweder man verwendet eine Stored Procedure, welche zur Laufzeit die entstehenden Spalten berechnet, daraus dynamisch einen String erstellt (welcher das SQL mit den einzelnen CASE … enthält) und führt diesen mittels EXECUTE aus, oder man verwendet aus dem Contrib-Modul tablefunc die crosstab - Funktion: PostgreSQL: Documentation: Manuals: PostgreSQL 8.4: tablefunc


Andreas