2013-04-09 17 views
11

Il DBMS con cui sto lavorando è MySQL, l'ambiente di programmazione è Delphi 7 (che non è molto importante per questo esempio).SQL e Delphi: meccanismo ricorsivo per la creazione di un albero da una tabella

Ho una tabella denominata "oggetto" in cui memorizzo tutti gli oggetti del libro nel sistema. I soggetti possono avere una relazione genitore-figlio, come la scienza può essere divisa, diciamo, in matematica e fisica, mentre la matematica può essere suddivisa in calcolo, algebra, geometria e via.

Quello che mi piacerebbe è creare un albero popolato con la data da quella tabella. Per favore, aiutami a farlo. Non importa nemmeno quale linguaggio usi a scopo illustrativo, può semplicemente essere pseudocodice.

Il diagramma di database per la tabella soggetto guarda in questo modo:

enter image description here

La definizione Soggetto tavolo:

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; 

popolamento tavolo Soggetto con alcuni dati dummy:

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

L'istruzione SELECT restituisce questo:

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 ║ 
╚════════════╩═════════════╩═══════════╝ 

Le stored procedure:

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 ; 

successivo è la mia procedura di Delphi che tenta di popolare una vista ad albero con i dati, ma come si può vedere ulteriormente, non si può ottenere qualsiasi più profondo del secondo livello :

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; 

questo è ciò che questo frammento di codice fa:

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

Ma vorrei farlo sembrare come questo:

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

+1 per la domanda ben formato –

+1

io non posso aiutarti con la soluzione di mysql, ma quello che stai cercando è una query gerarchica, e un esempio può essere trovato qui: http://explainextended.com/2009/03/17/hierarchical-queries-in-mysql/ mysql non ha una clausola connect, quindi dovresti farlo manualmente. –

risposta

4

vi consiglio di non caricare l'intero albero in una sola volta, perché dovrebbe? nessun uomo può vedere al momento un migliaio di oggetti. E può essere lungo e il tuo programma sembrerebbe congelato. E fa un enorme carico di luccio su rete e server.

È meglio utilizzare l'approccio VirtualTreeView, in cui ogni articolo carica i propri elementi figli su richiesta. Ciò richiederebbe una parametrizzata query preparata come

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

E sì, non fare nuovi tex SQL t per ogni oggetto.È sia pericoloso che lento (è necessario eliminare tutti i dati raccolti per una vecchia richiesta e analizzare quello nuovo)

Si dovrebbe fare una query parametrizzata, Prepare e basta chiudere/modificare valori param/aprire.

Vedi ragioni e campione Delphi a http://bobby-tables.com/


Un esempio di "caricarlo tutto in una volta del tutto" corsa è a dynamically create popup menu tree from sql server table in Delphi - anche se non credo che corsa è buon approccio per gli alberi più o meno grandi .

nota su questo approccio: si compilare radice elementi, poi trovi un modo o nell'altro per riempimento di elementi, non ancora riempito, ma già fa riferimento da altri fino a quando non v'è nessuna di tali elementi, finalmente.

Ovviamente è possibile farlo in modo ricorsivo, attraversando l'albero fino ai suoi estremi, ma ciò richiederebbe molte query di database annidate.

È possibile effettuare una richiesta SQL ricorsiva, ma probabilmente i motori RDBMS sono molto dipendenti dal server e di solito impongono i propri limiti alla profondità della ricorsione.

Un approccio forse un po 'peggio il controllo della struttura, ma più pulito e più facile su RDBMS sarebbe quella di fare un apposito TQueue di elementi albero appena aggiunti. Dopo aver caricato alcuni elementi - inizialmente tutti quelli di root - lo ricordi in coda. Quindi rimuovi uno per l'altro dalla coda e compili (carica e accoda) i suoi figli. Fino a quando la coda si svuota.

1

Mi piace usare una tabella hash per creare un indice di tutti i nodi indicizzati da keyID e utilizzarlo per costruire l'albero. Richiede 2 passaggi del tavolo. Il primo passaggio crea un nodo dell'albero radice per ogni record e aggiunge una voce hash di keyID al nodo dell'albero. il secondo passaggio cammina sul tavolo guardando il genitore nell'hash. Se lo trova, quindi sposta il nodo corrente sotto il nodo genitore altrimenti lo ignora. Alla fine del secondo passaggio, hai costruito l'albero completo.

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

quanto è veloce il "movimento" nel controllo ad albero incorporato di Windows? –

+0

MoveTo è lento quando i nodi dell'albero si estendono oltre l'area della finestra a causa del controllo che desidera scorrere fino al nodo corrente. Di solito uso questo metodo per costruire alberi di dati all'interno di un database personalizzato. L'esposizione a un treeView reale viene eseguita in insiemi più piccoli con una routine che ho creato chiamata getTreeBranch che ha già la struttura ad albero costruita, ma cammina solo nel ramo corrente per tutti gli elementi. – Robert

+0

Concordo sul fatto che per alcuni dati personalizzati per la struttura dei dati collegati a puntatori il tuo approccio potrebbe essere il più veloce. Ma TS vuole usare TTreeView di serie e meno lo tocchi meglio funziona!) –

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

Alcune spiegazioni potrebbero essere utili per accompagnare il dump del codice e spiegare perché risolve il problema dell'OP. –

Problemi correlati