1

私が扱っている非常に複雑なクエリがあり、その一部で製品に関連付けられたカテゴリを取得する必要があります。カテゴリはCategoryテーブルに再帰的に格納されます。製品とカテゴリのマッピングをProductCategory表に示します (技術的には、1 つの製品に複数のカテゴリを含めることができますが、考慮すべき単純な変数でない限り、ここではテーブルから除外します)。

Categoryはとてもシンプルです。1 つの列はCategoryID、別の列は 、 ParentCategoryID3 番目の列はName列です。これから、カテゴリがネストされます。ProductCategoryテーブルもシンプル。ある列はProductID別の列ですCategoryID

特定の製品の最上位と 2 番目のカテゴリを取得する必要があります。次に、この情報をいくつかの分析を含むレポートで使用します。私のソリューションは非常に遅く、うまくスケーリングできません。必要なデータをより効率的に抽出する方法がわかりません。

私の解決策は、カテゴリが割り当てられた特定の製品の親であるすべてのカテゴリを収集し、最後に見つけた 2 つを取得して返すことです。これをスカラー関数として実行し、現在CategoryIDの値と必要なレベルを送信するので、1 つの呼び出しでは 0、別の呼び出しでは 1 になります。

私のサンプルコード:

WITH Categories AS (
    SELECT DISTINCT
        CategoryID
    FROM
        ProductCategory
), CategoriesAtDepth AS (
    SELECT
        Categories.CategoryID
        , dbo.WR_f_GetCategoryIDAtDepth(Categories.CategoryID, 0) AS TopCategory
        , dbo.WR_f_GetCategoryIDAtDepth(Categories.CategoryID, 1) AS SecondCategory
    FROM
        Categories
)
SELECT
    CategoriesAtDepth.CategoryID
    , c1.Name AS TopCategory
    , c2.Name AS SecondCategory
FROM
    CategoriesAtDepth LEFT JOIN
    Category AS c1 ON CategoriesAtDepth.TopCategory = c1.CategoryID LEFT JOIN
    Category AS c2 ON CategoriesAtDepth.SecondCategory = c2.CategoryID

そして関数コード:

CREATE FUNCTION WR_f_GetCategoryIDAtDepth 
(
    @CategoryID AS int
    ,@Depth AS int = 0
)
RETURNS int
AS
BEGIN

    -- Declare the return variable here
    DECLARE @Result int
    DECLARE @CurrentHeight int = 0
    DECLARE @CurrentCategoryID int = @CategoryID
    DECLARE @CategoryLevels table
    (
        Height int
        ,CategoryID int
    )

    BEGIN
        --Populate a table with all the categoy IDs in the chain
        WHILE @CurrentCategoryID > 0
        BEGIN
            INSERT INTO @CategoryLevels (Height, CategoryID) VALUES (@CurrentHeight + 1, @CurrentCategoryID)
            SET @CurrentCategoryID = (SELECT ParentCategoryID FROM Category WHERE CategoryID = ISNULL((SELECT CategoryID FROM @CategoryLevels WHERE Height = @CurrentHeight + 1), 0))
            SET @CurrentHeight = @CurrentHeight + 1
        END
        SET @Result = (SELECT CategoryID FROM @CategoryLevels WHERE Height = (@CurrentHeight - @Depth))
    END

    -- Return the result of the function
    RETURN @Result

END
GO

@George Mavritsakis による再帰的 CTE の使用に関するコメントについてさらに考え、関数に実装してみることにし、このはるかに高速なソリューションを思いつきました。

CREATE FUNCTION WR_f_GetCategoryIDAtDepth 
(
    @CategoryID AS int
    ,@Depth AS int = 0
)
RETURNS int
AS
BEGIN

    -- Declare the return variable here
    DECLARE @Result int
    DECLARE @CategoryLevels table
    (
        Height int
        ,CategoryID int
    )

    BEGIN
        --Populate a table with all the categoy IDs in the chain
        WITH Base AS (
            SELECT
                0 AS Height
                , @CategoryID AS CategoryID

            UNION ALL

            SELECT
                Height + 1
                , ParentCategoryID
            FROM
                Category INNER JOIN
                    Base ON Category.CategoryID = Base.CategoryID
        )
        INSERT INTO @CategoryLevels (Height, CategoryID)
            SELECT * FROM Base

        SET @Result = (SELECT CategoryID FROM @CategoryLevels WHERE Height = ((SELECT MAX(Height) FROM @CategoryLevels) - @Depth - 1))
    END

    -- Return the result of the function
    RETURN @Result

END
GO
4

1 に答える 1