Firebird: Row Counter

1. Einleitung

Schema des DatensatzzählersDas Zählen von Datensätzen mittels COUNT(*) über eine ganze Tabelle kann in Firebird und Interbase, je nach Größe der Tabelle, einige Zeit in Anspruch nehmen.

Im Web werden verschieden Möglichkeiten vorgeschlagen, dieses Problem zu umgehen.
  • Auf firebirdsql.org, im Generator Guide, wird die Zählung mittels Triggern beschrieben. Es wird aber auch gleich darauf hingewiesen, daß die Zählung bei einem ROLLBACK aus dem Ruder läuft, da Trigger außerhalb der Transaktionskontrolle geändert werden.
     
  • In The Firebird FAQ werden unter Select count(*) is slow zwei Möglichkeiten beschrieben:
     
    1. Eine Zähltabelle, in der bei jeder Einfüge- und Löschoperation ein Datensatz mit +1 bzw. -1 und der Tabellenname eingetragen wird. Mittels der Aggregatfunktion SUM() wird dann die Anzahl der Datensätze, gruppiert nach Tabellennamen, ermittelt. Um die Tabelle klein zu halten, müssen regelmäßig die einzelnen Datensätze pro Tabellennamen aufsummiert und als einziger Datensatz abgelegt werden.
       
    2. Die Ermittlung der Datensatzanzahl über die Statistik des Primäschlüssels oder eines anderen eindeutigen Index. Da die Statistik eines Index der reziproke Wert der Datensatzanzhl ist, kann die Datensatzanzahl mi 1/Statistikwert berechnet werden. Hierbei kann es allerdings zu Rundungsbroblemen kommen, weil das Feld RDB$STATISTICS in RDB$INDICES einen DOUBLE PRECISION Wert enthält.
Hier wird eine Variante aus Trigger und Zähltabelle beschrieben, die Transaktionssicher ist und sich auch für Zählungen anderer Datensatzeigenschaften, z.B. eines Aktiv-Flags, eignet.

Zählung

Die einzelnen Zähldatensätze werden innerhalb einer Transaktion in der globalen temporären Tabelle "_TABLE_ROW_COUNT" gespeichert. Der Typ dieser Tabelle ist ON COMMIT DELETE ROWS, damit die aktuelle Zählung nur innerhalb dieser Transaktion gültig ist. In jedem Datensatz wird der Generator vermerkt, auf den die bei einem COMMIT auszuführende Auswertung wirksam wird.
CREATE GLOBAL TEMPORARY TABLE "_TABLE_ROW_COUNT" (
    "rcn_generator"  "dom_SYS$NAME" NOT NULL,
    "rcn_count"      "dom_INT16" NOT NULL
) ON COMMIT DELETE ROWS;

Auswertung

Die Auswertung erfolgt in einem Datenbank-Trigger, der bei einem COMMIT gefeuert wird. Innerhalb dieses Triggers werden alle Datensätze, gruppiert nach Zielgenerator, aufsummiert und daraus eine EXECUTE BLOCK Statement gebaut. Dieses Statement in einem VARCHAR wird dann mittels EXECUTE STATEMENT ausgeführt, um den zugehörigen Generator zu aktualisieren.
CREATE TRIGGER ROW_COUNT_COMMIT
ACTIVE ON TRANSACTION COMMIT POSITION 0
AS
DECLARE VARIABLE SQL TYPE OF "dom_Statement";
BEGIN
  /* alle Daten aus der Zähltabelle aufsummieren, */
  /* Statement bauen und ausführen                */
  FOR
      SELECT 'EXECUTE BLOCK AS '                                ||
             'DECLARE VARIABLE DUMMY TYPE OF "dom_INT64"; '     ||
             'BEGIN '                                           ||
               'DUMMY = GEN_ID('                                ||
                          "rcn_generator" || ', '               ||
                          CAST(SUM("rcn_count") AS VARCHAR(20)) ||
                       '); '                                    ||
             'END'
        FROM "_TABLE_ROW_COUNT"
    GROUP BY "rcn_generator"
      HAVING SUM("rcn_count") <> 0
  INTO :SQL
  DO
    EXECUTE STATEMENT SQL;
END