SQL 数据层次结构
Posted
技术标签:
【中文标题】SQL 数据层次结构【英文标题】:SQL Data Hierarchy 【发布时间】:2011-08-27 14:34:23 【问题描述】:我浏览了一些 SQL 层次结构教程,但没有一个对我的应用程序有多大意义。也许我只是没有正确理解它们。我正在编写一个 C# ASP.NET 应用程序,我想从 SQL 数据创建一个树视图层次结构。
这就是层次结构的工作方式:
SQL 表 身份证 |位置 ID |姓名 _______| __________ |_____________ 第1331章第1331章屋 第1321章第1331章房间 第2141章第1321章床 第1251章第2231章健身房如果 ID 和位置 ID 相同,这将确定***父级。该父级的任何子级都将具有与父级相同的位置 ID。该子项的任何孙子项的位置 ID 都与子项的 ID 相同,依此类推。
对于上面的例子:
- 屋 - 房间 - - 床任何帮助或指导易于遵循的教程将不胜感激。
编辑:
到目前为止我的代码,但它只得到父母和孩子,没有孙子。我似乎无法弄清楚如何让它递归地获取所有节点。
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 表的片段:位置
ID LocationID 姓名 ____________________________________ ____________________________________ ______________ DEAF3FFF-FD33-4ECF-910B-1B07DF192074 48700BC6-D422-4B26-B123-31A7CB704B97 下降 F 48700BC6-D422-4B26-B123-31A7CB704B97 7EBDF61C-3425-46DB-A4D5-686E91FD0832 奥尔威 06B49351-6D18-4595-8228-356253CF45FF 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 下降 E 5 E98BC1F6-4BAE-4022-86A5-43BBEE2BA6CD DEAF3FFF-FD33-4ECF-910B-1B07DF192074 下降 F 6 F6A2CF99-F708-4C61-8154-4C04A38ADDC6 7EBDF61C-3425-46DB-A4D5-686E91FD0832 Pree 0EC89A67-D74A-4A3B-8E03-4E7AAAFEBE51 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 下降 E 4 35540B7A-62F9-487F-B65B-4EA5F42AD88A 48700BC6-D422-4B26-B123-31A7CB704B97 奥尔威故障 5000AB9D-EB95-48E3-B5C0-547F5DA06FC6 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 输出 1 53CDD540-19BC-4BC2-8612-5C0663B7FDA5 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 下降 E 3 7EBDF61C-3425-46DB-A4D5-686E91FD0821 B46C7305-18B1-4499-9E1C-7B6FDE786CD6 测试 1 7EBDF61C-3425-46DB-A4D5-686E91FD0832 7EBDF61C-3425-46DB-A4D5-686E91FD0832 HMN谢谢。
【问题讨论】:
这些名字令人困惑。我会将Location ID
更改为Parent_ID
。
纯 SQL 答案可能需要知道您使用的是哪种 SQL。你能指定你有哪个数据库吗?也许是 SQL Server 2008?
不幸的是,我无法更改名称,太多其他事情取决于它们。我正在使用 SQL Server 2008。
您使用的是 HierarchyID 数据类型?
【参考方案1】:
您正在寻找使用公用表表达式或简称 CTE 的递归查询。在 SQL Server 2008 中对此进行详细说明可以是found on MSDN。
一般来说,它们的结构类似于以下:
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 解释为更简单的语言):
-
将 CTE 表达式拆分为锚和递归成员。
运行锚点,创建第一个结果集。
运行递归部分,将前面的步骤作为输入。
重复第 3 步,直到返回一个空集。
返回结果集。这是锚点和所有递归步骤的 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 与其 [Location ID] 不匹配,因此它不是根级项目。它的位置 ID 2231 未出现在有效父 ID 列表中。
编辑 1:
您曾询问过如何将其放入 C# 数据结构中。在 C# 中表示层次结构的方式有很多种。这是一个例子,选择它是为了简单。一个真正的代码示例无疑会更广泛。
第一步是定义层次结构中每个节点的外观。除了包含节点中每个数据的属性外,我还包含了Parent
和Children
属性,以及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();
这一行实际上不是问题,但应该删除它。你这里的cmets不准确;您并没有真正将树分配给控件。相反,您正在创建一个新的 TreeView,将其分配给 t1
,然后立即将不同的对象分配给 t1
。一旦执行下一行,您创建的 TreeView 就会丢失。
修复您的 SQL 语句
// SQL Commands
string getLocations = "SELECT ID, LocationID, Name FROM dbo.Locations";
用我之前建议的 SQL 语句替换这个 SQL 语句,并带有一个 ORDER BY 子句。阅读我之前的编辑,它解释了为什么“深度”很重要:您确实希望以特定顺序添加节点。在拥有父节点之前,您无法添加子节点。
(可选)我认为您在这里不需要 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);
如果您发现任何其他错误,请告诉我!
【讨论】:
太好了,这就是我想要的。谢谢!不过还有一个问题。我怎么知道哪个孩子属于哪个父母?为了开发这些数据的树形视图,我需要知道关系。 有没有办法使用 ASP.NET C# 完成同样的任务,这样我就可以在查询数据的同时构建树? 我已经包含了一些示例 C# 代码,这些代码利用 CTE 查询来构建 C# 树状结构。这是你要找的吗? 这就是我要找的。我在编译时遇到了一些问题。此代码是否适用于 ASP.NET C# 项目? 是的,我认为它应该可以工作。我没有执行它,但我确实验证了它在语法上是正确的。如果您愿意,请尝试使用更多详细信息来编辑您的问题,我可以尝试为您提供帮助。或者,如果你觉得你了解层次结构的 SQL 方面,并且你被困在 C# 方面,那么你可能会认为这是一个单独的问题,并发布一个新的 SO 帖子。【参考方案2】:我建议您还看看 SQL Server 2008 中引入的HierarchyId 数据类型,它为您提供了许多遍历和操作树结构的功能。这是一个教程:
Working With SQL Server HierarchyId Data Type In .NET Application
【讨论】:
【参考方案3】:抱歉,我只是在浏览 ***。我看到了你的问题,感觉就像三年前写了一篇回答你问题的文章。如果有帮助,请告诉我。http://www.simple-talk.com/dotnet/asp.net/rendering-hierarchical-data-with-the-treeview/
【讨论】:
【参考方案4】:SQl 2008 中有一个新功能。即hierarchyid。这个功能让我的生活更轻松。
有好用的method for hierarchyid datatype、GetAncestor()、GetRoot()... 一旦我处理层次结构,它将降低查询的复杂性。
【讨论】:
以上是关于SQL 数据层次结构的主要内容,如果未能解决你的问题,请参考以下文章
从 spark 数据框或 sql 中选择具有偏好层次结构的多个记录