簡體   English   中英

自聯接查詢

[英]Self-join Query

是否可以使用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的最長分支中獲取所有行,如果parentmenuid相同或者沒有與parent menuid匹配則menuid

添加腳本和樣本

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'

在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 
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 + '%'

使用這樣的公用表表達式(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)的匹配menu.parent = cte.menuid在你的情況下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'到內部選擇,這將從選擇中刪除根元素並停止無限遞歸。

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

暫無
暫無

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

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