簡體   English   中英

SQL數據層次結構

[英]SQL Data Hierarchy

我已經查看了一些SQL層次結構教程,但它們對我的應用程序都沒有多大意義。 也許我只是沒有正確理解它們。 我正在編寫一個C#ASP.NET應用程序,我想從SQL數據創建一個樹視圖層次結構。

這是層次結構的工作方式:

SQL TABLE

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

如果ID和位置ID相同,這將決定頂級父級。 該父母的任何子女都將擁有與父母相同的位置ID。 該孩子的任何孫子女的位置ID都等於孩子的ID,依此類推。

對於上面的例子:

- House
   -- Room
       --- Bed

任何幫助或指導易於遵循的教程將不勝感激。

編輯:

我到目前為止的代碼,但只有父母和孩子,沒有GrandChildren。 我似乎無法弄清楚如何讓它以遞歸方式獲取所有節點。

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();
    }

}
}

這是來自實際SQL表的snippit:Locations

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

謝謝。

您正在尋找使用公用表表達式的遞歸查詢,或簡稱CTE。 可以在MSDN找到 SQL Server 2008中對此的詳細說明。

通常,它們具有類似於以下的結構:

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將執行類似於以下操作(從MSDN轉換為更簡單的語言):

  1. 將CTE表達式拆分為錨點和遞歸成員。
  2. 運行錨點,創建第一個結果集。
  3. 運行遞歸部分,將前一步作為輸入。
  4. 重復步驟3,直到返回空集。
  5. 返回結果集。 這是一個UNION ALL的錨點和所有遞歸步驟。

對於這個具體的例子,嘗試這樣的事情:

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

根據您的示例數據,您應該得到以下內容:

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

請注意,“健身房”不包括在內。 根據您的示例數據,它的ID與[位置ID]不匹配,因此它不是根級別的項目。 它的位置ID 2231未出現在有效父ID列表中。


編輯1:

您已經詢問過如何將其轉換為C#數據結構。 在C#中表示層次結構的方法有很多種。 這是一個例子,因其簡單而選擇。 毫無疑問,真正的代碼示例會更廣泛。

第一步是定義層次結構中每個節點的外觀。 除了包含節點中每個數據的屬性外,我還包括ParentChildren屬性,以及Add子項和Get子項的方法。 Get方法將搜索節點的整個后代軸,而不僅僅是節點自己的子節點。

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;
    }
}

現在你想要填充你的樹。 這里有一個問題:以錯誤的順序填充樹是很困難的。 在添加子節點之前,您確實需要對父節點的引用。 如果必須按順序執行此操作,則可以通過兩次傳遞(一個用於創建所有節點,另一個用於創建樹)來緩解問題。 但是,在這種情況下,這是不必要的。

如果您采用我在上面提供的SQL查詢並按depth列排序,則可以在數學上確定在遇到其父節點之前永遠不會遇到子節點。 因此,您可以一次完成此操作。

您仍然需要一個節點作為樹的“根”。 您可以決定這是否為“House”(來自您的示例),或者它是否是您為此目的創建的虛構占位符節點。 我建議后來。

那么,代碼! 同樣,這是為了簡化和可讀性而優化的。 您可能希望在生產代碼中解決一些性能問題(例如,不必經常查找“父”節點)。 我在這里避免了這些優化,因為它們增加了復雜性。

// Create the root of the tree.
LocationNode root = new LocationNode();

using (SqlCommand cmd = new SqlCommand()) {
    cmd.Connection = conn; // your connection object, not shown here.
    cmd.CommandText = "The above query, ordered by [Depth] ascending";
    cmd.CommandType = CommandType.Text;
    using (SqlDataReader rs = cmd.ExecuteReader()) {
        while (rs.Read()) {
            int id = rs.GetInt32(0); // ID column
            var parent = root.Get(id) ?? root;
            parent.Add(new LocationNode {
                ID = id,
                LocationID = rs.GetInt32(1),
                Name = rs.GetString(2)
            });
        }
    }
}

當當! root LocationNode現在包含整個層次結構。 順便說一下,我還沒有真正執行過這段代碼,所以如果你發現任何明顯的問題,請告訴我。


編輯2

要修復示例代碼,請進行以下更改:

刪除此行:

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

這一行實際上不是問題,但應刪除。 你在這里的評論是不准確的; 你並沒有真正為控件分配樹。 相反,您正在創建一個新的TreeView,將其分配給t1 ,然后立即將另一個對象分配給t1 下一行執行后,您創建的TreeView將丟失。

修復您的SQL語句

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

使用ORDER BY子句將此SQL語句替換為我之前建議的SQL語句。 閱讀我之前的編輯,解釋為什么“深度”很重要:您確實希望按特定順序添加節點。 在擁有父節點之前,無法添加子節點。

或者,我認為您不需要SqlDataAdapter和DataTable的開銷。 我最初建議的DataReader解決方案更簡單,更易於使用,並且在資源方面更有效。

此外,大多數C#SQL對象實現IDisposable ,因此您需要確保正確使用它們。 如果某些東西實現了IDisposable ,請確保將其包裝在using語句中(參見我之前的C#代碼示例)。

修復樹木構建循環

您只獲取父節點和子節點,因為您有父節點循環和子節點內循環。 正如你必須已經知道的那樣,你沒有得到孫子孫女,因為你沒有添加它們的代碼。

你可以添加一個內在循環來獲得孫子孫女,但顯然你是在尋求幫助,因為你已經意識到這樣做只會導致瘋狂。 如果你想要曾孫子會怎么樣? 內 - 內 - 內環? 這種技術不可行。

你可能想過這里的遞歸。 這是一個完美的地方,如果你正在處理樹狀結構,它最終會出現。 既然您已經編輯了問題,很明顯您的問題幾乎與SQL無關 你的真正問題在於遞歸。 有人可能會最終出現並為此設計一個遞歸解決方案。 這將是一個完全有效的,可能更好的方法。

但是,我的答案已經涵蓋了遞歸部分 - 它只是將其移動到SQL層。 因此,我將保留以前的代碼,因為我覺得這是一個合適的通用答案。 根據您的具體情況,您需要進行一些修改。

首先,您不需要我建議的LocationNode類。 您正在使用TreeNode ,這將正常工作。

其次, TreeView.FindNode類似於我建議的LocationNode.Get方法,除了FindNode需要到節點的完整路徑。 要使用FindNode ,必須修改SQL以提供此信息。

因此,您的整個PopulateTree函數應如下所示:

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);
                    }
                }
            }
        }
    }
}

如果您發現任何其他錯誤,請告訴我們!

我建議您還要看一下SQL Server 2008中引入的HierarchyId數據類型,它為您提供了許多遍歷和操作樹結構的功能。 這是一個教程:

在.NET應用程序中使用SQL Server HierarchyId數據類型

對不起,我只是在瀏覽StackOverflow。 我看到了你的問題,我覺得我三年前寫了一篇回答你問題的文章。 如果有幫助,請告訴我。
http://www.simple-talk.com/dotnet/asp.net/rendering-hierarchical-data-with-the-treeview/

SQl 2008中有新功能。這就是hierarchyid 這個功能讓我的生活更輕松。

對於hierarchyid數據類型 ,GetAncestor(),GetRoot()有一個有用的方法 ...一旦我在層次結構上工作,它將降低查詢的復雜性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM