2017-06-07 24 views
3

一時テーブルをループせずに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 

サンプルクエリ:

hieararchyと
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 

サンプル:UPDATED

A0000 
|_______________________ 
|    |  | 
A0001   A0004 A0007 
|________ 
|  | 
A0002 A0003 
     |_______ 
     |  | 
     A0005 A0006 
       | 
       A0008 

私はmenuidと同じparentは、あるいは全くmenuidが存在しない場合menuidからparentとが停止したノードから可能な最長のブランチからのすべての行を取得したいです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' 
+2

https://stackoverflow.com/questions/tagged/ sql-server + recursive-query –

+0

したがって、最長のブランチに属するノードを取得したいですか? –

+0

@GiorgosBetsos:理解を深めるために階層スキームを更新します。ありがとうございました。 –

答えて

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 
+0

こんにちは、私はロジックを取得しますが、それはちょうど最長の行を示しています。有望な答えは、これによって引き起こされたものをまだ考え出している。 –

+0

私は自分の答えを編集した後、あなたの質問をもう一度変更したのを見た。私たちがあまりにも懸命に働かないようにしてください。あなたが質問を投稿する前に取得したいものを知っていることを確認してください。サンプルデータをDDL + DMLとして投稿することができれば、非常に役に立ちます。読みにくいですが、テスト環境にコピー&ペーストするのは簡単です。 –

+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 + '%' 
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から(子)への一致が見つかるまで元の表に再び結合します。その場合、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) 

/更新を働くことができ、このようなあなたの場合、何かに

:これは選択からルート要素を削除し、無限再帰を停止し、内側のSELECTにWHERE句WHERE menu.id <> 'A0000'を追加しました。

+0

こんにちは、私は得るこのエラー 'Msg 530、レベル16、状態1、行1 ステートメントが終了しました。最大再帰100は、サンプルデータベースを使用してステートメントの完了前に使い果たしました。 –

+0

@ColourDalnetちょうどあなたの質問を読んでください。その場合の問題は、ルートメニュー項目が親として設定されていることです。 where句を追加します(私の答えを更新します)。 – Adwaenyth

+0

ありがとうございました –

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