Matheu: Brauche Idee, für Titel Ausgabe bei einer SELECT abfrage, Ausgabe, gruppierte Var, dann Titel

Hallo, ich hole über eine SELECT Abfrage aus meiner MYSQL Datenbank meine Informationen die ich dann über eine WHILE Schleife mittels PHP ausgebe. Wobei die VAR cat mein Problem darstellt.

Ich möchte vor jedem CATtWechsel, eine Titel, den Namen der CAT ausgaben. Das holen aus einer weiteren Tabelle in der der CAT Namen steht ist nicht mein Problem, sondern, wie sage ich es meinen Daten, das eben vor jedem CAT Wechsel (Abschnitt) ein Name eingeblendet wird.

cat		Titel
1		Daten1
1		Daten2
1		Daten3
2		Daten4
2		Daten5
3		Daten6
cat		Titel
------Titel CAT
1		Daten1
1		Daten2
1		Daten3
------Titel CAT
2		Daten4
2		Daten5
------Titel CAT
3		Daten6
  1. Hallihallo!

    Hallo, ich hole über eine SELECT Abfrage aus meiner MYSQL Datenbank meine Informationen die ich dann über eine WHILE Schleife mittels PHP ausgebe.

    Ich möchte vor jedem CATtWechsel, eine Titel, den Namen der CAT ausgaben.

    Ich mache das immer so (ungetesteter Code):

    $lastCat = 0;
    while ($ds =) {
       if ($ds['cat'] != $lastCat) {
           // gib den Namen der Kategporie aus
       }// andere normale Ausgaben
       $lastCat = $ds['cat'];
    }
    
    

    Beste Grüsse, Tobias Hahner

    1. Perfekt, ABER bekomme ich es auch so hin das ich das Ende ermitteln kann. Wenn ich praktisch eine Ausgabe einer kompletten CAT in einem DIV ausgeben will,muss ich dieses ja öffnen und wieder schliessen.

      $lastCat = 0;
      while ($ds = …) {
         if ($ds['cat'] != $lastCat) {
             echo'<div>'; # Öffnen des DIV
         }
         … // andere normale Ausgaben
         $lastCat = $ds['cat'];
      }
      
      
      1. Hallihallo!

        Wenn ich praktisch eine Ausgabe einer kompletten CAT in einem DIV ausgeben will,muss ich dieses ja öffnen und wieder schliessen.

        Da gibt es mehrere Möglichkeiten. Einfach nachzurüsten wäre zum Beispiel:

        $lastCat = 0;
        while ($ds =) {
           if ($ds['cat'] != $lastCat) {
             if ($lastCat != 0) echo '</div>'; # Schliessen des vorherigen DIV
                echo'<div>'; # Öffnen des DIV
           }// andere normale Ausgaben
           $lastCat = $ds['cat'];
        }
        
        

        Man kann aber auch, wie Rolf schon vorgeschlagen hatte, erstmal alle Datensätze in einem geeigneten Container sammeln, dann weitere Berechnungen (falls nötig) durchführen, und erst am Ende ausgeben. Das wäre dann das EVA- Prinzip, was in Hinsicht auf eventuelle spätere Erweiterungen auf jeden Fall das Praktikabelste wäre:

        // erstmal alle Daten sammeln.
        // da ich jetzt schon weiss, dass ich nach Kategorien gruppiert ausgeben will, bereite ich das hier schon entsprechend vor:
        $container = array();
        while ($ds =) {
           if (!is_array($container[$ds['cat'])) $container[$ds['cat']] = array();
           $datensatz = $ds;
           // hier kann man mit $datensatz noch andere Sachen anstellen, falls noch
           // etwas berechnet werden soll.$container[$ds['cat']][] = $datensatz;
        }
        
        // Ausgabe:
        foreach ($container as $cat => $datensaetze) {
           echo '<h2>'.$cat.'</h2>'; #oder sowas wie catname($cat) als Funktion, oder oder oder
           echo '<div>';
           foreach ($datensaetze as $datensatz) {
              echo htmlspecialchars($datensatz['name']);
              // und was sonst noch so ausgegeben werden soll
           }
           echo '</div>'; # Hier ist die Kategorie zu Ende
        }
        
        

        Man sollte eigentlich auch den Namen der Kategorie mit in den Container basteln, dann könnte man nämlich den $container zum Beispiel an eine Template-Engine übergeben, die sich dann autark um die Ausgabe kümmert.

        Ich hoffe, damit konnte ich Dir helfen.

        Beste Grüsse, Tobias Hahner

        1. Hallo Tobias,

          eine Template-Engine ist eine ganz andere Sache, in dem Fall sollte man die Daten tatsächlich vorab in einem Array sammeln. Aber gut überlegen, wieviele Zeilen man da aufbereitet, um den Speicher nicht zu sehr zu stressen.

          Es ist eigentlich erstaunlich, PHP hat so viele Array-Funktionen, aber sowas wie "array_group" ist nicht dabei. Das wäre was für das eigene Schatzkästlein und würde dem ersten Teil deines Codes entsprechen. Wobei Du da etwas machst, was PHP implizit für Dich erledigt: Leeres Array anlegen wenn noch kein Eintrag für die Kategorie da ist. Das hier reicht:

          function array_group($array, $column) {
             $result = [];
             foreach ($arr as $r)
               $result[$r[$column]][] = $r;
             return $result;
          }
          
          $container = array_group($queryResult->fetch_all(), 'cat');
          

          Ein "Einzeiler" mit array_reduce wäre übrigens machbar, aber keine gute Idee.

          By the way: Mein "Struktur" Ansatz nach Jackson ist ebenfalls EVA. Niemand verbietet Dir, um die gute alte Tante noch ein oder mehrere rosa Schleifchen zu wickeln.

          Rolf

          --
          sumpsi - posui - obstruxi
          1. eine Template-Engine ist eine ganz andere Sache, in dem Fall sollte man die Daten tatsächlich vorab in einem Array sammeln. Aber gut überlegen, wieviele Zeilen man da aufbereitet, um den Speicher nicht zu sehr zu stressen.

            Da muss man mit PHP und PDO sowieso aufpassen: wenn man gepufferte Querys verwendet (PHPs Standardeinstellung), dann wird das gesamte Resultset in den Speicher geladen.

            Eine Möglichkeit den Speicherbedarf zu reduzieren sind ungepufferte Queries. Dann sollte man aber auch kein fetchAll verwenden, das würde nur wieder dazu führen, dass alles in den Speicher geladen wird.

            Das kann mit trägen Datenstrukturen wie Iteratoren vermeiden. Dein Algorithmus in PHP übersetzt, würde dann zu so etwas werden (ungetestet):

            function groupBy(Traversable $rows, string $col) : Iterator {
               $it = new IteratorIterator($rows);
               while ($it->valid()) {
                   yield slice($it, $col);
               }
            }
            
            function slice(Iterator $it, string $col) : Iterator {
               $row = $it->current();
               $cat = $row[$col];
               while ($it->isValid() && $row[$col] == $cat) {
                   yield $row;
                   $it->next();
                   $row = $it->current();
               }
            }
            

            Und benutzen ließe es sich so:

            $query->execute();
            $categories = groupBy($query, 'cat');
            foreach ($categories as $category) {
                // Kategorie-Kopf ausgeben
                foreach ($category as $entry) {
                    // Zeile ausgeben
                }
            }
            

            Wenn alle Zeilen einfach ausgegeben werden, wie in diesem Beispiel, dann müsste man auch dafür sorgen, dass der Output-Buffer zwischendurch geflusht wird. Ansonsten hat man schon wieder ein Speicherproblem.

            Bleibt noch zu sagen, dass diese Optimierung nicht umsonst ist, hier wird Speicherdarf gegen Rechnzeit getauscht. Normalerweise ist Speicher aber günstiger als Rechenleistung, deshalb lohnt sich so eine Optimierung meistens nicht.

            1. Hallo 1unitedpower,

              brrr - jetzt hast Du mich abgehängt. Oder Du hast geschickt ein Logikloch versteckt.

              Diese "hier geschieht ein Wunder" Zeile hier bringt mich aus dem Tritt

              // Kategorie-Kopf ausgeben
              

              Iteratoren und Generatoren verwende ich sehr gerne in C#, aber in PHP tue ich mich teils mit der Syntax schwer. Wenn ich das richtig verstanden habe, ist deine groupBy Funktion ein Generator, der Generatoren pro Kategorie erzeugt und zurückgibt. In der Anwendung nimmt die äußere Schleife diese Generatoren entgehen (als $category) und arbeitet sie in der inneren Schleife ab.

              Aber woher weiß die Anwendung an der Stelle, wo dein Kommentar steht, was in den Kategoriekopf gehört?

              Rolf

              --
              sumpsi - posui - obstruxi
              1. Iteratoren und Generatoren verwende ich sehr gerne in C#, aber in PHP tue ich mich teils mit der Syntax schwer. Wenn ich das richtig verstanden habe, ist deine groupBy Funktion ein Generator, der Generatoren pro Kategorie erzeugt und zurückgibt. In der Anwendung nimmt die äußere Schleife diese Generatoren entgehen (als $category) und arbeitet sie in der inneren Schleife ab.

                Korrekt soweit.

                Aber woher weiß die Anwendung an der Stelle, wo dein Kommentar steht, was in den Kategoriekopf gehört?

                Gute Frage. In meinem Beispiel ist eine Kategorie nichts weiter als eine Sammlung von Zeilen. Kopfdaten, wie Titel oder den Kateogrie-Schlüssel, habe ich vernachlässigt, da habe ich wohl zu sehr gekürzt. Hier mal für den Kategorie-Schlüssel nachgeholt:

                function groupBy(Traversable $rows, string $col) : Iterator {
                   $it = new IteratorIterator($rows);
                   while ($it->valid()) {
                       $key = $it->current()[$col];
                       yield [$key, slice($it, $col)];
                   }
                }
                

                Eine Kategorie ist jetzt also ein Paar bestehend aus einem Schlüssel und einer Sammlung von Zeilen. Schöner wäre natürlich an dieser Stelle eine Kategorie-Klasse.

                foreach ($categories as list($key, $category)) {
                    // Kategorie-Kopf ausgeben
                    foreach ($category as $entry) {
                        // Zeile ausgeben
                    }
                }
                
          2. Hallihallo!

            function array_group($array, $column) {
               $result = [];
               foreach ($arr as $r)
                 $result[$r[$column]][] = $r;
               return $result;
            }
            
            $container = array_group($queryResult->fetch_all(), 'cat');
            

            Die Funktion gefällt mir, die kommt auf jeden Fall in meine Truhe 😀 (Bis auf die kleine Änderung, dass in der foreach das "$arr" ein "$array" sein sollte…)

            By the way: Mein "Struktur" Ansatz nach Jackson ist ebenfalls EVA.

            Das wollte ich eigentlich auch damit ausdrücken, und habe deshalb Deinen Ansatz auch empfohlen. Ist vielleicht nicht ganz deutlich geworden, war aber so gemeint.

            Beste Grüsse, Tobias Hahner

  2. Hallo Matheu,

    das nennt sich Gruppenwechsel. Es gibt einen Artikel im Wiki.

    Bis demnächst
    Matthias

    --
    Du kannst das Projekt SELFHTML unterstützen,
    indem du bei Amazon-Einkäufen Amazon smile (Was ist das?) nutzt.
  3. Hallo Matheu,

    du kannst das so machen, wie Tobias das aufgeschrieben hat.

    Aber tatsächlich ist dein Problem ein Musterproblem der strukturierten Programmierung: wie passt man die Programmstruktur an die Datenstruktur an. In den 70 Jahren, als man dieses Thema standardisierte, hat man Dateien gelesen. Heute liest man SQL Query-Ergebnisse. Aber im Prinzip ist es das Gleiche.

    Deine Eingabe:

    Queryergebnis
       Row*
    

    Lies: Ein Queryergebnis, das Queryergebnis ist eine Iteration von Rows

    Deine Ausgabe:

    HTML-Segment
       Kategoriengruppe*
          Überschrift
          Inhalt
             Detailsatz*
    

    Lies: Deine Ausgabe ist ein Stück HTML, das aus Kategoriegruppen besteht. Eine Gruppe ist eine Abfolge von Überschrift und Inhalt. Der Inhalt ist eine Iteration von Detailsätzen.

    Aufgabe der strukturierten Programmierung ist, eine Programmstruktur zu finden, die gleichzeitig zu Eingabe- und Ausgabestruktur passt.

    Der Trick besteht in den meisten Fällen darin, einmal vorauszulesen. Damit wirft man einen kleinen Blick in die Zukunft und weiß, wie man die Logik steuern muss.

    Pseudocode: (Editiert: innere WHILE-Bedingung war falsch)

    Query ausführen
    Fetch auf erste Row
    WHILE (row vorhanden)
       $cat = $row['cat'];
       Kategorieüberschrift schreiben
       WHILE (row vorhanden UND row['cat'] == $cat)
          Detailzeile schreiben
          Fetch auf nächste Row
       END
    END
    

    So machte man das vor 50 Jahren. Und so funktioniert das auch heute noch :)

    Literatur: Michael Jackson (sic!), Principles of Program Design

    en. Wikipedia über jackson-structured programming

    dt. Wikipedia über Jackson-Diagramme

    Das klingt heute in der Zeit von objektorientierter und funktionaler Programmierung alles so obsolet. Als ich in den 80er Jahren Programmieren lernte, habe ich als BASIC Autodidakt angefangen, und dann in der Ausbildung JSP lernen müssen und fühlte mich zuerst vera...lbert. Aber für spezifische Aufgaben, wie genau dieser hier, ist Kenntnis dieser Techniken relevant und zeitlos.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Den Code vor 50 Jahren verstehe ich ;-)

      villeicht bekomme ich mein ENde aus einer Mishcung zischen neu und alt. Wenn ich mit meiner ESLECT abfrage die CAT zählen könnte, könnte ich anschliessend in PHP einfach das Ende errechnen.

      Nur wenn ich in meine SELCT Abfrage ein

      COUNT(a.cat)AS anzahl

      Einbaue, bekomme ich zwar die gesamt Anzahl, aber leider auch nur noch eine Zeile und nicht mehr alle Zeilen angezeigt

      1. Hallo Matheu,

        für den von mir dargestellten Ablauf brauchst du nicht zu zählen oder ein Ende zu errechnen. Das ist unnötige Mühe, die Gruppierung ergibt sich durch die Vorauslesetechnik ganz von allein. Der eine Satz, den Du vorausliest, erlaubt einen hinreichenden Blick in die Zukunft, um damit die notwendigen Entscheidungen treffen zu können.

        Es sei denn, du willst VORHER ausgeben, wieviele Kategorien es geben wird. Davon war bisher nicht die Rede - und in dem Fall könnte man auch die HTML Ausgabe zwischenpuffern (Stichwort: ob_start + ob_get_flush) und währenddessen zählen, wieviele Kategorien kommen. Oder Du verwendest fetch_all, dann hast Du alle Rows im Speicher, und kannst sie einmal zum Zählen durchgehen (mit PHP dann, nicht mit SQL).

        Ein SELECT COUNT(DISTINCT cat) (beachte DISTINCT) kann nur eine Zeile liefern, das hast Du ja so bestellt :) Eine Aggregierung und eine Detailabfrage in einem Zug geht nicht, dafür müsstest Du 2 Abfragen machen.

        Falls Du dich fragst, wie du das mit dem <div> hinbekommen sollst - ich erweitere meinen Pseudocode mal. Woher Du $catUeberschrift bekommst, lasse ich offen - dafür weiß ich zu wenig vom Umfeld des Codes.

        Das Beispiel geht davon aus, dass Du ein mysqli_result verwendest und die Ausgabevariablen nicht mit bind_result bindest.

        Statt des echo-Befehls schalte ich zwischen PHP und HTML Modus um. In diesem Fall ist die alternative Syntax für Kontrollstrukturen besser lesbar, darum verwende ich die.

        Deine Ausgabe sieht nach einer Tabelle aus, darum verwende ich mal <table>. Es mag semantisch falsch sein, aber dafür weiß man zu wenig.

        $result = /* query ausführen und result ermitteln */
        $row = /* Fetch auf erste Row */
        ?>
        <table>
          <thead>
            <tr><th>cat</th><th>Titel</th></tr>
          </thead>
        <?php
        while ($row):
           $cat = $row['cat'];
           $catTitel = "whatever";
        ?>
          <tbody>
            <tr><th colspan="2">------- <?= $catTitel ?></th></tr>
        <?php     
           while ($row && row['cat'] == $cat):
        ?>
            <tr><td><?= $row['id'] ?></td><td><?= $row['data'] ?></td></tr>
        <?php
              $row = /* Fetch auf nächste Row */
           endwhile;
        ?>
          </tbody>
        <?php
        endwhile;
        ?>
        </table>
        <?php
        

        Du sieht: Kein Schalter, kein Zähler, nur Struktur.

        Rolf

        --
        sumpsi - posui - obstruxi
    2. Query ausführen
      Fetch auf erste Row
      WHILE (row vorhanden)
        $cat = $row['cat'];
        Kategorieüberschrift schreiben
        WHILE (row vorhanden ODER row['cat'] != $cat)
           Detailzeile schreiben
           Fetch auf nächste Row
        END
      END
      

      Müsste die Bedingung in der inneren Schleife nicht so lauten?

      WHILE (row vorhanden AND row['cat'] == $cat)
      

      Lies: Solange noch eine Zeile vorhanden ist und sie sich in der selben Kategorie wie ihre Vorgängerin befindet.

      1. Hallo 1unitedpower,

        yup, danke, habe ich beim Schreiben des PHP Beispiels auch gesehen und editiert.

        Rolf

        --
        sumpsi - posui - obstruxi