Aufruf von Update vor einem Update auf gleiche Tabelle

Liebe Forumgemeinde,

der Threadtitel ist etwas verwirrend aber hier zu meinem Problem.

Ich habe eine Tabelle in der ich ein Ranking speichern möchte. Dieses Ranking fängt bei 0 an und geht mit fortlaufenden Zahlen weiter nach oben. Nach einigen Inserts könnte die Tabelle folgendermaßen aussehen:

Titel:                             Ranking:
Eins                               0
Zwei                              1
Drei                               2

Jetzt möchte ich in meiner Applikation (C++) allerdings auch erlauben, dass ein anderes Buch auf den Platz 1 gelangen kann. Dazu würde ich gerne einen Trigger schreiben, der dann dafür sorgt, dass alle Bücher mit einem höheren Ranking um eins erhöht werden. Nach einem Eintrag soll der Trigger also dafür sorgen, dass die Rankingreihenfolge konsistent ist:

Titel:                             Ranking:
Eins                               0
Neues Buch                     1
Zwei                              2
Drei                               3

Für den Insert- und den Delete-Fall (hier müssten Einträge um 1 verringert werden) kann ich mir das schon ganz gut vorstellen aber was passiert bei einem Update?

Nehmen wir im letzten Beispiel an, das Buch mit dem Titel “Drei” würde jetzt auf Ranking ‘0’ gelangen. Hierzu würde meine Applikation ein Update aufrufen. Innerhalb meiner Trigger-Funktion müsste ich wieder ein Update aufrufen, was dann implizit wieder den Update-Trigger auslöst usw. bis ein Stack-Overflow eintritt.

Gibt es generell - ohne mein Beispiel komplett zu berücksichtigen - eine Lösung für so ein Problem.? Oder liege ich mit dem Mechanismus des Triggers hier falsch und es gibt noch eine bessere Möglichkeit das über die DB abzubilden?

Viele Dank für Eure Hilfe,
–Heiko

Das Problem ist, daß das nicht performen wird. Ein anderer Ansatz wäre hier, die Ranking-Spalte vielleicht als float nehmen, dann kannst Du faktisch immer einen Datensatz irgendwo einfügen, ohne daß Du alle anderen Datensätze danach updaten mußt. Ditto beim Delete bzw. beim ändern des Rankings.

Ansonsten, schau mal in Google nach ‘linked list’.

Andreas

Hallo,

leider verstehe ich nicht was das genau heißen soll aber trotzdem danke dür die Idee mit den floating-points. Mal ganz davon abgesehen ist es mir eben doch gelungen das Problem zu lösen.

Ich habe jetzt einen zeilenbasierten Trigger (FOR EACH ROW), der nach einem UPDATE ausgeführt wird. Nehmen wir an wir haben wieder folgenden Zustand:

ID:              Titel:               Ranking:
1                 Ein Buch          0
2                 Zwei Bücher     1
3                 Drei Bücher      2

Das Buch mit dem Titel “Drei Bücher” soll jetzt das Ranking ‘0’ erhalten. Also führe ich ein Update auf der Tabelle ‘Buch’ aus:

UPDATE t_buch
SET ranking=0
where ID=3;

Das führt direkt vor der Ausführung meines Triggers zu folgender Tabelle:

ID:              Titel:               Ranking:
1                 Ein Buch          0
2                 Zwei Bücher     1
3                 Drei Bücher      0                    <--

Der AFTER Trigger muss jetzt alle Zeilen finden, die ebenfalls das Ranking ‘0’ haben, aber nicht die ID=3 haben. Für einen solchen Datensatz ist das Ranking um ‘1’ zu erhöhen.

Es kommt also zu folgender Tabelle:

ID:              Titel:               Ranking:
1                 Ein Buch          1                  <--
2                 Zwei Bücher     1
3                 Drei Bücher      0

Durch das Update-Kommando des Triggers wird ja genau dieser Trigger - jetzt nur mit anderen Parametern - erneut ausgelöst. Im nächsten Schritt werden alle Zeilen gesucht, die ebenfalls das Ranking 1 haben jedoch nicht die ID ‘1’.

Es kommt also zu folgender Tabelle:

ID:              Titel:               Ranking:
1                 Ein Buch          1                  
2                 Zwei Bücher     2               <--
3                 Drei Bücher      0

Durch das Update wird der Trigger wieder ausgelöst. Er sucht nach Zeilen, die ein Ranking von 2 aber eine ID != ‘2’ haben. Da es eine solche Zeile nicht gibt, ist der Vorgang hiermit beendet und der Trigger wird - zumindest durch diesen Statement - nicht erneut gefeuert.

Wen es interessiert hier noch der getestete Quellcode für den UPDATE-Zweig:

DECLARE
	counter INTEGER;
BEGIN
  IF (TG_OP = 'DELETE') THEN
	
	...

	return OLD;

  ELSIF (tg_op = 'INSERT') THEN
	
	...

	RETURN NEW;
  ELSIF (tg_op = 'UPDATE') THEN
	
	SELECT count(id) into counter from t_buch
	WHERE ranking = NEW.ranking
	AND id <> NEW.id;

	
	IF ( counter = 1 ) THEN
		UPDATE t_buch
		SET ranking = ranking+1
		WHERE ranking = NEW.ranking 
		AND id <> NEW.id;
	ELSIF ( counter > 1 ) THEN
		RAISE EXCEPTION 'COUNTER cannot be greater than 1.';
	END IF;
	
	RETURN NEW;
  END IF;
END

Vielen Dank für Eure Hilfe,
–Heiko

Nun, möglicherweise wirst Du bei großen Tabellen Laufzeitprobleme (schlechte Performance) haben. Es sind recht viele Updates nötig, es entstehen viele geänderte Zeilen (dead tuples).

Andreas