Auge: Problem mit GROUP_CONCAT in Subquery

Hallo

Ich bilde die Navigationsstruktur einer Seite in einer MySQL-Datenbank ab. Die Tabelle hat (sinngemäß) folgende Struktur.

id | pfad              | titel       | parent | sortierer
--------------------------------------------------------
1  | index.php         | Home        | 0      | 0
2  | k/index.php       | Kontakt     | 0      | 1
3  | k/impressum.php   | Impressum   | 2      | 1
4  | k/datenschutz.php | Datenschutz | 2      | 2

Das Feld parent ist dazu da, die Punkte einer Unternavigation dem zugehörigen Punk der Hauptnavigation zuzuweisen. Es verweist auf das Feld id des Hauptmenüpunkts bzw. ist bei Hauptmenüpunkten mit 0 angegeben. Jeder Hauptnavigationspunkt selbst ist eine index.php im entsprechenden Unterverzeichnis. Der Sortierer legt die Reihenfolge der Ausgabe in den Menüs fest.

Rufe ich eine der Unterseiten auf, baue ich das dazugehörige Untermenü über die mir bereits bekannte ID des parents zusammen. Das funktioniert natürlich nur bei den Unterseiten, da die index.php des Unterpunktes selbst zur Hauptnavigation gehört (parent = 0). Deswegen habe ich Subqueries in einem IF in einem IN in der WHERE-Klausel notiert.

Ich unterscheide im IF, ob parent 0 ist (oder nicht), wenn 0 zutrifft (Hauptmenüpunkt) hole ich alle Einträge, die die aktuelle Seite als parent haben (deren "Kinder"), wenn nicht (die Seite gehört zu einer Unternavigation), hole ich alle ihre "Geschwister", die die gleiche Seite als parent haben.

SELECT  
  id,  
  parent,  
  pfad,  
  titel  
FROM Struktur  
  WHERE id IN(IF((SELECT parent FROM Struktur WHERE id = "[code lang=php]. intval($currentPage['id']) .
~~~") = 0,  
    (SELECT GROUP\_CONCAT(id) AS id FROM Struktur WHERE parent = "`. intval($currentPage['id']) .`{:.language-php}"),  
    (SELECT GROUP\_CONCAT(id) AS id FROM Struktur WHERE parent = "`. intval($currentPage['parent']) .`{:.language-php}")))  
  ORDER by sortierer ASC"[/code]  
  
Was ich schon einmal sagen kann: es funktioniert nicht wie gewünscht.  
  
Lasse ich jeden der drei Subqueries einzeln laufen, geben sie mir die gewünschte Werte aus. Setze ich auf einer der Seiten, bei denen die Bedingung zutrifft bzw. nicht zutrifft den Subquery für den entsprechenden Fall ohne `IF`{:.language-sql} ein, werden mir ebenfalls die gewünschten Werte zurückgegeben. Folgende Beispiele stammen von einem anderen Punkt der Hauptnavigation.  
  
`IF`{:.language-sql}:  
  
~~~sql
SELECT  
parent  
FROM Struktur  
WHERE id = 10
Array  
(  
    [0] => Array  
        (  
            [parent] => 0  
        )  
)

THEN trifft zu, da es sich um einen Hauptmenüpunkt handelt. Der einzeln abgesetzte Query listet brav die ids der zugehörigen Unterpunkte.

SELECT  
GROUP_CONCAT(id) AS id  
FROM Struktur  
WHERE parent = 10
Array  
(  
    [0] => Array  
        (  
            [id] => 11,12,13,14,15,16,17  
        )  
)

Auch der einzeln abgesetzte Query des ELSE-Zweiges tut, wie erwartet, auch wenn das in diesem Beispiel unerheblich ist.

Ich setze mal alles, soweit bekannt in den ersten Query.

SELECT  
  id,  
  parent,  
  pfad,  
  titel  
FROM Struktur  
  WHERE id IN(IF((SELECT parent FROM Struktur WHERE id = 0) = 0,  
    11,12,13,14,15,16,17,  
    (SELECT GROUP_CONCAT(id) AS id FROM Struktur WHERE parent = "[code lang=php]. intval($currentPage['parent']) .
~~~")))  
  ORDER BY sortierer ASC"[/code]  
  
Ich erwarte also die Daten zu den Seiten mit den IDs 11 bis 17 vorzufinden. Ausgegeben wird aber nur der Datensatz für die id 11. Ich stehe nun auf dem Schlauch und weiß nicht weiter. Bin ich mit diesem zugegebenermaßen nicht unkomplizierten Vorgehen vielleicht auf dem Holzweg?  
  
Tschö, Auge  

-- 
Verschiedene Glocken läuteten in der Stadt, und jede von ihnen vertrat eine ganz persönliche Meinung darüber, wann es Mitternacht war.  
Terry Pratchett, "Wachen! Wachen!"  
  
ie:{ fl:| br:> va:) ls:[ fo:) rl:( ss:| de:> js:| zu:}  
  
[Veranstaltungsdatenbank Vdb 0.3](http://termindbase.auge8472.de/)
  1. Tach!

    SELECT

    id,
      parent,
      pfad,
      titel
    FROM Struktur
      WHERE id IN(IF((SELECT parent FROM Struktur WHERE id = "[code lang=php]. intval($currentPage['id']) .

    
    >     (SELECT GROUP\_CONCAT(id) AS id FROM Struktur WHERE parent = "`. intval($currentPage['id']) .`{:.language-php}"),  
    >     (SELECT GROUP\_CONCAT(id) AS id FROM Struktur WHERE parent = "`. intval($currentPage['parent']) .`{:.language-php}")))  
    >   ORDER by sortierer ASC"[/code]  
      
    GROUP\_CONCAT() liefert einen String zurück. Der wird komplett als ein Parameter von IN() angesehen. Du willst jedoch, dass er als kommaseparierte Liste angesehen wird. Das geschieht aber nicht, weil das bedeuten würde, dass Daten als Code interpretiert werden müssten. Wenn du die Werte einzeln verarbeitbar haben willst, muss die Query eine Ergebnismenge zurückliefern.  
      
    Ob du damit ans Ziel kommst oder ob es bessere Varianten für die eigentliche Aufgabenstellung gibt, habe ich nicht untersucht.  
      
      
    dedlfix.
    
    1. Hallo

      Tach!

      SELECT

      id,
        parent,
        pfad,
        titel
      FROM Struktur
        WHERE id IN(IF((SELECT parent FROM Struktur WHERE id = "[code lang=php]. intval($currentPage['id']) .

      
      > >     (SELECT GROUP\_CONCAT(id) AS id FROM Struktur WHERE parent = "`. intval($currentPage['id']) .`{:.language-php}"),  
      > >     (SELECT GROUP\_CONCAT(id) AS id FROM Struktur WHERE parent = "`. intval($currentPage['parent']) .`{:.language-php}")))  
      > >   ORDER by sortierer ASC"[/code]  
      >   
      > GROUP\_CONCAT() liefert einen String zurück. Der wird komplett als ein Parameter von IN() angesehen. Du willst jedoch, dass er als kommaseparierte Liste angesehen wird.  
        
      Verständnisfrage: Wenn ich den Query händisch zusammenflicke, ist der betreffende Teil doch auch nur ein String. Was ist hier anders?  
        
      
      > Das geschieht aber nicht, weil das bedeuten würde, dass Daten als Code interpretiert werden müssten. Wenn du die Werte einzeln verarbeitbar haben willst, muss die Query eine Ergebnismenge zurückliefern.  
      >   
      > Ob du damit ans Ziel kommst ... habe ich nicht untersucht.  
        
      Mit der normalen Ergebnismenge bin ich ebenfalls gescheitert, da dann (völlig zu recht) angemeckert wird, dass der Subquery mehr als ein Ergebnis zurückgegeben hat (`Subquery returns more than 1 row`{:.language-sql}).  Mit dem [in der Doku (ERROR 1242)](http://dev.mysql.com/doc/refman/5.1/de/subquery-errors.html) empfohlenen `ANY`{:.language-sql} vor dem Subquery haut's mir in meinem Konstrukt einen Systax-Error um die Ohren, zumal ich da \*irgendein\* Ergebnis bekäme.  
        
      Tschö, Auge  
      
      -- 
      Verschiedene Glocken läuteten in der Stadt, und jede von ihnen vertrat eine ganz persönliche Meinung darüber, wann es Mitternacht war.  
      Terry Pratchett, "Wachen! Wachen!"  
        
      ie:{ fl:| br:> va:) ls:[ fo:) rl:( ss:| de:> js:| zu:}  
        
      [Veranstaltungsdatenbank Vdb 0.3](http://termindbase.auge8472.de/)
      
      1. Tach!

        GROUP_CONCAT() liefert einen String zurück. Der wird komplett als ein Parameter von IN() angesehen. Du willst jedoch, dass er als kommaseparierte Liste angesehen wird.
        Verständnisfrage: Wenn ich den Query händisch zusammenflicke, ist der betreffende Teil doch auch nur ein String. Was ist hier anders?

        Das SQL-Statement wird geparst. Dann wird das Parse-Ergebnis ausgeführt. Der Parser hat nun fertig und dein Kommas enthaltender String, der erst während der Verarbeitung anfällt, bleibt wie er ist. MySQL kann ja auch nicht hellsehen, wann der Autor ein Parsen wünscht und wann das Ergebnis einer Funktion wie es ist weiterverarbeitet werden soll. So bleibt's bei letzterem.

        Mit der normalen Ergebnismenge bin ich ebenfalls gescheitert, da dann (völlig zu recht) angemeckert wird, dass der Subquery mehr als ein Ergebnis zurückgegeben hat (Subquery returns more than 1 row).

        Ja, weil das IF() nur einen Parameter und keine Ergebnismenge haben will. IN(SELECT ...) ginge.

        dedlfix.

        1. Hallo

          GROUP_CONCAT() liefert einen String zurück. Der wird komplett als ein Parameter von IN() angesehen. Du willst jedoch, dass er als kommaseparierte Liste angesehen wird.
          Verständnisfrage: Wenn ich den Query händisch zusammenflicke, ist der betreffende Teil doch auch nur ein String. Was ist hier anders?

          Das SQL-Statement wird geparst. Dann wird das Parse-Ergebnis ausgeführt. Der Parser hat nun fertig und dein Kommas enthaltender String, der erst während der Verarbeitung anfällt, bleibt wie er ist. MySQL kann ja auch nicht hellsehen, wann der Autor ein Parsen wünscht und wann das Ergebnis einer Funktion wie es ist weiterverarbeitet werden soll. So bleibt's bei letzterem.

          Schade, ich bin halt davon ausgegangen, dass, da die Queries von innen nach außen ausgeführt werden, die Ergebnisse der inneren Queries dem äußeren Query als das, was sie sind (hier ein String mit einer Aufzählung im für IN() korrekten Format), fix und fertig zur Verfügung stehen.

          Ich habe das jetzt in zwei Schritten erledigt.

          • Schritt 1: Anhand des bekannten Werts von 'parent' wird mit dem jeweils passenden Query die Werteliste generiert.
          # get the items for the sub navigation  
          if ($currentPage['parent'] == 0) {  
          $queryGetSubNavPrep = "[code lang=sql]SELECT  
            GROUP_CONCAT(id) AS id  
          FROM Struktur  
          WHERE parent = 
          ~~~". intval($currentPage['id']);  
          } else {  
          $queryGetSubNavPrep = "~~~sql
          SELECT  
            GROUP_CONCAT(id) AS id  
          FROM Struktur  
          WHERE parent = 
          ~~~". intval($currentPage['parent']);  
          }[/code]  
            
          Das ergibt mit dem Wert [aus dem Eröffnungsposting](https://forum.selfhtml.org/?t=215572&m=1476637) zuverlässig folgende Liste.  
            
          ~~~php
          Array  
          (  
              [0] => Array  
                  (  
                      [id] => 11,12,13,14,15,16,17  
                  )  
          )
          

          2. Schritt: Das Ergebnis der vorherigen Abfrage wird in den nächsten Query eingefügt.

            
          $queryGetSubNav = "[code lang=sql]SELECT  
          id,  
          parent,  
          pfad,  
          titel  
          FROM Struktur  
          WHERE id IN([code lang=php]". $valueSubNavPrep[0]['id'] ."
          ~~~)  
          ORDER by sortierer ASC[/code]";  
          [/code]  
            
          Das ist zwar um einiges weniger elegant, aber schlussendlich funktioniert es.  
            
          
          > > Mit der normalen Ergebnismenge bin ich ebenfalls gescheitert, da dann (völlig zu recht) angemeckert wird, dass der Subquery mehr als ein Ergebnis zurückgegeben hat (`Subquery returns more than 1 row`{:.language-sql}).  
          >   
          > Ja, weil das IF() nur einen Parameter und keine Ergebnismenge haben will. IN(SELECT ...) ginge.  
            
          jup  
            
          Tschö, Auge  
          
          -- 
          Verschiedene Glocken läuteten in der Stadt, und jede von ihnen vertrat eine ganz persönliche Meinung darüber, wann es Mitternacht war.  
          Terry Pratchett, "Wachen! Wachen!"  
            
          ie:{ fl:| br:> va:) ls:[ fo:) rl:( ss:| de:> js:| zu:}  
            
          [Veranstaltungsdatenbank Vdb 0.3](http://termindbase.auge8472.de/)
          
          1. Tach!

            Schade, ich bin halt davon ausgegangen, dass, da die Queries von innen nach außen ausgeführt werden, die Ergebnisse der inneren Queries dem äußeren Query als das, was sie sind (hier ein String mit einer Aufzählung im für IN() korrekten Format), fix und fertig zur Verfügung stehen.

            Auch dann bliebe das Problem bestehen, was als String und was als Code angesehen werden soll. Außerdem ist das gar nicht so einfach mit dem Auflösen. Bei einer Correlated Subquery wird diese ja mehrfach aufgerufen, und es muss erstmal der Wert aus der Hauptquery bekannt sein, damit man die Subquery aufrufen kann. Ich weiß nicht, ob der Query-Ausführer einfach feststellen kann, wann eine Subquery ein konstantes Ergebnis liefert und wann es bei jedem Durchlauf verschieden ist. Ich denke, man muss davon ausgehen, dass bei jedem Datensatz der innere Teil wieder neu ausgewertet wird.

            Ich habe das jetzt in zwei Schritten erledigt.

            • Schritt 1: Anhand des bekannten Werts von 'parent' wird mit dem jeweils passenden Query die Werteliste generiert.

            get the items for the sub navigation

            if ($currentPage['parent'] == 0) {
            $queryGetSubNavPrep = "[code lang=sql]SELECT
              GROUP_CONCAT(id) AS id
            FROM Struktur
            WHERE parent =

            
            > } else {  
            > $queryGetSubNavPrep = "~~~sql
            
            SELECT  
            
            >   GROUP_CONCAT(id) AS id  
            > FROM Struktur  
            > WHERE parent = 
            
            ~~~". intval($currentPage['parent']);  
            
            > }[/code]  
              
            Der variable Teil sitzt doch in der WHERE-Klausel. Du kannst da das IF() einbauen und die drei Parameter übergeben, oder du berechnest den Wert in PHP und übergibst übergibst das Ergebnis als Parameter für "WHERE parent = ?" Und lass das GROUP\_CONCAT() weg. Damit bekommst du eine Ergebnismenge. Und die Query, die diese Ergebnismenge erzeugt, lässt sich als Subbquery ins IN() schreiben.  
              
              
            dedlfix.