无需递归实现二叉树查询上线/下线节点的数据结构实现






4.92/5 (12投票s)
可用于多层次营销的二叉树(可相应修改用于 n 叉树)。
引言
我们有时会面临存储二叉树数据并进行查询的问题。我们通常依赖递归(因此成本高)来查询树结构。这里有一个简单的数据结构更改,可以使用简单的 SQL 语句查询树结构 - 无需递归。
背景
在某些情况下,我们希望将二叉树的节点与它们的父节点一起存储在数据库中,并带有位置信息(在这种情况下,是父节点的左节点或右节点)。当我们要查询数据库以获取某个节点上线或下线节点,或者我们想获取二叉树中每个级别的节点计数时,问题就出现了。
您可以考虑在此代码示例的实现中,为多级营销公司创建数据结构来维护成员及其层级关系。
Using the Code
提出的解决方案包含 2 个数据表和 1 个触发器。
注意:下面使用的字段命名约定有点奇怪,但遵循以下规则 - 字段名称的前 5 个字符对应表名,接下来的 2 个字符定义数据类型,接下来的 2 个字符定义约束,其余的字符是字段名。
表 1
- tbl_BinaryTree_Nodes,存储节点详情(例如成员详情)的主表
字段
- Nodes_IN_PK_Code-- 表的主键
- Nodes_IN_FK_ParentCode-- 父节点
- Nodes_CH_xx_Position-- 父节点下的位置,左或右
- Nodes_VC_xx_NodeData-- 节点数据 -(可以添加更多字段来容纳节点数据
表 2
- tbl_BinaryTree_Hierarchy,存储节点层级信息的辅助表
字段
- Hiera_IN_PK_Code-- 表的主键
- Hiera_IN_FK_ParentCode-- 外键
- Hiera_IN_FK_ChildCode-- 外键
- Hiera_CH_xx_Position-- 父节点下的位置,左或右
- Hiera_IN_xx_NodeLevel-- 树下的节点级别
触发器 1
trg_BinaryTree_Nodes_ADD,用于更新后续查询的树层级信息
创建表和触发器的 SQL 如下
Create Table tbl_BinaryTree_Nodes
(
    Nodes_IN_PK_Code Int Identity(1,1) Constraint PK_Nodes Primary Key,
    Nodes_IN_FK_ParentCode Int Constraint _
	FK_Nodes_To_Nodes References tbl_BinaryTree_Nodes (Nodes_IN_PK_Code ),
    Nodes_CH_xx_Position Char(1) Not Null Constraint CH_Nodes_Position Check _
	(Nodes_CH_xx_Position = 'L' OR Nodes_CH_xx_Position = 'R'),
    Nodes_VC_xx_NodeData Varchar(50)
)
GO
Create Table tbl_BinaryTree_Hierarchy
(
    Hiera_IN_PK_Code Int Identity(1,1) Constraint PK_Hiera Primary Key,
    Hiera_IN_FK_ParentCode Int Constraint _
	FK_Hiera_Nodes_Parent References tbl_BinaryTree_Nodes(Nodes_IN_PK_Code ),
    Hiera_IN_FK_ChildCode Int Constraint FK_Hiera_Nodes_Child References _
	tbl_BinaryTree_Nodes(Nodes_IN_PK_Code ),
    Hiera_CH_xx_Position  Char(1) Not Null Constraint CH_Hiera_Position Check _
	(Hiera_CH_xx_Position = 'L' OR Hiera_CH_xx_Position = 'R'),
    Hiera_IN_xx_NodeLevel int
)
GO
Create Trigger trg_BinaryTree_Nodes_ADD
On tbl_BinaryTree_Nodes
For Insert
AS
Begin
    IF (@@RowCount=1)
        Begin
            Insert Into tbl_BinaryTree_Hierarchy
                (Hiera_IN_FK_ParentCode, Hiera_IN_FK_ChildCode, _
		Hiera_CH_xx_Position, Hiera_IN_xx_NodeLevel)
            Select (Case When Hiera_IN_FK_ParentCode Is Null _
		Then Hiera_IN_FK_ChildCode Else Hiera_IN_FK_ParentCode  End), 
                    (Select Nodes_IN_PK_Code From Inserted), _
			(Case When Hiera_IN_FK_ParentCode Is Null _
			Then (Select Nodes_CH_xx_Position From Inserted) _
			Else Hiera_CH_xx_Position End) , 
                    (Hiera_IN_xx_NodeLevel + 1 )
                From tbl_BinaryTree_Hierarchy Where Hiera_IN_FK_ChildCode = _
			(Select Nodes_IN_FK_ParentCode From Inserted)
            Union ALL
            Select NULL , Nodes_IN_PK_Code , Nodes_CH_xx_Position , 1 From Inserted 
        End
    Else
        Begin
            RaisError ('Multiple Insertion Not Handled in Trigger to _
				update tbl_BinaryTree_Hierarchy',1,1)
            -- Code Using Cursor for Multiple insert
            --... 
            --...
            --...
            --
        End
End
GO 
SQL 查询
我们将使用示例数据填充数据库以进行查询。我们将创建一个如下所示的树
 --'------------------------------------------
--'          The Binary Tree Representation
--'------------------------------------------
--'                 RootNode
--'             --------|-------
--'             |              |
--'            AL1            AL2
--'       -----|-----
--'       |          |
--'      AL2        AR2
--'   ---|---
--'  |       |
--' AL3     AR3
--'------------------------------------------
生成上述树的 SQL 如下
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(Null,'L','Root Node')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(1,'L','AL1')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(1,'R','AR1')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(2,'L','AL2')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(2,'R','AR2')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(4,'L','AL3')
GO
Insert Into tbl_BinaryTree_Nodes (Nodes_IN_FK_ParentCode, Nodes_CH_xx_Position, _
	Nodes_VC_xx_NodeData) Values(4,'R','AR3') 
-- A. Get All Nodes directly under current Node
Select B.Nodes_VC_xx_NodeData, Hiera_IN_xx_NodeLevel From tbl_BinaryTree_Hierarchy H
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ChildCode  = B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ParentCode=1 
-- B. Get All Nodes directly under current Node in left side
Select B.Nodes_VC_xx_NodeData, Hiera_IN_xx_NodeLevel From tbl_BinaryTree_Hierarchy H
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ChildCode  = B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ParentCode=1 And H.Hiera_CH_xx_Position ='L'
-- C. Get All Nodes directly under current Node in right side
Select B.Nodes_VC_xx_NodeData, Hiera_IN_xx_NodeLevel From tbl_BinaryTree_Hierarchy H
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ChildCode  = B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ParentCode=1 And H.Hiera_CH_xx_Position ='R'
-- D. Get All Nodes directly above the current 
Select B.Nodes_VC_xx_NodeData, Hiera_IN_xx_NodeLevel From tbl_BinaryTree_Hierarchy H
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ParentCode  = B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ChildCode=7
-- E. Get count of Nodes at each level under a specific node
Select Hiera_IN_xx_NodeLevel, count(*) From tbl_BinaryTree_Hierarchy H
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ChildCode  = B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ParentCode=1 
Group By Hiera_IN_xx_NodeLevel
-- F. To find out the Nodes which got balanced due to new node entry
-- Lets us assume that the newly added node is AR3 - (which makes AL2 balanced)
-- We can use simple SQL Query or Common Table Expressions (CTE) 
-- on SQL 2005 to get the data
-- SQL Using Common Table Expressions (CTE) on SQL 2005
With CTE as (
Select B.Nodes_IN_PK_Code as PK, B.Nodes_VC_xx_NodeData as NodeData, _
	Hiera_IN_xx_NodeLevel as NodeLevel, 
      '' as IsBalanced,
   Power(2,Hiera_IN_xx_NodeLevel)-2 as TotalNodeForBalanceTree,
   (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'L' and _
	Hiera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) _
	as TotalLeftChild,
   (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'R' and _
	iera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) _
	as TotalRightChild
From tbl_BinaryTree_Hierarchy H 
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ParentCode   = B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ParentCode   is NOT NULL and H.Hiera_IN_FK_ChildCode = 7   
					-- (Primary key of Newly Added Node) 
)
Select CTE.PK, CTE.NodeData, CTE.NodeLevel,
Case   When ((CTE.TotalNodeForBalanceTree / 2 = TotalLeftChild) and _
	(CTE.TotalNodeForBalanceTree / 2 = TotalRightChild)) then _
	'Yes' Else' No' End As IsBalanced, 
CTE.TotalNodeForBalanceTree , CTE.TotalLeftChild , CTE.TotalRightChild
From CTE 
更多查询 - 2
 --'------------------------------------------
--' TREE 2
--'  The Binary Tree Representation  Tree - 2 
--'------------------------------------------
--'                 RootNode
--'             --------|-------
--'             |              |
--'            AL              AR
--'       -----|-----      -----|-----
--'       |          |     |          |
--'      ZL         ZR     BL        BR
--'                      ---|---   ---|---
--'                     |       |  |      |
--'                    CL      CR  DL     *DR*
--'------------------------------------------ 
A. 如何根据新节点插入查找平衡节点(参考 Tree -2)
假设新添加的节点是 *DR* -(这使得 AR、BR 平衡)。
我们可以使用简单的 SQL 查询或 SQL 2005 上的通用表表达式 (CTE) 来获取数据
With CTE as (
Select B.Nodes_IN_PK_Code as PK, B.Nodes_VC_xx_NodeData as NodeData, _
	Hiera_IN_xx_NodeLevel as NodeLevel, 
      '' as IsBalanced,
   Power(2,Hiera_IN_xx_NodeLevel)-2 as TotalNodeForBalanceTree,
   (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'L' and _
	Hiera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) _
	as TotalLeftChild,
   (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'R' and _
	Hiera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) _
	as TotalRightChild
From tbl_BinaryTree_Hierarchy H 
Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ParentCode   = _
	B.Nodes_IN_PK_Code
Where H.Hiera_IN_FK_ParentCode   _
is NOT NULL and H.Hiera_IN_FK_ChildCode = 7   -- (Primary key of Newly Added Node) 
)
Select CTE.PK, CTE.NodeData, CTE.NodeLevel,
Case   When ((CTE.TotalNodeForBalanceTree / 2 = TotalLeftChild) and _
	(CTE.TotalNodeForBalanceTree / 2 = TotalRightChild)) then 'Yes' Else' No' _
	End As IsBalanced, 
CTE.TotalNodeForBalanceTree , CTE.TotalLeftChild , CTE.TotalRightChild
From CTE
B. 查找节点的极端左叶和右叶(参考 Tree -2)
要在任何节点的左侧或右侧添加新节点,您只需知道相应侧的最后一个节点(叶子节点)。您可以使用简单的 SQL 查询来获取此信息。
例如,在上面的树中,根节点的极端右侧是 *DR*,极端左侧是 ZL。类似地,节点 AR 的极端右侧是 *DR*,极端左侧是 CL。
;with CTE (ExtremeNode, Position, NodeLevel  )As 
(
    Select Hiera_IN_FK_ChildCode, Hiera_CH_xx_Position, 1 from  tbl_BinaryTree_Hierarchy 
            where Hiera_IN_xx_NodeLevel=2 and Hiera_IN_FK_ParentCode = _
		?ParentNodePK?  and Hiera_CH_xx_Position = ?LegSide?
    UNION ALL
    Select Hiera_IN_FK_ChildCode, Hiera_CH_xx_Position , CTE.NodeLevel + 1   _
		from  tbl_BinaryTree_Hierarchy 
            INNER JOIN CTE  ON CTE.ExtremeNode = Hiera_IN_FK_ParentCode _
		and CTE.Position = Hiera_CH_xx_Position
            where Hiera_IN_xx_NodeLevel = 2
)
Select Top 1 *  From CTE order By NodeLevel Desc; 
C. 获取因新插入而成为平衡节点的节点列表(参考 Tree -2 和 MLM 佣金支付)
当 *DR* 被添加时,BR 和 AR 会获得报酬 - 但具体多少尚不清楚 - 根据一般的 MLM 趋势,我们可以计算出 BR 将获得 1 个对的收益,AR 将获得 1 个对的收益。
我们需要找出相对于新添加节点的父节点列表,这些节点将获得收益。
现在,获取需要支付报酬的节点列表如下。
下面的 SQL 查询将为您提供所有父节点的列表,最后一列 ShouldIPay (Pay 或 DoNotPay)指示哪个父节点需要支付报酬(根据需要修改字段列表)。
With CTE as (
  Select B.Nodes_IN_PK_Code as PK, B.Nodes_VC_xx_NodeData as NodeData, _
	Hiera_IN_xx_NodeLevel as NodeLevel, _
	Hiera_CH_xx_Position as Position, '' as IsBalanced,
    Power(2,Hiera_IN_xx_NodeLevel)-2 as TotalNodeForBalanceTree,
    (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'L' and _
	Hiera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) as TotalLeftChild,
    (Select Count(Hiera_IN_PK_Code) From tbl_BinaryTree_Hierarchy _
	Where Hiera_CH_xx_Position = 'R' and _
	Hiera_IN_FK_ParentCode=B.Nodes_IN_PK_Code) as TotalRightChild
  From tbl_BinaryTree_Hierarchy H 
  Left Outer Join tbl_BinaryTree_Nodes B on H.Hiera_IN_FK_ParentCode   = _
	B.Nodes_IN_PK_Code
  Where H.Hiera_IN_FK_ParentCode   is NOT NULL and _
	H.Hiera_IN_FK_ChildCode = ?PKofNewnode?   -- (Primary key of Newly Added Node) 
)
Select CTE.PK, CTE.NodeData, CTE.NodeLevel, CTE.Position,
Case   When ((CTE.TotalNodeForBalanceTree / 2 = TotalLeftChild) _
	and (CTE.TotalNodeForBalanceTree / 2 = TotalRightChild)) _
	then 'Yes' Else' No' End As IsBalanced, 
CTE.TotalNodeForBalanceTree , CTE.TotalLeftChild , CTE.TotalRightChild,
Case   When Position ='L' then (Case When TotalLeftChild<=TotalRightChild _
	Then 'PAY' Else 'Do Not Pay' End ) 
       When Position ='R' then (Case When TotalLeftChild>=TotalRightChild _
	Then 'PAY' Else 'Do Not Pay' End ) 
        End As ShouldIPay
From CTE 
这些表可以轻松修改以存储有关节点信息的附加信息,但基本字段将保持不变。
目前,触发器仅针对单次插入编写,我们可以修改触发器以处理批量插入。我们需要编写触发器来处理节点移动,以修改层级表。
历史
- 修订 1:添加了更多查询 - 2 个部分

