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