Entwickler-Ecke

Datenbanken - [SQL] Mehrere Zeilen zusammenfassen


Der Jan - Fr 16.12.05 11:17
Titel: [SQL] Mehrere Zeilen zusammenfassen
Hallo,

ich hab ein Problem bei einer SQL Abfrage bzw. weiss ich gar nicht, ob das, was ich vorhabe einfach mit SQL geht:

Tabelle "Raten", enthält Daten zu monatlichen Ratenzahlungen bei bestimmten Verträgen:


SQL-Anweisung
1:
2:
3:
4:
5:
6:
7:
8:
9:
CREATE TABLE RATEN (
    RATEN_ID          INTEGER NOT NULL,
    RATEN_NR          INTEGER,
    RATEN_START_DAT   D_DATUMNULL,
    RATEN_END_DAT     D_DATUMNULL,
    RATEN_BETRAG      D_ZAHL2KOMMANOTNULL,      //monatliche Rate
    LEASING_VERTR_ID  TINT,                     //Vertrags-ID
    RATEN_MONAT       D_DATUMNULL
);


Jetzt bräuchte ich eine Abfrage, mit der ich alle Raten eines Vertrages in eine Zeile bekomme.Allerdings nicht z.B. mittels SUM/GROUP, sondern jede Rate muß in eine Spalte.

Dafür habe ich eine Zieltabelle (RATEN2, wie sinnig :) )

SQL-Anweisung
1:
2:
3:
4:
5:
6:
7:
8:
CREATE TABLE RATEN2 (
    VERTR_ID INTEGER NOT NULL,
    RATE01   D_ZAHL2KOMMA,
    RATE02   D_ZAHL2KOMMA,
    //undsoweiter
    RATE71   D_ZAHL2KOMMA,
    RATE72   D_ZAHL2KOMMA
)


Wie könnte man das machen?

Moderiert von user profile iconraziel: Code- durch SQL-Tags ersetzt


noidic - Fr 16.12.05 11:33

Ist die Anzahl der Raten festgelegt? Dann ginge das mit n ( n = Anzahl der Raten ) Joins...


Der Jan - Fr 16.12.05 11:35

Nein. Es sind zwischen 12 und 72.
Wie würde das mit den n Joins aussehen?


Der Jan - Fr 16.12.05 12:47

ich habe mir überlegt, ob man das vielleicht mit einer (oder zwei) Stored Procedure hinkriegen könnte. So sinngemäß: (Pseudocode)


SQL-Anweisung
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
PROCEDURE wasweissich 
FOR SELECT LEASING_VERTR_ID
  FROM RATEN
  INTO :act_ID
DO
  FOR SELECT LEASING_VERTR_ID, RATEN_BETRAG, RATEN_NR
    FROM RATEN
    INTO :temp_ID, :temp_BETRAG
    WHERE LEASING_VERTR_ID = :act_ID
    ORDER BY RATEN_NR
  DO
    IF(erster_datensatz)
      INSERT INTO RATEN2 (VERTR_ID, RATE01) VALUES (:temp_ID, :temp_BETRAG)
    ELSE
      UPDATE RATEN2 SET RATE_xx = :temp_BETRAG    //hier Problem
      WHERE :temp_ID = :act_ID

  SUSPEND;
END;


Ginge das prinzipiell so?
Ein Problem: wie setze ich das "RATE_xx" im ELSE-Zweig? Kann man hier evtl. mit Arrays arbeiten?

Moderiert von user profile iconraziel: Code- durch SQL-Tags ersetzt


alzaimar - Fr 16.12.05 12:56

Du musst eine 'Pivot-Tabelle' bauen:
Wenn das hier der Inhalt der Tabelle ist
    ID NR WERT
    1 1 10
    1 2 20
    1 3 30
    2 1 10
    3 2 20

Und Du willst eine Tabelle haben wie die hier:

    ID WERT1 WERT2 WERT3
    1 10 20 30
    2 10 NULL NULL
    3 NULL 20 NULL

Dann schreibst Du einfach:

SQL-Anweisung
1:
2:
3:
4:
5:
6:
select   [ID],
  sum (case when Nr=1 then Wert Else NULL endas Wert1,
  sum (case when Nr=2 then Wert Else NULL endas Wert2,
  sum (case when Nr=3 then Wert Else NULL endas Wert3
from Tabelle 
group by [ID]

Wenn Dein SQL-Server das 'case' nicht versteht (MSSQL verstehts), dann geht es so:

SQL-Anweisung
1:
2:
3:
4:
5:
6:
select [ID],
   (select Sum (Wert) from Tabelle x where x.Nr = 1 and x.[ID] = t.[ID]) as Wert1,
   (select Sum (Wert) from Tabelle x where x.Nr = 2 and x.[ID] = t.[ID]) as Wert2,
   (select Sum (Wert) from Tabelle x where x.Nr = 3 and x.[ID] = t.[ID]) as Wert3
from Tabelle t 
group by [ID]

Summieren deshalb, weil es (unabhängig von Deinem konkreten Fall) sein kann, das zu einer Nr und einer ID mehrere Werte existieren.

Das Wort 'Pivot' kommt von 'Drehen', weil man immer einen Teil der Tabelle um 90 Grad dreht (Alle Werte einer ID stehen in der Tabelle untereinander, im Pivot dann nebeneinander).


Der Jan - Fr 16.12.05 15:25

Das hilft mir schonmal ein stückchen weiter. Allerdings isses bei mir nicht so direkt einsetzbar, da ich eine Spalte wie "NR" in deinem Beispiel nicht habe.
Vielmehr soll der entsprechende Wert aus der ersten passenden Zeile (mit der richtigen ID) in die erste Spalte, der zweite WErt in die zweite spalte usw.


alzaimar - Fr 16.12.05 16:18

Ah... ok, ich dachte, die RATEN_NR wäre das..

Dann ist das nicht trivial. Aber mit SQL geht das trotzdem. Weil aber Dein Darstellungswunsch sich nicht mit deinen Daten deckt (hast ja keinen individuellen Ratenzähler je Vertrags_ID) musst Du etwas mehr tun:

Es gibt Komponenten (http://www.devexpress.com, QuantumGrid 5), die können Master-Detail Beziehungen so ähnlich darstellen, ohne das man viel rumhantieren muss. Die Master-Tabelle sind die Verträge, die Details sind die Raten. Die Verträge werden untereinander, die Raten jedes Vertrages unterhalb der 'Vertragszeile' einfach nebeneinander dargestellt.

Wenn Du das selbst basteln willst, würde ich die gesamte Tabelle in Delphi einlesen, und in einem geeigneten Grid, oder einer lokalen Tabelle (ADO mit Texttreiber und LockType=ltBatchOptimistic, dann ist es ein In-Memory-Dataset ;-) selbst generieren:
Sei 'T' die lokale Tabelle

Quelltext
1:
2:
3:
4:
ForEach Record r do
  T.Locate ('Vertrag_ID',r.ID,[]);
  F := FindNullRatenField (T);
  F.AsInteger := R.Rate

Für jede Zeile in der Raten-Tabelle suchst Du die Zeile mit dem Vertrag (oder erzeugst eine neue, per append). Dann suchst Du in dem Record das erste RatenXX-Feld, das noch nicht befüllt ist und packst da den Wert rein.

Natürlich kannst Du das auch in SQL-Coden. Aber nur, wenn Du den Server ärgern willst.


Der Jan - Di 20.12.05 11:33

Danke für die Hilfe.
Ich glaube, ich will den Server ärgern :P
Nee, mal im Ernst... Ich kann hier mit Delphi nicht wirklich ran, drum muß es in SQL sein, aber nun krieg ich es hin. :dance: