Da das Thema Gruppenwechsel immer mal wieder im Forum auftaucht, will ich an dieser Stelle mal einen lächerlich einfachen Ansatz vorstellen und erklären.

Wenn man eine Abfrage über mehrere Datenbanktabellen durchführt, erhält man fast immer eine Ergebnismenge in der Daten mehrfach vorkommen, die zwar in der Tabelle aus der sie stammen eindeutig sind, durch die Verknüpfung mit anderen Tabellen jedoch mehrfach in verschiedenen Kombinationen erscheinen. Ein Beispiel:

Code:
+---------------------------------------------------------+
| table: revision                                         |
+----+------------+--------+---------------------+--------+
| id | table_fkey | row_id | time                | type   |
+----+------------+--------+---------------------+--------+
|  1 |          1 |   NULL | 2010-03-03 09:20:30 | insert |
|  2 |          1 |      1 | 2010-03-03 09:20:30 | update |
|  3 |          1 |      1 | 2010-03-03 09:20:30 | update |
|  4 |          1 |   NULL | 2010-03-03 09:20:30 | insert |
|  5 |          1 |      2 | 2010-03-03 09:20:30 | update |
|  6 |          1 |      2 | 2010-03-03 09:20:31 | update |
|  7 |          1 |      1 | 2010-03-03 09:20:31 | update |
|  8 |          1 |      2 | 2010-03-03 09:20:31 | update |
|  9 |          1 |   NULL | 2010-03-03 09:20:31 | insert |
| 10 |          1 |      3 | 2010-03-03 09:20:31 | update |
:    :            :        :                     :        :
+----+------------+--------+---------------------+--------+

+-------------------------------------------------------------------+
| table: change                                                     |
+----+-------------+---------------+--------------------+-----------+
| id | column_fkey | revision_fkey | new_value          | old_value |
+----+-------------+---------------+--------------------+-----------+
|  1 |           1 |             1 | 1                  | NULL      |
|  2 |           2 |             1 | 2                  | NULL      |
|  3 |           3 |             1 | NULL               | NULL      |
|  4 |           4 |             1 |                    | NULL      |
|  5 |           3 |             2 | phpstaruml         | NULL      |
:    :             :               :                    :           :
| 13 |           2 |             7 | 4                  | 2         |
| 14 |           1 |             8 | 2                  | 3         |
| 15 |           2 |             8 | 3                  | 4         |
| 16 |           1 |             9 | 5                  | NULL      |
| 17 |           2 |             9 | 6                  | NULL      |
| 18 |           3 |             9 | NULL               | NULL      |
| 19 |           4 |             9 |                    | NULL      |
| 20 |           3 |            10 | AssociationTag.php | NULL      |
:    :             :               :                    :           :
+----+-------------+---------------+--------------------+-----------+
Die Abfrage

Code:
select
	revision.id as rev_id,
	revision.row_id,
	revision.type,
	`change`.id as chg_id,
	`change`.new_value,
	`change`.old_value,
from `change`
join revision on revision.id = change.revision_fkey;
liefert dann jeden revision-Datensatz mehrfach, wenn ihm mehrere change-Datensätze zugeordnet sind:

Code:
+--------+--------+--------++--------+--------------------+-----------+
| rev_id | row_id | type   || chg_id | new_value          | old_value |
+--------+--------+--------++--------+--------------------+-----------+
|      1 |   NULL | insert ||      1 | 1                  | NULL      |
|      1 |   NULL | insert ||      2 | 2                  | NULL      |
|      1 |   NULL | insert ||      3 | NULL               | NULL      |
|      1 |   NULL | insert ||      4 |                    | NULL      |
|      2 |      1 | update ||      5 | phpstaruml         | NULL      |
:        :        :        ::        :                    :           :
|      7 |      1 | update ||     13 | 4                  | 2         |
|      8 |      2 | update ||     14 | 2                  | 3         |
|      8 |      2 | update ||     15 | 3                  | 4         |
|      9 |   NULL | insert ||     16 | 5                  | NULL      |
|      9 |   NULL | insert ||     17 | 6                  | NULL      |
|      9 |   NULL | insert ||     18 | NULL               | NULL      |
|      9 |   NULL | insert ||     19 |                    | NULL      |
|     10 |      3 | update ||     20 | AssociationTag.php | NULL      |
:        :        :        ::        :                    :           :
+--------+--------+--------++--------+--------------------+-----------+
In der Ausgabe will man so etwas aber meist gruppiert darstellen, weil das einfach übersichtlicher ist oder aber daraus eine hierarchische Struktur (z. B. XML) erstellen und genau das macht man mit einem Gruppenwechsel.

Die am häufigsten anzutreffende Variante besteht darin, einen Wert zwischenzuspeichern, um ihn dann mit dem nächsten vergleichen zu können. Unterscheiden sich die beiden, wird in der Ausgabe der Gruppenbruch erzeugt und den neue Wert für die nachfolgenden Vergleiche zwischengespeichert:

PHP-Code:
$old null;
foreach (
$rows as $row) {
    if (
$row[$column] != $old) {
        
create_group_break($row[$column]);
        
$old $row[$column];
    }
    
create_row_output($row);

Bis hierher ist das auch noch recht übersichtlich, aber damit ist es dann vorbei, wenn man sowieso mit Arrays arbeiten möchte oder der Gruppenwechsel mehrere Ebenen umfasst.

Dabei ist es gerade in PHP so leicht, wenn man sich die Eigenschaften von Arrays zunutze macht. Bei Wertzuweisungen zu einem Array gibt man ja den Schlüssel an, unter dem der Wert gespeichert werden soll:

PHP-Code:
$array["key"] = $neuerWert
Dabei ist es naheliegenderweise egal, ob unter diesem Schlüssel bereits ein Wert gespeichert war (dann wird er überschrieben) oder ob er neu angelegt wird. Das schöne ist aber, dass dies auch über mehrere Ebenen funktioniert:

PHP-Code:
$array["abc"]["def"][] = "wert 1";
$array["abc"]["ghi"][] = "wert 2";
$array["abc"]["def"][] = "wert 3";
print_r($array); 
ergibt

PHP-Code:
Array
(
    [
abc] => Array
        (
            [
def] => Array
                (
                    [
0] => wert 1
                    
[1] => wert 3
                
)
            [
ghi] => Array
                (
                    [
0] => wert 2
                
)
        )

Vorhandene Schlüssel werden also jeweils weiter benutzt und neue einfach hinzugefügt. Genau das ist schon alles, was man für einen Gruppenwechsel braucht. Wenn man also im Beispiel oben die Datensätze zweistufig gruppieren will – zuerst nach Typ, dann nach Revision – braucht man dafür nicht mehr als das hier:

PHP-Code:
$array = array();
foreach (
$rows as $row) {
    
$array[$row["type"]][$row["rev_id"]][] = $row;

Das gruppierte Ergebnis steht dann in $array. Et voilà.