2013-04-09 9 views
11

DBMS, z którym pracuję to MySQL, środowiskiem programistycznym jest Delphi 7 (co nie ma znaczenia dla tego przykładu).SQL i Delphi: mechanizm rekursywny do tworzenia drzewa z tabeli

Mam tabelę o nazwie "temat", w której przechowuję wszystkie książki w systemie. Przedmioty mogą mieć relacje rodzic-dziecko, podobnie jak nauka może być podzielona, ​​powiedzmy, na matematykę i fizykę, podczas gdy matematyka może być podzielona na rachunek różniczkowy, algebrę, geometrię i dalej.

Chciałbym utworzyć drzewo z datą z tego stołu. Proszę, pomóż mi to zrobić. Nawet nie ma znaczenia, jakiego języka używa się do celów ilustracyjnych, po prostu może to być pseudokod.

Schemat bazy danych dla tabeli Temat wygląda następująco:

enter image description here

definicji tabeli Temat:

DROP TABLE IF EXISTS subject; 
CREATE TABLE IF NOT EXISTS subject (     # Comment 
    subject_id INT UNSIGNED NOT NULL AUTO_INCREMENT, # Subject ID 
    subject  VARCHAR(25) NOT NULL,    # Subject name 
    parent_id INT UNSIGNED  NULL DEFAULT NULL, # Parent ID as seen from 
    PRIMARY KEY (subject_id),       # the diagram refers to 
    UNIQUE (subject),         # the subject_id field 
    INDEX (parent_id), 
    CONSTRAINT fk_subject_parent 
    FOREIGN KEY (parent_id) 
     REFERENCES subject (subject_id) 
      ON DELETE RESTRICT 
      ON UPDATE CASCADE 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

Zapełnianie tabeli tematów z jakiegoś manekina danych:

INSERT INTO subject (subject, parent_id) VALUES 
        ('Science', NULL), 
        ('Mathematics', 1), 
        ('Calculus',  2), 
        ('Algebra',  2), 
        ('Geometry',  2), 
        ('Languages', NULL), 
        ('English',  6), 
        ('Latin',   6); 

Instrukcja SELECT zwraca to:

SELECT * FROM subject; 
╔════════════╦═════════════╦═══════════╗ 
║ subject_id ║ subject ║ parent_id ║ 
╠════════════╬═════════════╬═══════════╣ 
║   1 ║ Science  ║  NULL ║ 
║   2 ║ Mathematics ║   1 ║ 
║   3 ║ Calculus ║   2 ║ 
║   4 ║ Algebra  ║   2 ║ 
║   5 ║ Geometry ║   2 ║ 
║   6 ║ Languages ║  NULL ║ 
║   7 ║ English  ║   6 ║ 
║   8 ║ Latin  ║   6 ║ 
╚════════════╩═════════════╩═══════════╝ 

Procedury składowane:

DELIMITER$$ 

DROP PROCEDURE IF EXISTS get_parent_subject_list; 
CREATE PROCEDURE get_parent_subject_list() 
BEGIN 
    SELECT subject_id, subject 
    FROM subject 
    WHERE parent_id IS NULL 
    ORDER BY subject ASC; 
END$$ 


DROP PROCEDURE IF EXISTS get_child_subject_list; 
CREATE PROCEDURE get_child_subject_list (IN parentID INT) 
BEGIN 
    SELECT subject_id, subject 
    FROM subject 
    WHERE parent_id = parentID 
    ORDER BY subject ASC; 
END$$ 

DELIMITER ; 

Następny jest mój procedura Delphi, który próbuje zapełnić widok drzewa z danymi, ale jak widać dalej, że nie może dostać się głębiej niż drugi poziom :

procedure TForm1.CreateSubjectTreeView(Sender: TObject); 
var 
    i : integer; 
begin 
    i := 0; 

    q1.SQL.Clear; 
    q1.SQL.Add('CALL get_parent_subject_list()'); 
    q1.Open; 
    q1.First; 

    while not q1.EOF do 
    begin 
     TreeView.Items.Add(nil, q1.Fields[1].Value); 

     q2.SQL.Clear; 
     q2.SQL.Add('CALL get_child_subject_list(' + 
        VarToStr(q1.Fields[0].Value) + ')'); 
     q2.Open; 
     q2.First; 

     while not q2.EOF do 
     begin 
      TreeView.Items.AddChild(TreeView.Items.Item[i], q2.Fields[1].Value); 
      q2.Next; 
     end; 

     i := TreeView.Items.Count; 
     q1.Next; 
    end; 
end; 

to właśnie ten fragment kodu:

+- Science 
| | 
| +- Mathematics 
| 
+- Languages 
    | 
    +- English 
    +- Latin 

Ale chciałbym to wyglądać tak:

+- Science 
| | 
| +- Mathematics 
|  | 
|  +- Calculus 
|  +- Algebra 
|  +- Geometry 
| 
+- Languages 
    | 
    +- English 
    +- Latin 
+5

+1 za dobrze uformowanej pytanie –

+1

nie mogę pomóc w rozwiązaniu mysql, ale to, co szukasz jest hierarchicznych zapytań, a przykładem może można znaleźć tutaj: http://explainextended.com/2009/03/17/hierarchical-queries-in-mysql/ mysql nie ma klauzuli connect by, więc musiałbyś zrobić to ręcznie. –

Odpowiedz

4

Proponuję nie załadować całe drzewo na raz, dlaczego ty? nikt nie może oglądać w tej chwili tysiąca przedmiotów. Może to potrwać długo, a twój program będzie wyglądał na zamrożony. I sprawia, że ​​olbrzymie szczupaki ładują sieć i serwer.

Lepiej użyj podejścia VirtualTreeView, gdzie każda pozycja ładuje swoje elementy podrzędne na żądanie. Wymagałoby to jeden parametryzowane przygotowane zapytanie jak

Select ID, Title, This, That from TREE where Parent_ID = :ID 

I tak, nie rób nowego SQL teksów t dla każdego przedmiotu.Jest to zarówno niebezpieczne, jak i powolne (musisz upuścić wszystkie dane zebrane dla starego żądania i przeanalizować nowe).

Powinieneś wykonać jedno sparametryzowane zapytanie, Prepare to i po prostu zrób/zmień wartości parametrów/otwórz.

Zobacz przyczyny i próbkę Delphi w http://bobby-tables.com/


jeden przykład „załaduj wszystko na raz zupełnie” pęd jest dynamically create popup menu tree from sql server table in Delphi - choć nie sądzę, pośpiech jest dobre podejście do bardziej lub mniej dużych drzew .

Uwaga o tym podejściu: wypełnić korzeniowych elementów, a następnie znaleźć taki czy inny sposób do wypełnienia w elementach, jeszcze nie wypełnione, ale już określany przez innych dopóki nie ma takich elementów w końcu.

Oczywiście można to zrobić rekurencyjnie, przechodząc drzewo do jego końców - ale wymagałoby to wielu zagnieżdżonych zapytań do bazy danych.

Możesz utworzyć rekurencyjne zapytanie SQL, ale prawdopodobnie będzie to zależne od serwera, a silniki RDBMS na ogół ograniczają głębokość rekurencji.

Podejście może nieco gorzej na drzewie, ale bardziej czyste i łatwiejsze w RDBMS byłoby zrobić dedykowany TQueue z właśnie dodane elementy drzewa. Po załadowaniu jakiegoś elementu - początkowo wszystkich elementów root - zapamiętasz go w kolejce. Następnie usuwasz jeden z drugiego z kolejki i wypełniasz (ładuj i upuść) jego potomka. Dopóki kolejka nie zostanie opróżniona.

1

Lubię używać tabeli mieszania, aby utworzyć indeks wszystkich węzłów indeksowanych przez keyID i użyć tego do zbudowania drzewa. Wymaga 2 przebiegów stołu. Pierwsze przejście tworzy węzeł drzewa głównego dla każdego rekordu i dodaje wpis hash identyfikatora keyID względem węzła drzewa. drugi pas przechodzi przez stół, przeglądając parentID w haszyszu. Jeśli go znajdzie, przesunie bieżący węzeł pod węzeł nadrzędny, w przeciwnym razie go zignoruje. Pod koniec drugiego przejścia masz kompletne drzewo.

var i,imax,ikey,iParent : integer; 
     aNode,aParentNode : TTreeNode; 
     aData : TMyData; 
     aContainer : TSparseObjectArray; // cDataStructs , delphi fundamentals 
     aNodeIndex : TSparseObjectArray; // delphi 7 
    begin 
     try 
     aContainer := TSparseObjectArray.Create(true); 
     aNodeIndex := TSparseObjectArray.Create(False); 
     imax := 10000; 
     // create test data; 
     for i := 1 to imax do 
     begin 
      aData := TMyData.Create; 
      aData.iKey := i; 
      aData.iParent := Random(imax); // random parent 
      aData.Data := 'I:' + IntToStr(aData.iKey); 
      aContainer.Item[i] := aData; 
     end; 

     tv1.Items.Clear; 
     tv1.Items.BeginUpdate; 
     // build tree 
     // First Pass - build root tree nodes and create cross ref. index 
     for i := 1 to imax do 
     begin 
      aData := TMYData(aContainer.Item[i]); 
      aNode := tv1.Items.AddChild(nil,aData.Data); 
      aNodeIndex.Item[aData.iKey] := aNode; 
     end; 
     // Second Pass - find parent node using index and move node 
     for i := 1 to imax do 
     begin 
      aData := TMYData(aContainer.Item[i]); 
      aNode := TTreeNode(aNodeIndex.Item[aData.iKey]); 
      if aNodeIndex.HasItem(aData.iparent) 
      then begin 
       aParentNode := TTreeNode(aNodeIndex.Item[aData.iparent]); 
       aNode.MoveTo(aParentNode,naAddChild); 
       end; 
     end; 
     tv1.Items.EndUpdate; 
     tv1.Select(tv1.Items.GetFirstNode); 
     finally 
     aContainer.Free; 
     aNodeIndex.free; 
     end; 
    end; 
+0

jak szybkie jest "poruszanie się" we wbudowanym drzewie sterowania Windows? –

+0

Funkcja MoveTo jest wolna, gdy węzły drzewa rozciągają się poza obszar okna ze względu na sterowanie, które chce przewinąć do bieżącego węzła. Zwykle używam tej metody budowania drzew danych w niestandardowej bazie danych. Ekspozycja na rzeczywiste treeView jest wykonywane w mniejszych zestawach z rutynową budową o nazwie getTreeBranch, która ma już zbudowaną strukturę drzewiastą, ale po prostu przechodzi do bieżącej gałęzi dla wszystkich elementów. – Robert

+0

Zgadzam się, że w przypadku niektórych niestandardowych struktur danych drzewiastych podejście może być z łatwością najszybsze. Ale TS chce używać TTreeView w magazynie, a im mniej go dotkniesz, tym lepiej działa;) –

0

procedure TdfmMed.Button1Click(Sender: TObject); 
 
var 
 
    NodePai : TTreeNode; 
 
     procedure MontaFilho(Node : TTreeNode; Cod : integer); 
 
     var 
 
      qry : TFDQuery; 
 
      node1 : TTreeNode; 
 
     begin 
 
      qry := TFDQuery.Create(nil); 
 
      qry.Connection := dm1.FDConnection1; 
 
      qry.close; 
 
      qry.SQL.Add('SELECT cod, nome_grupo FROM teste WHERE parent_cod = :cod ORDER BY nome_grupo ASC'); 
 
      qry.ParamByName('cod').AsInteger := cod; 
 
      qry.Open(); 
 
      qry.First; 
 
      while not qry.EOF do 
 
      begin 
 
       node1 := TreeView1.Items.AddChild(NODE, qry.Fields[1].Value); 
 
       MontaFilho(node1, qry.Fields[0].Value); 
 

 
       qry.Next; 
 
      end; 
 
     end; 
 
begin 
 
    TreeView1.Items.Clear; 
 

 
    qryGrupoPai.close; qryGrupoPai.Open; 
 

 
    qryGrupoPai.First; 
 
    while not qryGrupoPai.EOF do 
 
    begin 
 
     NodePai := TreeView1.Items.Add(nil, qryGrupoPai.Fields[1].Value); 
 
     MontaFilho(NodePai, qryGrupoPai.Fields[0].Value); 
 

 
     qryGrupoPai.Next; 
 
    end; 
 
end;

+1

Pewne wyjaśnienie dobrze byłoby dołączyć do zrzutu kodu i wyjaśnić, dlaczego rozwiązuje problem PO. –

Powiązane problemy