3

次の形式の階層情報を含むSQLServer2008R2データベーステーブルがあります。

MarketID    Time                    menuPath                                                       SID  MarketName
107397507   2012-11-18 13:00:00.000 \Project 1\Phase 1\Project Mgmt\Date 18 November\Requirements   1   Meeting
107397508   2012-11-18 13:00:00.000 \Project 1\Phase 1\Project Mgmt\Date 18 November\Requirements   1   Plan
145556789   2012-11-20 12:00:00.000 \Project 2\Phase 3\Training\Date 20 November                    3   Verbal
145686775   2012-11-20 15:00:00.000 \Project 2\Phase 4\Testing\Date 20 November                     3   Structural
145686776   2012-11-20 15:00:00.000 \Project 2\Phase 4\Testing\Date 20 November                     3   Optical

必要な階層出力は次のとおりです。

ID  ParentID    Depth   Name          MarketID
1   0           0       Project 1        NULL
2   1           1       Phase 1          NULL
3   2           2       Project Mgmt     NULL
4   3           3       18 November      NULL
5   4           4       Requirements     NULL
6   5           5       Meeting          107397507
7   5           5       Plan             107397508
8   0           0       Project 2        NULL
9   8           1       Phase 3          NULL
10  9           2       Training         NULL
11  10          3       20 November      NULL
12  11          4       12:00 Verbal     145556789
13  8           1       Phase 4          NULL
14  13          2       Testing          NULL
15  14          3       20 November      NULL
16  15          4       15:00 Structural 145686775
17  15          4       15:00 Optical    145686776

注:「日付」という単語がノード「11月18日」から削除されていることに注意してください。

  • 編集:一意の親子ノードのみが出力されます。たとえば、「プロジェクト1 \フェーズ1」ノードは1つだけですが、「11月20日」ノードは2つあります。1つは「トレーニング\ 11月20日」で、もう1つは「テスト」です。 11月20日」。
  • 編集:SID = 3のすべてのノードについて、時間を最後のノードに追加する必要があります。例:「テスト\2011月20日\15:00オプティカル」
  • 編集:menuPathフィールドに含まれる正確な深さは異なる場合があります。

これを実現する次の手続き型SQLクエリを作成できましたが、同等のセットベースのアプローチに変換する方法を知っている人はいますか?

/* Begin build of Menu table */
Declare @marketid int
Declare @Time DATETIME

DECLARE @StrMenu NVARCHAR(MAX)
DECLARE @SID INT

DECLARE @StrMarketName NVARCHAR(MAX)

DECLARE @selection VARCHAR(MAX)
DECLARE @parentname VARCHAR(MAX)
DECLARE @parentid INT
DECLARE @depth INT
DECLARE @boolDate INT
DECLARE @EIND INT 

DECLARE @Part NVARCHAR(MAX)
DECLARE @IND    INT

DECLARE cur CURSOR LOCAL for
    SELECT MarketID, Time, menuPath, SID, MarketName FROM test.dbo.Markets

OPEN cur
fetch next from cur into @marketid, @Time, @StrMenu, @SID, @StrMarketName

while @@FETCH_STATUS = 0 BEGIN
    SET @IND = CHARINDEX('\',@StrMenu)
    -- if the last character is not a \ then append it to the string
    IF RIGHT(@StrMenu,1) != '\'
        BEGIN
            SET @StrMenu = @StrMenu + '\'
        END

    IF @SID = 3
        BEGIN
        -- IF SID = 3 then append the Time to the MarketName
            SET @StrMarketName = (convert(varchar(5), @Time, 108)) + ' ' + @StrMarketName
        END

    SET @StrMenu = @StrMenu + @StrMarketName + '\'

    Set @EIND = 0
    SET @boolDate = 0
    SET @depth = 0

    WHILE(@IND != LEN(@StrMenu))
        BEGIN
            SET  @EIND = ISNULL(((CHARINDEX('\', @StrMenu, @IND + 1)) - @IND - 1), 0)
            SET @selection = (SUBSTRING(@StrMenu, (@IND  + 1),  @EIND))

            IF @depth = 0
                BEGIN
                    SET @parentid = 0
                END
            IF @depth > 0
                BEGIN
                    SET @parentid = (SELECT TOP 1 ID FROM test.dbo.Menu WHERE NAME = @parentname ORDER BY ID DESC )
                END

            IF (@selection LIKE '%Date%')
                BEGIN
                    SET @boolDate = 1
                    SET @selection = REPLACE(@selection, 'Date ', '')
                    SET @parentid = (SELECT ID FROM test.dbo.Menu WHERE NAME = @parentname )
                    -- insert values into the menu table
                    IF NOT EXISTS (SELECT NAME FROM test.dbo.Menu WHERE NAME = @selection AND ParentID = @parentid)
                        INSERT INTO test.dbo.Menu (ParentID, Depth, Name)
                        Values (@parentid, @depth, @selection)
                END

            -- only continue if the selection and its parent combination does not already exist
            IF NOT EXISTS (SELECT ID FROM test.dbo.Menu WHERE NAME = @selection AND ParentID = @parentid) AND @boolDate = 0
                BEGIN                   
                    IF (LEN(@StrMenu) = @EIND + @IND + 1)
                        BEGIN
                            -- If the current loop is the last loop then insert the MarketID
                            INSERT INTO test.dbo.Menu (ParentID, Depth, Name, MarketID)
                            Values (@parentid, @depth, @selection, @marketid)
                        END
                    Else
                        BEGIN
                            -- Otherwise only insert the basic info into the menu table
                            INSERT INTO test.dbo.Menu (ParentID, Depth, Name)
                            Values (@parentid, @depth, @selection)
                        END
                END

            SET @boolDate = 0
            -- increment the index values and set the parent name for the next loop
            SET @IND = ISNULL(CHARINDEX('\', @StrMenu, @IND + 1), 0)
            SET @depth = @depth + 1
            SET @parentname = @selection
        END
    fetch next from cur into @marketid, @Time, @StrMenu, @SID, @StrMarketName
END

close cur
deallocate cur

menuPathこのSQLは、列から階層情報を抽出するために作成しました。SID番号に応じて、MarketName情報とTime列もこれに追加されます。menuPathたとえば、SID = 1の場合はのみMarketNameが追加されますが、SID = 3の場合は、TimeとのMarkeName両方が追加されます。

はノードMarketIDのメニューテーブルにのみ追加されますMarketName

テーブルスキーマと私が使用しているデータの例を以下に示します。

    USE [test]
GO

CREATE TABLE [dbo].[Markets](
    [MarketID] [int] PRIMARY KEY NOT NULL,
    [Time] [datetime] NULL,
    [menuPath] [varchar](255) NULL,
    [SID] [int] NULL,
    [MarketName] [varchar](255) NULL
    )

CREATE TABLE [dbo].[Menu](
    [ID] [int] PRIMARY KEY IDENTITY,
    [ParentID] [int] NOT NULL,
    [Depth] [int] NOT NULL,
    [Name] [varchar] (255) NOT NULL,
    [MarketID] [int] NULL
 )

INSERT Markets (MarketID, Time, menuPath, SID, MarketName)
SELECT 107397507, '2012-11-18 13:00:00.000', '\Project 1\Phase 1\Project Mgmt\Date 18 November\Requirements', 1, 'Meeting'
UNION ALL SELECT 107397508, '2012-11-18 13:00:00.000', '\Project 1\Phase 1\Project Mgmt\Date 18 November\Requirements', 1, 'Plan'
UNION ALL SELECT 107397509, '2012-11-18 13:00:00.000', '\Project 1\Phase 1\Project Mgmt\Date 18 November\Requirements', 1, 'Write Up'
UNION ALL SELECT 107397513, '2012-11-18 13:00:00.000', '\Project 1\Phase 1\Project Mgmt\Date 18 November\Building 1', 1, 'Plan'
UNION ALL SELECT 107397514, '2012-11-18 13:00:00.000', '\Project 1\Phase 1\Project Mgmt\Date 18 November\Building 1', 1, 'Write Up'
UNION ALL SELECT 107397533, '2012-11-19 14:30:00.000', '\Project 1\Phase 1\Project Mgmt\Date 19 November\Building 2', 1, 'Plan'
UNION ALL SELECT 107397537, '2012-11-19 14:30:00.000', '\Project 1\Phase 1\Project Mgmt\Date 19 November\Building 2', 1, 'Write Up'
UNION ALL SELECT 107398573, '2012-11-20 09:00:00.000', '\Project 1\Phase 1\Installation\Date 20 November\Building 3', 1, 'Plan'
UNION ALL SELECT 107398574, '2012-11-20 09:00:00.000', '\Project 1\Phase 1\Installation\Date 20 November\Building 3', 1, 'Write Up'
UNION ALL SELECT 108977458, '2012-11-21 10:00:00.000', '\Project 1\Phase 2\Setup\Date 21 November\Building 4', 1, 'Prep'
UNION ALL SELECT 108977459, '2012-11-21 10:00:00.000', '\Project 1\Phase 2\Setup\Date 21 November\Building 4', 1, 'Clear'
UNION ALL SELECT 145556788, '2012-11-20 12:00:00.000', '\Project 2\Phase 3\Training\Date 20 November', 3, 'Written'
UNION ALL SELECT 145556789, '2012-11-20 12:00:00.000', '\Project 2\Phase 3\Training\Date 20 November', 3, 'Verbal'
UNION ALL SELECT 145686775, '2012-11-21 15:00:00.000', '\Project 2\Phase 4\Testing\Date 21 November', 3, 'Structural'
UNION ALL SELECT 145686776, '2012-11-21 15:00:00.000', '\Project 2\Phase 4\Testing\Date 21 November', 3, 'Optical'
4

1 に答える 1

1

XML拡張機能を使用して、パスをそのコンポーネントパラメーターに分割します。また、xml拡張機能を使用して、各xml要素の位置を取得することもできROW_NUMBERますdepth

WITH Menus AS
(   SELECT  m.MarketID,
            [Name] = y.value('.', 'nvarchar(max)'),
            [Depth] = ROW_NUMBER() OVER(PARTITION BY MarketID ORDER BY y.value('for $i in . return count(../*[. << $i]) + 1', 'int')) - 1
    FROM    Markets m
            CROSS APPLY (VALUES (CAST('<x><y>' + REPLACE(menuPath, '\', '</y><y>') + '</y><y>' + CASE WHEN SID = 3 THEN CONVERT(VARCHAR(5), [Time], 8) + ' ' ELSE '' END +  marketName + '</y></x>' AS XML))) a (x)
            CROSS APPLY x.nodes('/x/y') b (y)
    WHERE   y.value('.', 'nvarchar(max)') != ''
)
SELECT  MarketID,
        [Name] = CASE WHEN LEFT(Name, 5) = 'Date ' THEN STUFF(Name, 1, 5, '') ELSE Name END,
        Depth
FROM    Menus

SQLフィドルでの分割の例

テーブル構造に冗長な情報が含まれることに注意してください。再帰の数をカウントして最上位の親に戻るか、marketIDがすべての行に格納されている場合は、最上位の親を次のように取得できます。深さ=0を見つける。したがって、上記のクエリの出力は、必要なすべてを提供するはずです。しかし、それでも続けます。

0最初のステップは、親としてすべてのアイテムをメニューテーブルに挿入することです。

WITH Menus AS
(   SELECT  m.MarketID,
            [Name] = y.value('.', 'nvarchar(max)'),
            [Depth] = ROW_NUMBER() OVER(PARTITION BY MarketID ORDER BY y.value('for $i in . return count(../*[. << $i]) + 1', 'int')) - 1
    FROM    Markets m
             CROSS APPLY (VALUES (CAST('<x><y>' + REPLACE(menuPath, '\', '</y><y>') + '</y><y>' + marketName + '</y></x>' AS XML))) a (x)
            CROSS APPLY x.nodes('/x/y') b (y)
    WHERE   y.value('.', 'nvarchar(max)') != ''
)
INSERT Menu (ParentID, Depth, Name, MarketID) 
SELECT  [ParentID] = 0,
        Depth,
        [Name] = CASE WHEN LEFT(Name, 5) = 'Date ' THEN STUFF(Name, 1, 5, '') ELSE Name END,
        MarketID
FROM    Menus

次に、適切な親IDでマーケットテーブルを更新します

UPDATE  Menu
SET     ParentID = p.ID
FROM    Menu c
        INNER JOIN 
        (   SELECT  ID, MarketID, Depth
            FROM    Menu 
        ) p
            ON c.MarketID = p.MarketID
            AND c.Depth = p.Depth + 1

最後のステップは、ベースメニューを除くすべてのマーケットIDをnullに設定することです。

WITH CTE AS
(   SELECT  *,  
            [maxDepth] = MAX(Depth) OVER(PARTITION BY MarketID)
    FROM    Menu
)
UPDATE  CTE
SET     MarketID = NULL
WHERE   MaxDepth != Depth;

そして出来上がり、あなたはあなたの望む結果を持っています。

SQLフィドルの例


補遺

これはうまくいくようです:

CREATE TABLE #TempMenu (MarketID INT, Name VARCHAR(200) NOT NULL, Depth INT NOT NULL);
WITH Menus AS
(   SELECT  m.MarketID,
            [Name] = y.value('.', 'nvarchar(max)'),
            [Depth] = ROW_NUMBER() OVER(PARTITION BY MarketID ORDER BY y.value('for $i in . return count(../*[. << $i]) + 1', 'int')) - 1
    FROM    Markets m
             CROSS APPLY (VALUES (CAST('<x><y>' + REPLACE(menuPath, '\', '</y><y>') + '</y><y>' + CASE WHEN SID = 3 THEN CONVERT(VARCHAR(5), [Time], 8) + ' ' ELSE '' END +  marketName + '</y></x>' AS XML))) a (x)
            CROSS APPLY x.nodes('/x/y') b (y)
    WHERE   y.value('.', 'nvarchar(max)') != ''
)
INSERT #TempMenu (MarketID, name, Depth)
SELECT  MarketID,
        [Name] = CASE WHEN LEFT(Name, 5) = 'Date ' THEN STUFF(Name, 1, 5, '') ELSE Name END,
        Depth
FROM    Menus;

CREATE TABLE #TempPaths 
(   ID          INT NOT NULL, 
    ParentID    INT NOT NULL, 
    Depth       INT NOT NULL, 
    Name        VARCHAR(200) NOT NULL, 
    MarketID    INT NULL, 
    ParentPath  VARCHAR(200) NULL, 
    CurrentPath VARCHAR(200) NULL
);

WITH Paths AS
(   SELECT  MarketID,
            [Name] = CASE WHEN LEFT(Name, 5) = 'Date ' THEN STUFF(Name, 1, 5, '') ELSE Name END,
            Depth,
            [MaxDepth] = MAX(Depth) OVER(PARTITION BY MarketID),
            [ParentPath] = (    SELECT  '/' + Name
                                FROM    #TempMenu p
                                WHERE   p.MarketID = c.MarketID
                                AND     p.Depth < c.Depth
                                FOR XML PATH(''), TYPE
                            ).value('.', 'nvarchar(max)'),
            [CurrentPath] = (   SELECT  '/' + Name
                                FROM    #TempMenu p
                                WHERE   p.MarketID = c.MarketID
                                AND     p.Depth <= c.Depth
                                FOR XML PATH(''), TYPE
                            ).value('.', 'nvarchar(max)')
    FROM    #TempMenu c
), Paths2 AS
(   SELECT  DISTINCT [ParentID] = 0, Depth, Name, [MarketID] = NULL, [ParentPath], [CurrentPath]
    FROM    Paths
    WHERE   MaxDepth != Depth
    UNION 
    SELECT  0, Depth, Name, MarketID, [ParentPath], [CurrentPath]
    FROM    Paths
    WHERE   MaxDepth = Depth
)
-- USE MERGE CONDITION THAT WILL NEVER MATCH, ALLOWS ACCESS TO VALUES NOT BEING INSERTED IN THE OUTPUT CLAUSE
MERGE INTO Menu m USING Paths2 p ON 1 = 0  
WHEN NOT MATCHED THEN
    INSERT (ParentID, Depth, Name, MarketID)
    VALUES (p.ParentID, p.Depth, p.Name, p.MarketID)
    OUTPUT inserted.ID, inserted.ParentID, inserted.Depth, inserted.Name, inserted.MarketID, p.ParentPath, p.CurrentPath INTO #TempPaths;

UPDATE  Menu
SET     ParentID = rel.ParentID
FROM    Menu
        INNER JOIN
        (   SELECT  [ChildID] = c.ID, [ParentID] = p.ID
            FROM    #TempPaths c
                    INNER JOIN #TempPaths p
                        ON c.ParentPath = p.CurrentPath
        ) rel
            ON rel.ChildID = Menu.ID;

DROP TABLE #TempMenu, #TempPaths;

何が起こっているのかを大まかに説明するために、上記と同じ方法を使用してパスをコンポーネントパーツに分割し、これらを一時テーブルに配置します(パフォーマンス上の理由から)。分割されたパスは再びフルパスに結合され、別のパスに配置されます。一時テーブル。これらのパスは、後で親レコードと子レコードを照合するために使用されます。

次の部分では、mergeステートメントを使用してレコードをメニューテーブルに挿入します。これは、挿入からのIDをフルパスに一致させる必要があるために使用されます。使用INSERTするOUTPUT場合は、挿入された値へのアクセスのみを許可し、他の列へのアクセスは許可しません。ソースから。

最後に、すべてのレコードが挿入されると、パスの一時テーブルを使用して、(パスに基づいて)親レコードと子レコードを照合し、メニューテーブルを更新できます。

これはかなり複雑な方法のように見えますが、完全にセットベースであるため、手続き型アプローチを実行する必要があります。

于 2013-02-04T16:36:22.040 に答える