是否可以使用join进行父子查询而不通过临时表循环?
数据库示例:
menuid name parent url
----------------------------------------------------------
A0000 Master A0000 #
A0001 Rekening A0000 /master/rekening.aspx
A0002 Master Nominal A0001 /master/nominal.aspx
A0003 Master Satuan Other A0001 /master/satuan.aspx
A0004 Master Kondisi A0000 /master/kondisi.aspx
A0005 Master Tujuan A0003 /master/tujuan.aspx
A0006 Master Item A0003 /master/item.aspx
A0007 Master Warehouse A0000 /master/warehouse.aspx
A0008 Master Kapal A0006 /master/kapal.aspx
如果选择了uri = '/master/kapal.aspx'
所需的结果:
menuid name parent url
----------------------------------------------------------
A0000 Master A0000 #
A0001 Rekening A0000 /master/rekening.aspx
A0003 Master Satuan Other A0001 /master/satuan.aspx
A0006 Master Item A0003 /master/item.aspx
A0008 Master Kapal A0006 /master/kapal.aspx
如果选择了uri = /master/tujuan.aspx'
所需的结果:
menuid name parent url
----------------------------------------------------------
A0000 Master A0000 #
A0001 Rekening A0000 /master/rekening.aspx
A0005 Master Tujuan A0003 /master/tujuan.aspx
示例查询:
declare @menuid varchar(255) = 'menuid'
declare @parent varchar(255) = 'parent'
declare @temp_parent varchar(255)
declare @i smallint = 0
delete from temp_menu
while (@menuid <> @parent)
begin
if(@i = 0)
begin
insert into temp_menu
select * from menu where uri = '/master/kapal.aspx'
select @menuid = menuid, @parent = parent from menu where uri = '/master/kapal.aspx'
set @i = 1;
end
else
begin
insert into temp_menu
select * from menu where menuid = @parent
select @menuid = menuid, @temp_parent = parent from menu where menuid = @parent
set @parent = @temp_parent;
end
end
select * from temp_menu
hieararchy的样本:
A0000
|_______________________
| | |
A0001 A0004 A0007
|________
| |
A0002 A0003
|_______
| |
A0005 A0006
|
A0008
更新:
我想从节点parent
到menuid
获取最长分支中的所有行,如果parent
与menuid
相同或者没有menuid
匹配则停止与parent
。
添加脚本和样本
IF OBJECT_ID('dbo.menu', 'U') IS NOT NULL
DROP TABLE dbo.menu
GO
IF OBJECT_ID('dbo.temp_menu', 'U') IS NOT NULL
DROP TABLE dbo.temp_menu
GO
IF OBJECTPROPERTY(object_id('dbo.sp_get_parent'), N'IsProcedure') = 1
DROP PROCEDURE dbo.sp_get_parent
GO
create table dbo.menu (
menuid varchar(255)
, name varchar(255)
, parent varchar(255)
, uri varchar(255)
);
insert into dbo.menu (menuid, name, parent, uri)
values ('A0000', 'Master', 'A0000', '#')
, ('A0001', 'Rekening', 'A0000', '/master/rekening.aspx')
, ('A0002', 'Master Nominal', 'A0001', '/master/nominal.aspx')
, ('A0003', 'Master Satuan Other', 'A0001', '/master/satuan.aspx')
, ('A0004', 'Master Kondisi', 'A0000', '/master/kondisi.aspx')
, ('A0005', 'Master Tujuan', 'A0003', '/master/tujuan.aspx')
, ('A0006', 'Master Item', 'A0003', '/master/item.aspx')
, ('A0007', 'Master Warehouse', 'A0000', '/master/warehouse.aspx')
, ('A0008', 'Master Kapal', 'A0006', '/master/kapal.aspx');
create table dbo.temp_menu (
menuid varchar(255)
, name varchar(255)
, parent varchar(255)
, uri varchar(255)
);
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
Create PROCEDURE [dbo].[sp_get_parent]
@uri VARCHAR (255)
AS
declare @menuid varchar(255) = 'menuid'
declare @parent varchar(255) = 'parent'
declare @temp_parent varchar(255)
declare @i smallint = 0
delete from temp_menu
while (@menuid <> @parent)
begin
if(@i = 0)
begin
insert into temp_menu
select * from menu where uri = @uri
select @menuid = menuid, @parent = parent from menu where uri = @uri
set @i = 1;
end
else
begin
insert into temp_menu
select * from menu where menuid = @parent
select @menuid = menuid, @temp_parent = parent from menu where menuid = @parent
set @parent = @temp_parent;
end
end
select * from temp_menu order by menuid asc
GO
对于上面的所需示例,可以尝试此查询:
sp_get_parent '/master/kapal.aspx'
和
sp_get_parent '/master/tujuan.aspx'
答案 0 :(得分:2)
在SQL Server中,关于如何查询分层数据的每个问题的答案都是使用递归公用表表达式。
在您的情况下,由于您想获得最长的分支,您应该添加一个计数列:
;WITH CTE AS
(
SELECT menuid, name, parent, url, 0 as level
FROM menu WHERE parent = menuid -- Usually, the parent column is simply nullable
UNION ALL
SELECT menu.menuid, menu.name, menu.parent, menu.url, level + 1
FROM menu
INNER JOIN CTE ON menu.parent = CTE.menuid
AND menu.parent <> CTE.parent -- This is why parent column is nullable :-)
)
SELECT TOP 1 *
FROM CTE
ORDER BY Level DESC
此查询将为您提供离其顶级父级最远的叶子。
<强>更新强>
根据您的评论,我认为这正是您所寻找的:
;WITH CTERecursion AS
(
SELECT menuid,
name,
parent,
url,
0 as level,
menuid as TopLevelParent
FROM menu WHERE parent = menuid -- Usually, the parent column is simply nullable
UNION ALL
SELECT menu.menuid,
menu.name,
menu.parent,
menu.url,
level + 1,
TopLevelParent
FROM menu
INNER JOIN CTERecursion CTE ON menu.parent = CTE.menuid
AND menu.menuid <> CTE.menuid -- This is why parent column is nullable :-)
), CTELongestPath AS
(
SELECT TOP 1 TopLevelParent
FROM CTERecursion
ORDER BY Level DESC
)
SELECT menuid, name, parent, url
FROM CTERecursion r
INNER JOIN CTELongestPath l ON r.TopLevelParent = r.TopLevelParent
更新#2
既然你的问题已经改变了,你似乎只想从一个叶子到另一个父亲遍历。在这种情况下,您的递归CTE应该是这样的:
DECLARE @url varchar(100) = '/master/kapal.aspx';
;WITH CTERecursion AS
(
SELECT menuid,
name,
parent,
url
FROM menu
WHERE url = @url
UNION ALL
SELECT menu.menuid,
menu.name,
menu.parent,
menu.url
FROM menu
INNER JOIN CTERecursion CTE ON menu.menuid = CTE.parent
AND menu.menuid <> CTE.menuid -- This is why parent column is nullable :-)
)
SELECT menuid, name, parent, url
FROM CTERecursion
答案 1 :(得分:0)
drop table if exists dbo.Menu;
create table dbo.Menu (
menuid varchar(100)
, name varchar(100)
, parent varchar(100)
, url varchar(100)
);
insert into dbo.Menu (menuid, name, parent, url)
values ('A0000', 'Master', 'A0000', '#')
, ('A0001', 'Rekening', 'A0000', '/master/rekening.aspx')
, ('A0002', 'Master Nominal', 'A0001', '/master/nominal.aspx')
, ('A0003', 'Master Satuan Other', 'A0001', '/master/satuan.aspx')
, ('A0004', 'Master Kondisi', 'A0000', '/master/kondisi.aspx')
, ('A0005', 'Master Tujuan', 'A0003', '/master/tujuan.aspx')
, ('A0006', 'Master Item', 'A0003', '/master/item.aspx')
, ('A0007', 'Master Warehouse', 'A0000', '/master/warehouse.aspx')
, ('A0008', 'Master Kapal', 'A0006', '/master/kapal.aspx');
with cteMenu as (
select
m.menuid, m.name, m.parent, m.url
, convert(varchar(max), '.' + m.menuid + '.') as Hierarchy
, 0 as Lvl
from dbo.Menu m
where m.menuid = m.parent
union all
select
m.menuid, m.name, m.parent, m.url
, cm.Hierarchy + m.menuid + '.' as Hierarchy
, cm.Lvl + 1 as Lvl
from dbo.Menu m
inner join cteMenu cm on m.parent = cm.menuid
where m.menuid <> m.parent
)
select
cm.menuid, cm.name, cm.parent, cm.url
from (
select
top(1)
cm.*
from cteMenu cm
order by cm.Lvl desc
) t
inner join cteMenu cm on t.Hierarchy like cm.Hierarchy + '%'
答案 2 :(得分:0)
使用这样的公用表表达式(CTE):
WITH cte_name AS
(
SELECT <base_elements> FROM <table_name> WHERE <root_condition>
UNION ALL
SELECT <child_elements>
FROM cte_name
JOIN <table_name> ON cte_name.id = <table_name>.parentid
)
SELECT DISTINCT * FROM cte_name
这样做会选择所有作为根元素的元素。在您的情况下,这将类似于menuid = 'A0000'
。
然后它将所有这些联合回自身并再次加入原始表,只要它找到parentId与(child)的匹配-Id在你的情况下,on条件将是menu.parent = cte.menuid
。
然后它选择与递归查询返回的所有内容不同的所有内容。如果你想获得最大深度,你必须在根选择上添加一个常量值,如(0),然后随着每个联合所有增加它。然后,您可以从最终的不同查询中选择max(nesting_level)
。
在你的情况下,这样的事情可以起作用
WITH cteMenu AS
(
SELECT menuid, name, parent, url, 0 as nesting_level FROM menu WHERE menuid = 'A0000'
UNION ALL
SELECT menu.menuid, menu.name, menu.parent, menu.url, nesting_level + 1
FROM menu
JOIN cteMenu ON menu.parent = cteMenu.menuid
WHERE menu.id <> 'A0000'
)
SELECT DISTINCT * FROM cteMenu WHERE nesting_level = (SELECT MAX(nesting_level) FROM cteMenu)
/ update:将WHERE子句WHERE menu.id <> 'A0000'
添加到内部选择中,这将从选择中删除根元素并停止无限递归。
答案 3 :(得分:0)
declare @uri nvarchar(256) = '/master/kapal.aspx'
; with cte as (
select MenuId, Name, Parent, Url
, 1 reverseOrder
from MyTable
where url = @uri
union all
select b.MenuId, b.Name, b.Parent, b.Url
, a.reverseOrder + 1
from cte a
inner join MyTable b
where b.MenuId = a.Parent
and b.MenuId = a.MenuId --don't repeat ourselves after reaching the root
)
select MenuId, Name, Parent, Url
from cte
ordre by reverseOrder desc
<强>更新强>
对于问题的第二部分(即最长的分支),请尝试:
; with cte as (
select MenuId, Name, Parent, Url
, 1 branchLength
, cast(MenuId as nvarchar(max)) branchPath
from MyTable
where Parent = menuid --i.e. top level / root elements
union all
select b.MenuId, b.Name, b.Parent, b.Url
, a.branchLength + 1
, a.branchPath + '\' + cast(b.MenuId as nvarchar(max)) branchPath
from cte a
inner join MyTable b
where b.Parent = a.MenuId --i.e. branch of previous result
and b.MenuId != a.MenuId --ensure we don't repeat ourselves
)
select a.MenuId, a.Name, a.Parent, a.Url
from cte a
inner join (
select top 1 branchPath
from cte
order by branchLength Desc
) b
on b.branchPath like a.branchPath + '%' --gives us an easy way to traverse back up the tree, without recording every possible paths' inheritance / by using a directory structure
ordre by a.branchLength desc