2011-05-17 16 views
10

Birkaç SQL hiyerarşisi eğitimine göz attım, ancak hiçbiri başvurum için çok anlamlı değildi. Belki de onları doğru bir şekilde anlamadım. Bir C# ASP.NET uygulaması yazıyorum ve SQL verilerinden bir ağaç görünümü hiyerarşisi oluşturmak istiyorum.SQL Veri Hiyerarşisi

Bu hiyerarşi çalışma şekli şöyledir: Kimliği ve Yer Kimliği aynıdır

 
SQL TABLE 

ID  | Location ID | Name 
_______| __________ |_____________ 
1331 | 1331  | House 
1321 | 1331  | Room 
2141 | 1321  | Bed 
1251 | 2231  | Gym 

, bu üst Ebeveyn belirleyeceğini

. Bu ebeveynin herhangi bir çocuğu, ebeveyn ile aynı konum kimliğine sahip olacaktır. Bu çocuğun herhangi bir torununun, çocuğun kimliğine eşit bir yer kimliği olacaktır. Yukarıdaki örnek için

:

 
- House 
    -- Room 
     --- Bed 

öğreticiler takip edilmesi kolay herhangi bir yardım veya yön büyük takdir.

DÜZENLEME: Ben

Kod şimdiye kadar, ancak yalnızca Ebeveyn ve Çocuk torun yok olur. Tüm düğümleri tekrar tekrar elde etmek için nasıl elde edeceğimi anlayamıyorum. İşte

using System; 
using System.Data; 
using System.Collections.Generic; 
using System.Web; 
using System.Web.UI; 
using System.Web.UI.WebControls; 
using System.Configuration; 
using System.Data.SqlClient; 

namespace TreeViewProject 
{ 
public partial class _Default : System.Web.UI.Page 
{ 
    protected void Page_Load(object sender, EventArgs e) 
    { 
     PopulateTree(SampleTreeView); 

    } 



    public void PopulateTree(Control ctl) 
    { 

     // Data Connection 
     SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["AssetWhereConnectionString1"].ConnectionString); 
     connection.Open(); 

     // SQL Commands 
     string getLocations = "SELECT ID, LocationID, Name FROM dbo.Locations"; 
     SqlDataAdapter adapter = new SqlDataAdapter(getLocations, connection); 
     DataTable locations = new DataTable(); 
     // Fill Data Table with SQL Locations Table 
     adapter.Fill(locations); 
     // Setup a row index 
     DataRow[] myRows; 
     myRows = locations.Select(); 

     // Create an instance of the tree 
     TreeView t1 = new TreeView(); 
     // Assign the tree to the control 
     t1 = (TreeView)ctl; 
     // Clear any exisiting nodes 
     t1.Nodes.Clear(); 

     // BUILD THE TREE! 
     for (int p = 0; p < myRows.Length; p++) 
     { 
      // Get Parent Node 
      if ((Guid)myRows[p]["ID"] == (Guid)myRows[p]["LocationID"]) 
      { 
       // Create Parent Node 
       TreeNode parentNode = new TreeNode(); 
       parentNode.Text = (string)myRows[p]["Name"]; 
       t1.Nodes.Add(parentNode); 

       // Get Child Node 
       for (int c = 0; c < myRows.Length; c++) 
       { 
        if ((Guid)myRows[p]["LocationID"] == (Guid)myRows[c]["LocationID"] 
         && (Guid)myRows[p]["LocationID"] != (Guid)myRows[c]["ID"] /* Exclude Parent */) 
        { 
         // Create Child Node 
         TreeNode childNode = new TreeNode(); 
         childNode.Text = (string)myRows[c]["Name"]; 
         parentNode.ChildNodes.Add(childNode); 
        } 
       } 
      } 
     } 
     // ALL DONE BUILDING! 

     // Close the Data Connection 
     connection.Close(); 
    } 

} 
} 

gerçek SQL tablosundan bir snippit geçerli: Mekanlar

 
ID          LocationID        Name 
____________________________________ ____________________________________ ______________ 
DEAF3FFF-FD33-4ECF-910B-1B07DF192074 48700BC6-D422-4B26-B123-31A7CB704B97 Drop F 
48700BC6-D422-4B26-B123-31A7CB704B97 7EBDF61C-3425-46DB-A4D5-686E91FD0832 Olway 
06B49351-6D18-4595-8228-356253CF45FF 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 Drop E 5 
E98BC1F6-4BAE-4022-86A5-43BBEE2BA6CD DEAF3FFF-FD33-4ECF-910B-1B07DF192074 Drop F 6 
F6A2CF99-F708-4C61-8154-4C04A38ADDC6 7EBDF61C-3425-46DB-A4D5-686E91FD0832 Pree 
0EC89A67-D74A-4A3B-8E03-4E7AAAFEBE51 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 Drop E 4 
35540B7A-62F9-487F-B65B-4EA5F42AD88A 48700BC6-D422-4B26-B123-31A7CB704B97 Olway Breakdown 
5000AB9D-EB95-48E3-B5C0-547F5DA06FC6 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 Out 1 
53CDD540-19BC-4BC2-8612-5C0663B7FDA5 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 Drop E 3 
7EBDF61C-3425-46DB-A4D5-686E91FD0821 B46C7305-18B1-4499-9E1C-7B6FDE786CD6 TEST 1 
7EBDF61C-3425-46DB-A4D5-686E91FD0832 7EBDF61C-3425-46DB-A4D5-686E91FD0832 HMN 

teşekkürler.

+0

Bu isimler kafa karıştırıcı. 'Yer Kimliği' ni 'Parent_ID' olarak değiştirirdim. – FrustratedWithFormsDesigner

+2

Saf bir SQL cevabı kullanmakta olduğunuz SQL çeşidini bilmeniz gerekebilir. Hangi veritabanını belirtebilir misiniz? SQL Server 2008, belki? –

+0

Maalesef isimleri çok fazla değiştiremem onlara bağlı. SQL Server 2008 kullanıyorum. – Will

cevap

13

Genel tablo ifadesi veya kısaca CTE kullanarak yinelemeli bir sorgu arıyorsanız. SQL Server 2008'de bunun için ayrıntılı bir yazma işlemi found on MSDN olabilir. Genelde

, onlar benzer bir yapıya sahip aşağıdadır: Bu çalıştırır

WITH cte_name (column_name [,...n]) 
AS (
    –- Anchor 
    CTE_query_definition 

    UNION ALL 

    –- Recursive portion 
    CTE_query_definition 
) 
-- Statement using the CTE 
SELECT * FROM cte_name 

, SQL Server benzer bir şey yapacak aşağıdaki (MSDN'den daha basit bir dile aktarılan):

  1. CTE ifadesini çapa ve yinelemeli üyelere bölün.
  2. İlk sonuç kümesini oluşturarak bağlantıyı çalıştırın.
  3. Yinelemeli bölümü, giriş olarak önceki adımla çalıştırın.
  4. Boş bir set iade edilene kadar 3. adımı tekrarlayın.
  5. Sonuç kümesini döndür. Bu bir çapa TÜMÜ ve tüm özyinelemeli adımlar. Bu özel Örneğin

, böyle bir şey deneyin: Örneğin veri Verilen

With hierarchy (id, [location id], name, depth) 
As (
    -- selects the "root" level items. 
    Select ID, [LocationID], Name, 1 As depth 
    From dbo.Locations 
    Where ID = [LocationID] 

    Union All 

    -- selects the descendant items. 
    Select child.id, child.[LocationID], child.name, 
     parent.depth + 1 As depth 
    From dbo.Locations As child 
    Inner Join hierarchy As parent 
     On child.[LocationID] = parent.ID 
    Where child.ID != parent.[Location ID]) 
-- invokes the above expression. 
Select * 
From hierarchy 

, böyle bir şey almalısınız: "Gym" veremediğini

ID  | Location ID | Name | Depth 
_______| __________ |______ | _____ 
1331 | 1331  | House |  1 
1321 | 1331  | Room |  2 
2141 | 1321  | Bed |  3 

Not. Örnek verilerinize dayanarak, kimliği [Konum Kimliği] ile eşleşmiyor, dolayısıyla kök düzeyinde bir öğe olmayacak. Konum kimliği, 2231, geçerli üst kimlikler listesinde görünmüyor.


Düzenleme 1:

Bir C# veri yapısı içine bu alma hakkında sordunuz. C# içinde bir hiyerarşiyi temsil etmenin birçok farklı yolu vardır. İşte sadeliği için seçilen bir örnek. Gerçek bir kod örneği şüphesiz daha kapsamlı olacaktır.

İlk adım, hiyerarşideki her düğümün neye benzediğini tanımlamaktır. Düğümdeki her veri için özellikler içermenin yanı sıra, Parent ve Children özelliklerinin yanı sıra Add bir çocuğa ve Get bir çocuğa yöntemleri dahil ettik. Get yöntemi, düğümün yalnızca kendi alt öğelerini değil, yalnızca düğümün kendi alt öğelerini arar.

public class LocationNode { 
    public LocationNode Parent { get; set; } 
    public List<LocationNode> Children = new List<LocationNode>(); 

    public int ID { get; set; } 
    public int LocationID { get; set; } 
    public string Name { get; set; } 

    public void Add(LocationNode child) { 
     child.Parent = this; 
     this.Children.Add(child); 
    } 

    public LocationNode Get(int id) { 
     LocationNode result; 
     foreach (LocationNode child in this.Children) { 
      if (child.ID == id) { 
       return child; 
      } 
      result = child.Get(id); 
      if (result != null) { 
       return result; 
      } 
     } 
     return null; 
    } 
} 

Şimdi ağacınızı yerleştirmek isteyeceksiniz. Burada bir probleminiz var: Yanlış sırada bir ağacı yerleştirmek zor. Bir alt düğüm eklemeden önce, ana düğüm için bir referansa ihtiyacınız vardır. Bunu yapmak için varsa, iki geçiş yaparak sorunu azaltabilirsiniz (biri tüm düğümleri oluşturmak için, sonra ağacı oluşturmak için başka bir tane). Ancak, bu durumda, bu gereksizdir.

Yukarıda sağladığım SQL sorgusunu ve depth sütununda sırayla alırsanız, üst düğümüyle karşılaşmadan önce hiçbir zaman bir çocuk düğümüyle karşılaşmayacağınızdan emin olabilirsiniz. Bu nedenle, bunu tek geçişte yapabilirsiniz.

Ağacınızın "kökü" olarak hizmet etmek için bir düğüme ihtiyacınız olacaktır. Bunun "Ev" (örneğinizden) olup olmayacağına veya sadece bu amaç için oluşturduğunuz kurgusal bir yer tutucu düğümü olup olmadığına karar verirsiniz. Sonra öneririm.

Kod için! Yine, bu basitlik ve okunabilirlik için optimize edilmiştir. Üretim kodunda ele almak isteyebileceğiniz bazı performans sorunları vardır (örneğin, "ana" düğümü sürekli olarak bakmak gerçekten gerekli değildir). Bu optimizasyonlardan kaçındım çünkü karmaşıklığı arttırıyorlar.

( ) root LocationNode artık tüm hiyerarşinizi içerir. Bu arada, bu kodu gerçekten uygulamamışım, bu yüzden göze çarpan sorunları fark ederseniz lütfen bana bildirin.


Düzenleme 2

, örnek kod düzeltmek Bu değişiklikleri yapmak için:

bu satırı kaldırın:

// Create an instance of the tree 
TreeView t1 = new TreeView(); 

Bu hat aslında bir sorun değil ama bunu kaldırılmalı. Buradaki yorumlarınız yanlış; Gerçekten kontrol için bir ağaç atamadınız. Bunun yerine, t1'a atayarak yeni bir TreeView yaratıyorsunuz, ardından hemen farklı bir nesneyi t1'a ataıyorsunuz. Oluşturduğunuz TreeView, bir sonraki satır yürütüldüğü anda kaybolur.

// SQL Commands 
string getLocations = "SELECT ID, LocationID, Name FROM dbo.Locations"; 

Ben ORDER BY ile, daha önce önerilen SQL deyimi ile bu SQL deyimi yerine SQL deyimi düzelt. "Derinlik" in neden önemli olduğunu açıklayan önceki düzenlememi okuyun: Belirli bir sırayla düğümleri gerçekten eklemek istersiniz. Üst düğüme sahip olana kadar bir alt düğüm ekleyemezsiniz.

İsteğe bağlı olarak, burada bir SqlDataAdapter ve DataTable eklentisine ihtiyacınız yoktur. Başlangıçta önerdiğim DataReader çözümü, kaynak açısından daha basit, daha kolay çalışmak ve daha verimli olmaktır.

Ayrıca, çoğu C# SQL nesnesi IDisposable uygular, böylece bunları doğru şekilde kullandığınızdan emin olmak istersiniz. Bir şey IDisposable uygularsa, using ifadelerine sardığınızdan emin olun (önceki C# kod örneğime bakın).

sizin ağaç bina döngü

Eğer ebeveynler için bir döngü ve çocuklar için bir iç döngü var çünkü Yalnızca üst ve alt düğümleri alıyoruz düzelt. Bildiğiniz gibi, torunları almıyorsunuz çünkü onları ekleyen bir kodunuz yok.

Torunları almak için içsel bir iç döngü ekleyebilirsiniz, ancak açıkça yardım istemeniz çünkü bunu yapmanın yalnızca deliliğe yol açacağını fark ettiniz. Daha sonra büyük torunları istiyorsan ne olur? İç-iç-iç döngü? Bu teknik geçerli değildir.

Muhtemelen özyineleme burada düşünmüşsünüzdür. Bu, onun için mükemmel bir yer ve ağaç gibi yapılarla uğraşıyorsanız, sonunda ortaya çıkacak. Sorunuzu düzenlediğinize göre, sorununun SQL ile ilgili bir sorunu varsa, çok azı vardır. Asıl sorunun özyineli. Birisi sonunda gelebilir ve bunun için tekrarlayıcı bir çözüm geliştirebilir. Bu kesinlikle geçerli ve muhtemelen tercih edilebilir bir yaklaşım olacaktır. Bununla birlikte, cevabım zaten özyinelemeli kısmı ele aldı - basitçe SQL katmanına taşıdı. Bu nedenle, önceki kodumu, bu soruya uygun genel bir cevap olduğunu düşündüğümden, etrafta tutacağım. Özel durumunuz için, birkaç değişiklik daha yapmalısınız.

İlk olarak, önerdiğim LocationNode sınıfına ihtiyacınız olmayacak. Bunun yerine TreeNode kullanıyorsunuz ve bu iyi çalışıyor. İkinci olarak, FindNode, FindNode düğümünün tam yolunu gerektirmesi dışında, yöntemine benzer bir yöntemdir,. FindNode'u kullanmak için, size bu bilgileri vermek için SQL'i değiştirmeniz gerekir.

Bu nedenle, tüm PopulateTree fonksiyonu aşağıdaki gibi görünmelidir:

public void PopulateTree(TreeView t1) { 

    // Clear any exisiting nodes 
    t1.Nodes.Clear(); 

    using (SqlConnection connection = new SqlConnection()) { 
     connection.ConnectionString = "((replace this string))"; 
     connection.Open(); 

     string getLocations = @" 
      With hierarchy (id, [location id], name, depth, [path]) 
      As (

       Select ID, [LocationID], Name, 1 As depth, 
        Cast(Null as varChar(max)) As [path] 
       From dbo.Locations 
       Where ID = [LocationID] 

       Union All 

       Select child.id, child.[LocationID], child.name, 
        parent.depth + 1 As depth, 
        IsNull(
         parent.[path] + '/' + Cast(parent.id As varChar(max)), 
         Cast(parent.id As varChar(max)) 
        ) As [path] 
       From dbo.Locations As child 
       Inner Join hierarchy As parent 
        On child.[LocationID] = parent.ID 
       Where child.ID != parent.[Location ID]) 

      Select * 
      From hierarchy 
      Order By [depth] Asc"; 

     using (SqlCommand cmd = new SqlCommand(getLocations, connection)) { 
      cmd.CommandType = CommandType.Text; 
      using (SqlDataReader rs = cmd.ExecuteReader()) { 
       while (rs.Read()) { 
        // I guess you actually have GUIDs here, huh? 
        int id = rs.GetInt32(0); 
        int locationID = rs.GetInt32(1); 
        TreeNode node = new TreeNode(); 
        node.Text = rs.GetString(2); 
        node.Value = id.ToString(); 

        if (id == locationID) { 
         t1.Nodes.Add(node); 
        } else { 
         t1.FindNode(rs.GetString(4)).ChildNodes.Add(node); 
        } 
       } 
      } 
     } 
    } 
} 

herhangi bir ek hataları bulmak bana bildirin lütfen!

+0

Mükemmel, aradığım şey bu. Teşekkür ederim! Yine de bir soru. Hangi çocuğun hangi ebeveyne ait olduğunu nasıl bilebilirim? Bu verinin ağaç görünümünü geliştirmek için ilişkiyi bilmem gerekecek. – Will

+0

Bu görevi ASP.NET C# kullanarak gerçekleştirmenin bir yolu var, bu yüzden ağacı sorgulayabildiğim aynı zamanda verileri sorgulayabiliyor muyum? – Will

+0

C# ağaç benzeri bir yapı oluşturmak için CTE sorgusunu kullanan bazı örnek C# kodu ekledim. Aradığınız bu mu? –

0

SQl 2008'de yeni özellik var. Bu, hierarchyid. Bu özellik hayatımı kolaylaştırıyor.

Yararlı olan var method for hierarchyid datatype, GetAncestor(), GetRoot() ... Bir kez hiyerarşi üzerinde çalışırken sorgu karmaşıklığını azaltacaktır.