PHP Forum

PHP Forum (http://www.selfphp.de/forum/index.php)
-   MySQLi/PDO/(MySQL) (http://www.selfphp.de/forum/forumdisplay.php?f=22)
-   -   NESTED SETS in XML - Gruppierung von Elementen (http://www.selfphp.de/forum/showthread.php?t=23681)

mr_sol 18.10.2010 08:20:36

NESTED SETS in XML - Gruppierung von Elementen
 
Hallo Thomas!


Auf Basis unterer Baumstruktur und der Basisquery http://www.selfphp.de/forum/showthread.php?t=23668 suche ich eine Möglichkeit der XML Ausgabe bei gruppierten Elementen. Der Vorteil liegt im einfacheren Einfügen von Elementen bei grossen Bäumen.

Code:

vcard
            n
                                  given
                                          familie
                                          additional
            geo
                                  latitude
                                          longitude
                        adr
                                  street
                                          city
                                          country
                                          zip


Code:

<vcard>
<n>
    <given>Max</given>
        <familie>Mustermann</familie>
        <additional>Dr</additional>
</n>
<geo>
    <latitude>48°12'N</latitude>
        <longitude>16°22'E</longitude>
</geo>
<adr>
        <street>Mustergasse 13</street>
        <city>Vienna</city>
        <country>Austria</country>
        <zip>1010</zip>
</adr>
</vcard>


Meine Überlegung. Da das Einfügen in einen Nested Set Baum recht aufwendig ist. (Alle lft,rgt des gesamte Baum
hinter den neuen Element müssen neu beschrieben werden). Gibt es zwei Möglichkeiten.



Lösungsansatz 1) Zusammenfassen von Elemente zu Gruppen

zB: Element geo besteht aus Unterelemente latitude,longitude wobei die Reihenfolge der der Unterelemente egal ist

Am Beispiel vcard würde die Grundstruktur so ausschauen

vcard[1,8]
n[2,3]
geo[4,5]
adr[6,7]


für alle Unterelemente von geo[4,5] gilt (man kann den beliebig viele Unterelemente einfügen mit geringstem Aufwand)
latitude[4,5]
longitude[4,5]


Ergebnis:
vcard[1,8]
n[2,3]
geo[4,5],latitude[4,5],longitude[4,5]
adr[6,7]


geo hat die Eigenschaft die niedrigste id in der Gruppe zu besitzen

Ansatz: Baum mit Elemente zusammenbauen (wie bisher), Unterelemente per group_concat zusammenbauen und nach <> anhängen






Lösungsansatz 2) Zweite Tabelle anlegen

Eine Tabelle für Grobstruktur, eine für Unterelemente




Im Sinne des Nested Set "alles aus eine Struktur" finde ich die erste Lösung besser. Was sagt Du?


grüsse helmut

(Content liefere ich jeweils nach)

thomas_w 18.10.2010 13:12:06

AW: NESTED SETS in XML - Gruppierung von Elementen
 
Zitat:

Zitat von mr_sol (Beitrag 138564)

Lösungsansatz 1) Zusammenfassen von Elemente zu Gruppen


Ergebnis:
vcard[1,8]
n[2,3]
geo[4,5],latitude[4,5],longitude[4,5]
adr[6,7]


geo hat die Eigenschaft die niedrigste id in der Gruppe zu besitzen

Die Lösung mit geo[4,5],latitude[4,5],longitude[4,5] mag zwar auf den ersten Blick einfacher sein, aber mit welcher Eigenschaft soll die Sortierung definiert werden. GROUP_CONCAT() oder so kann dann nur nach dem "tag" - Namen sortieren.

Zitat:

Zitat von mr_sol (Beitrag 138564)
Lösungsansatz 2) Zweite Tabelle anlegen

Eine Tabelle für Grobstruktur, eine für Unterelemente



Im Sinne des Nested Set "alles aus eine Struktur" finde ich die erste Lösung besser. Was sagt Du?

Durch Unterelemente ist das vielleicht nicht mehr eine Struktur, aber im Prinzip ist das Ganze mit den Koodinaten eigentlich kein "Nested Set", sondern eine "Zusammenbauanleitung". Bei Nested Set sind Abhängigkeiten (vater = 1, kind_von_vater = 1, etc.) über Spaltenwerte definiert. Das liegt hier so nicht vor. Deshalb wäre eine Untertabelle aus meiner Sicht auch okay und besser pflegbar.

Grüße
Thomas

mr_sol 19.10.2010 14:22:12

AW: NESTED SETS in XML - Lösung 1a
 
Lösung für Ansatz 1
Code:

SET @a=0;
  SELECT CASE flag
              WHEN 1 THEN
              IF(@a=t.lft,
                    CONCAT( '<' , t.tag, '>', t.`desc`,'<' , t.tag, '>'),
                    CONCAT( '<' , t.tag, '>', t.`desc`))
              WHEN 2 THEN
                                CONCAT( '</' , t.tag, '>')
              END AS xml_teil,
              @a:=t.lft
  FROM  ( SELECT        tag, 1 AS flag, lft AS pt, id
              FROM        tree
              UNION ALL
              SELECT        tag, 2 AS flag, rgt AS pt, id
              FROM        tree
              GROUP BY  pt
              ORDER BY        pt,id) r
    JOIN  tree t
      ON          t.id = r.id
ORDER          BY r.pt;


Erklärung:
Erste Unionmenge bleiben alle Elemente nach lft - Zweite Menge werden alle mit gleichen rgt "groupiert" und nach id sortiert.
Da die Unterelemente alle recht von <> stehen sollten. Mit @a stellt man fest ob es ein <> sonst kommt <>xx</>
Frage: wie würdest du die Tabelle den Indizieren?


Eine andere Möglichkeit währe noch eine weitere UNION Menge (Menge aller Unterelemente) hinzuzufügen und WENN zu erweitern.
Leider bin ich nicht dahinter gekommen wie ich diese Menge erzeugen soll -
-Eine Idee?

An Lösungsansatz 2 (2 tabellen) arbeite ich gerade. Ich würde gerne beide Auflösen um die Geschwindigkeit zu Testen.


Hier zu Testzwecken für Lösung 1
Code:

CREATE TABLE IF NOT EXISTS `tree` (
  `id` int(11) NOT NULL,
  `tag` varchar(100) NOT NULL,
  `lft` int(11) NOT NULL,
  `rgt` int(11) NOT NULL,
  `desc` text NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

--
-- Daten für Tabelle `tree`
--

INSERT INTO `tree` (`id`, `tag`, `lft`, `rgt`, `desc`) VALUES
(1, 'A', 1, 16, 'content A'),
(2, 'B', 2, 3, 'content B'),
(3, 'C', 4, 13, 'content C'),
(4, 'D', 5, 12, 'content D'),
(5, 'E', 6, 7, 'content E'),
(6, 'F', 8, 11, 'content F'),
(7, 'G', 9, 10, 'content G'),
(8, 'H', 14, 15, 'content H'),
(9, 'G2', 9, 10, 'content G1'),
(10, 'G3', 9, 10, 'content G2'),
(11, 'C1', 4, 13, 'content C1');



grüsse Helmut - Du hast mir wirklich geholfen, oft genügt ja schon ein Hinweis und es kommt ins Rollen:

thomas_w 19.10.2010 18:24:38

AW: NESTED SETS in XML - Gruppierung von Elementen
 
Also Deine Lösung #1 sieht ja eigentlich ganz aus.

Für die Lösung #2 habe ich an folgende Tabellen gedacht, aber so richtig einfach wird der SQL nicht. Auf die schnelle habe ich keine Lösung gefunden.

Also ..

Code:

CREATE TABLE IF NOT EXISTS `tree` (
  `id` int(11) NOT NULL,
  `tag` varchar(100) NOT NULL,
  `lft` int(11) NOT NULL,
  `rgt` int(11) NOT NULL,
  `desc` text NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

--
-- Daten für Tabelle `tree`
--

INSERT INTO `tree` (`id`, `tag`, `lft`, `rgt`, `desc`) VALUES
(1, 'A', 1, 16, 'content A'),
(2, 'B', 2, 3, 'content B'),
(3, 'C', 4, 13, 'content C'),
(4, 'D', 5, 12, 'content D'),
(5, 'E', 6, 7, 'content E'),
(6, 'F', 8, 11, 'content F'),
(7, 'G', 9, 10, 'content G'),
(8, 'H', 14, 15, 'content H'),
(11, 'C1', 4, 13, 'content C1');


-- Sub Elemente - Sub-Elemente für G

CREATE TABLE IF NOT EXISTS `tree_sub` (
  id INT NOT NULL,
  id_tree INT NOT NULL,
  tag varchar(100) NOT NULL,
  lft INT NOT NULL,
  rgt INT NOT NULL,
  description text NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;


INSERT INTO tree_sub (id, id_tree, tag, lft, rgt, description) VALUES
(1, 7, 'G2', 9, 10, 'content G1'),
(2, 7, 'G3', 9, 10, 'content G2');

Grüße
Thomas

thomas_w 19.10.2010 18:48:16

AW: NESTED SETS in XML - Gruppierung von Elementen
 
Na vielleicht hat ich doch eine Idee für die Lösung #2

Code:

SELECT CASE r.flag
        WHEN 1 THEN CONCAT( '<' , t.tag, '>', t.`desc` )
        WHEN 2 THEN CONCAT( '</' , t.tag, '>')
        WHEN 3 THEN CONCAT( '<' , ts.tag, '>', ts.description, '</' , ts.tag, '>')
        END AS xml_teil
  FROM (SELECT tag, 1 AS flag, lft AS pt, id
          FROM tree
       
        UNION ALL
       
        SELECT tag, 2 AS flag, rgt AS pt, id
          FROM tree
         
        UNION ALL
       
        SELECT ts.tag, 3 AS flag, ts.lft AS pt, ts.id
          FROM tree t
          JOIN tree_sub ts
            ON ts.id_tree = t.id
   
        ORDER BY pt, tag ) r
  LEFT JOIN tree t
    ON t.id = r.id
  LEFT JOIN tree_sub ts
    ON ts.id = r.id   
ORDER BY r.pt, r.tag;

+---------------------+
| xml_teil            |
+---------------------+
| <A>content A        |
| <B>content B        |
| </B>                |
| <C>content C        |
| <C1>content C1      |
| <D>content D        |
| <E>content E        |
| </E>                |
| <F>content F        |
| <G>content G        |
| <G2>content G1</G2> |
| <G3>content G2</G3> |
| </G>                |
| </F>                |
| </D>                |
| </C>                |
| </C1>              |
| <H>content H        |
| </H>                |
| </A>                |
+---------------------+
20 rows in set (0.01 sec)

mysql>

Aber so richtig flexibel ist das Ganze immer noch nicht. Vermutlich kommt man bei genauerem Hinsehen (oder bei entsprechenden Anforderungen) nicht um eine echte "Vater/Kind" Lösung. Also um rekursives SQL drum herum.

Grüße
Thomas

mr_sol 19.10.2010 20:01:52

AW: NESTED SETS in XML - Gruppierung von Elementen
 
Tabelle für Lösung 2

Deinen Ansatz finde ich gut, hätte ich mir aber eine einfache Tabellestruktur gedacht

Code:

CREATE TABLE IF NOT EXISTS `tree` (
  `id` int(11) NOT NULL,
  `tag` varchar(100) NOT NULL,
  `lft` int(11) NOT NULL,
  `rgt` int(11) NOT NULL,
  `desc` text NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

--
-- Daten für Tabelle `tree`
--

INSERT INTO `tree` (`id`, `tag`, `lft`, `rgt`, `desc`) VALUES
(1, 'A', 1, 16, 'content A'),
(2, 'B', 2, 3, 'content B'),
(3, 'C', 4, 13, 'content C'),
(4, 'D', 5, 12, 'content D'),
(5, 'E', 6, 7, 'content E'),
(6, 'F', 8, 11, 'content F'),
(7, 'G', 9, 10, 'content G'),
(8, 'H', 14, 15, 'content H');


-- Sub Elemente - Sub-Elemente für G

CREATE TABLE IF NOT EXISTS `tree_sub` (
  id INT NOT NULL,
  tag varchar(100) NOT NULL,
  description text NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;


INSERT INTO tree_sub (id, tag, description) VALUES
(7,'G2','content G1'),
(7,'G3','content G2'),
(3,'G2','content C1');


id_sub mit id verknüpfen


Funkt nicht aber aber meine Überlegung ging etwa in diese Richtung
Code:

SELECT CASE r.flag
        WHEN 1 THEN CONCAT( '<' , t.tag, '>', t.`desc`,GROUP_CONCAT('<',ts.tag,'>',ts.description,'<',ts.tag,'/>') )
        WHEN 2 THEN CONCAT( '</' , t.tag, '>')
        END AS xml_teil
  FROM  ( SELECT        tag, 1 AS flag, lft AS pt, id
              FROM        tree
              UNION ALL
              SELECT        tag, 2 AS flag, rgt AS pt, id
              FROM        tree
              ORDER BY pt) r
  LEFT JOIN tree t
    ON t.id = r.id
  LEFT JOIN tree_sub ts
    ON t.id = ts.id
ORDER BY r.pt;






Vielleicht noch zu Lösung 1 (ohne Zusatztabelle)

Gibts bei UNION auch die Möglichkeit einen Teil von zB Menge A in C auszuschliessen. C währe die Menge aller Unterelemente
zB:
Code:

SELECT A.tag, 1 AS flag, A.lft AS pt, A.id
  FROM        tree AS A
  GROUP BY  pt
 UNION ALL
SELECT B.tag, 2 AS flag, B.rgt AS pt, B.id
  FROM tree AS B
  GROUP BY  pt
 UNION ALL
SELECT C.tag, 3 AS flag, C.rgt AS pt, C.id
  FROM tree AS C
  WHERE A.id!=C.id


thomas_w 19.10.2010 20:29:39

AW: NESTED SETS in XML - Gruppierung von Elementen
 
Zitat:

CREATE TABLE IF NOT EXISTS `tree_sub` (
id INT NOT NULL,
tag varchar(100) NOT NULL,
description text NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;


INSERT INTO tree_sub (id, tag, description) VALUES
(7,'G2','content G1'),
(7,'G3','content G2'),
(3,'G2','content C1');

Dieser Tabelle fehlt, dass eine einzelne Zeile nicht gezielt angesprochen werden kann. Das führt früher oder später zu echten Problemem. D.h. die
Spalte id_tree ist meiner Meinung nach notwendig.


Der GROUP_CONCAT() funktioniert nur, wenn ein GROUP BY entsprechend vorhanden ist.

Grüße
Thomas

mr_sol 20.10.2010 06:43:42

AW: NESTED SETS in XML - Gruppierung von Elementen
 
Du hast natürlich Recht die zweite Tabelle mit einer id_tree auszustatten und GROUP_CONCAT mit group by.

Habe die Lösungen überarbeitet:




Ansatz zu Lösung 1b (eine Tabellen)

Meine Überlegung für Menge aller Unterelemente - bei UNION zu ergänzen
a Oberelemente, b Alle Elemente durch Ausschliessen der Schnittmenge bleiben Unterelemente

klappt aber nicht

Code:

SELECT tag, 3 AS flag, lft AS pt, id
FROM tree a
RIGHT JOIN tree b ON a.id=b.id
WHERE b.id IS NULL
GROUP BY a.lft

Guter Link für JOIN: http://www.codeproject.com/KB/databa...JOINS_orig.jpg




Lösungansatz 2 (zwei Tabellen)

Habe nochmals die Tabellen überarbeitet - Reiner Purismus, dieser Lösungsansatz - Tabellen:
tree...nur noch die Baumstruktur ohne Inhalt
element...alle Elemente und Content wobei der erste in der Reihenfolge immer das Oberelement ist

Test-Content
Code:

CREATE TABLE IF NOT EXISTS `tree` (
  `id` int(11) NOT NULL,
  `lft` int(11) NOT NULL,
  `rgt` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

--
-- Daten für Tabelle `tree`
--

INSERT INTO `tree` (`id`, `lft`, `rgt`) VALUES
(1, 1, 16),
(2, 2, 3),
(3, 4, 13),
(4, 5, 12),
(5, 6, 7),
(6, 8, 11),
(7, 9, 10),
(8, 14, 15);



CREATE TABLE IF NOT EXISTS `element` (
  `id` int(11) NOT NULL,
  `tag` varchar(100) NOT NULL,
  `desc` text NOT NULL,
  `id_tree` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

--
-- Daten für Tabelle `element`
--

INSERT INTO `element` (`id`, `tag`, `desc`, `id_tree`) VALUES
(1, 'A', 'content A', 1),
(2, 'B', 'content B', 2),
(3, 'C', 'content C', 3),
(4, 'D', 'content D', 4),
(5, 'E', 'content E', 5),
(6, 'F', 'content F', 6),
(7, 'G', 'content G', 7),
(8, 'H', 'content H', 8),
(9, 'G2', 'content G1', 7),
(10, 'G3', 'content G2', 7),
(11, 'C1', 'content C1', 3);



Fast die Lösung - Fehler liegt bei group_concat, liefert das Oberelement nochmals - EINE IDEE?
Code:

  SELECT CASE flag
  WHEN 1 THEN
                          CONCAT( '<' , e.tag, '>',e.desc,GROUP_CONCAT('<' , e.tag, '>',e.desc, '</' , e.tag, '>'))
  WHEN 2 THEN
                          CONCAT( '<' , e.tag, '>',e.desc, '</' , e.tag, '>')
  END AS xml_teil
  FROM  (
              SELECT 1 AS flag, lft AS pt, id
              FROM        tree
              UNION ALL
              SELECT 2 AS flag, rgt AS pt, id
              FROM        tree
              ORDER BY        pt,id) r
    LEFT JOIN  tree t
      ON          t.id = r.id
    LEFT JOIN element e
    ON e.id_tree = t.id
GROUP BY r.pt
ORDER BY r.pt;




grüsse helmut

thomas_w 20.10.2010 10:05:47

AW: NESTED SETS in XML - Gruppierung von Elementen
 
Hallo Helmut,

ich denke, die Lösung 1 ist eine Sackgasse.

Zur Lösung 2 folgendes:

Nachdem die Tabellenstruktur komplett umgestellt ist, muss natürlich auch der SQL neu überdacht werden.

Mein Vorschlag wäre somit:

Code:

CREATE TABLE IF NOT EXISTS `tree` (
  `id` int(11) NOT NULL,
  `lft` int(11) NOT NULL,
  `rgt` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

--
-- Daten für Tabelle `tree`
--

INSERT INTO `tree` (`id`, `lft`, `rgt`) VALUES
(1, 1, 16),
(2, 2, 3),
(3, 4, 13),
(4, 5, 12),
(5, 6, 7),
(6, 8, 11),
(7, 9, 10),
(8, 14, 15);



CREATE TABLE IF NOT EXISTS `element` (
  `id` int(11) NOT NULL,
  `tag` varchar(100) NOT NULL,
  `desc` text NOT NULL,
  `id_tree` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

--
-- Daten für Tabelle `element`
--

INSERT INTO `element` (`id`, `tag`, `desc`, `id_tree`) VALUES
(1, 'A', 'content A', 1),
(2, 'B', 'content B', 2),
(3, 'C', 'content C', 3),
(4, 'D', 'content D', 4),
(5, 'E', 'content E', 5),
(6, 'F', 'content F', 6),
(7, 'G', 'content G', 7),
(8, 'H', 'content H', 8),
(9, 'G2', 'content G1', 7),
(10, 'G3', 'content G2', 7),
(11, 'C1', 'content C1', 3);


SELECT CASE r.flag
        WHEN 1 THEN CONCAT( '<' , r.tag, '>', r.`desc` )
        WHEN 2 THEN CONCAT( '</' , r.tag, '>')
        WHEN 3 THEN CONCAT( '<' , r.tag, '>', r.`desc`, '</' , r.tag, '>')
        END AS xml_teil
  FROM (SELECT e.tag, 1 AS flag, t.lft AS pt, e.`desc`
          FROM tree t
          JOIN element e
            ON e.id_tree = t.id
          WHERE  1 = ( SELECT COUNT(*)
                        FROM element e2
                        WHERE e2.id_tree = e.id_tree
                    )           
           
        UNION ALL
       
        SELECT e.tag, 2 AS flag, t.rgt AS pt, e.`desc`
          FROM tree t
          JOIN element e
            ON e.id_tree = t.id
          WHERE  1 = ( SELECT COUNT(*)
                        FROM element e2
                        WHERE e2.id_tree = e.id_tree
                    )                         

        UNION ALL
       
        SELECT e.tag, 3 AS flag, t.rgt AS pt, e.`desc`
          FROM tree t
          JOIN element e
            ON e.id_tree = t.id
          WHERE  1 < ( SELECT COUNT(*)
                        FROM element e2
                        WHERE e2.id_tree = e.id_tree
                    )
   
        ) r

ORDER BY r.pt, r.tag;

+---------------------+
| xml_teil            |
+---------------------+
| <A>content A        |
| <B>content B        |
| </B>                |
| <D>content D        |
| <E>content E        |
| </E>                |
| <F>content F        |
| <G>content G</G>    |
| <G2>content G1</G2> |
| <G3>content G2</G3> |
| </F>                |
| </D>                |
| <C>content C</C>    |
| <C1>content C1</C1> |
| <H>content H        |
| </H>                |
| </A>                |
+---------------------+
17 rows in set (0.00 sec)

mysql>


Grüße
Thomas

mr_sol 20.10.2010 11:19:35

AW: NESTED SETS in XML - Gruppierung von Elementen
 
Du hast recht - lassen wir Lösung 1 weg, wir haben ja eine gut.

Bei der letzten Query hats einen Bug

C,C1 an falscher Position
G zu früh geschlossen

Bin noch nicht dahinter gekommen.


Alle Zeitangaben in WEZ +2. Es ist jetzt 16:45:23 Uhr.

Powered by vBulletin® Version 3.8.3 (Deutsch)
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.