SQL数据层次结构

我已经查看了一些SQL层次结构教程,但它们对我的应用程序都没有多大意义。 也许我只是没有正确理解它们。 我正在编写一个C#ASP.NET应用程序,我想从SQL数据创建一个树视图层次结构。

这是层次结构的工作方式:

 SQL TABLE

 ID | 位置ID | 名称
 _______ |  __________ | _____________
 1331 |  1331 | 屋
 1321 |  1331 | 房间
 2141 |  1321 | 床
 1251 |  2231 | 健身房

如果ID和位置ID相同,则将确定顶级父级。 该父母的任何子女都将拥有与父母相同的位置ID。 该孩子的任何孙子女的位置ID都等于孩子的ID,依此类推。

对于上面的例子:

 - 屋
    - 房间
        ---床

任何帮助或指导易于遵循的教程将不胜感激。

编辑:

我到目前为止的代码,但只有父母和孩子,没有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 位置 ID 名称
 ____________________________________ ____________________________________ ______________
 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故障
 5000AB9D-EB95-48E3-B5C0-547F5DA06FC6 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0输出1
 53CDD540-19BC-4BC2-8612-5C0663B7FDA5 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 Drop E 3
 7EBDF61C-3425-46DB-A4D5-686E91FD0821 B46C7305-18B1-4499-9E1C-7B6FDE786CD6测试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 Children = new List(); 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数据类型,它为您提供了许多遍历和操作树结构的function。 这是一个教程:

在.NET应用程序中使用SQL Server HierarchyId数据类型

对不起,我只是在浏览StackOverflow。 我看到了你的问题,我觉得我三年前写了一篇回答你问题的文章。 如果有帮助,请告诉我。
http://www.simple-talk.com/dotnet/asp.net/rendering-hierarchical-data-with-the-treeview/

SQl 2008中有新function。那就是hierarchyid 。 这个function让我的生活更轻松。

hierarchyid数据类型有一些有用的方法 ,GetAncestor(),GetRoot()……一旦我在层次结构上工作,它将降低查询的复杂性。