两次 table 没有层次结构的递归查找,这可能吗?

Two table recursive lookup without hierarchy, is this possible?

这是我正在使用的 SQL 服务器数据库的当前设计。

我有两个 table:

  1. 食谱

  1. 配方成分

食谱由食谱配料组成,但一种配料可以是另一种食谱。理论上有无限级别,因为每个食谱都可以包含另一种也是食谱的成分。

在上面的数据示例中,Fresh Salsa 食谱 (ID 3047) 有 7 种成分。六种是原材料,但一种是另一种配方(配方 ID 3008)。此配方 ID 引用 'recipes' table.

中的另一个配方

没有层次结构,我认为我无法创建层次结构。

目标是提取具有 'sub' 食谱和 'sub-sub' 食谱等的特定食谱的所有食谱项目。

递归查找似乎是答案,但由于没有层次结构,这似乎行不通。

这是我尝试的查询(recipeItem 列表变量是所有 recipeitems 的列表,这些 recipeitems 也是在先前查询中创建的食谱):

<cfquery name="whatever">

WITH MenuPrepOfPreps (recipe_id, depth, otherRecipe_id, recipe_name)
AS

(
    SELECT r.recipe_id, 
    0 as depth,
    ri.otherRecipe_id,
    r.recipe_name 
    FROM menu_recipes r
    JOIN menu_recipeItems ri
        ON ri.otherRecipe_id = r.recipe_id
    WHERE ri.otherRecipe_id in (#recipeItemList#)
    UNION ALL
-- recursive members
    SELECT 
    mop.recipe_id,
    mop.depth + 1 as depth,
    ri.otherRecipe_id,
    r.recipe_name
    FROM menu_recipes r
    JOIN menu_recipeItems ri
        ON ri.otherRecipe_id = r.recipe_id
    INNER JOIN MenuPrepOfPreps AS MOP
        ON ri.otherRecipe_id = MOP.recipe_id
        
)

SELECT top(6)recipe_id, recipe_name
FROM MenuPrepOfPreps
GROUP BY recipe_id, recipe_name

</cfquery>

它一直在创建一个无限循环。当我将结果限制在前几行(前 6 行)时,它确实给出了所需的结果。

可能是数据库的设计不正确,所以这可能永远行不通。

感谢任何帮助。

[更新查询基于@NewBie20200101 提议的解决方案并更改了 VARIABLE/COLUMN 名称]

<cfquery name="whatever">

WITH MenuPrepOfPreps AS

(
    SELECT otherrecipe_id, 
    CASE 
        when 
            otherRecipe_id = 0 then null 
        else 
            otherRecipe_id 
        end 
        as sub_recipe 

    FROM menu_recipeItems as a -- anchor

    UNION ALL

    SELECT 
    a.otherrecipe_id,
    CASE 
        when
            b.otherRecipe_id = 0 then null 
        else
            b.otherRecipe_id 
        end 
        as sub_recipe
    FROM menu_recipeItems as b
    where b.recipe_id = a.otherRecipe_id --recursion
    and a.otherRecipe_id is null --stopper      
), allrecipeitems as (

SELECT recipe_id, sub_recipe
FROM MenuPrepOfPreps
)

Select
 c.recipe_id,
 d.otherRecipe_id
 From MENU_recipes c
 INNER JOIN MENU_recipeItems d on c.recipe_id = d.otherRecipe_id
 Where c.recipe in (#recipelist#)

</cfquery>

不起作用并出现以下错误:

The multi-part identifier "a.otherRecipe_id" could not be bound.

不确定这是否有效:

With preppreprep as (
    Select     
        Recipeid,    
        Case when otherrecipeId = 0 then null else otherrecipeID end as otherrecipeID, ——remove 0 it might be a problem    
    From 
    Recipeitems as a ——————anchor    
     Left outer join recipeitems as b on a.otherrecipeID = b.recipeID
    Union all

    Select
        C.recipeid,    
        Case when c.otherrecipeID = 0 the null else c.other recipeID end as otherrecipeID, also remove 0    
    From preprepprep as c 
    Left outer join recipeitems as d
    Where c.recipeid = d.otherrecipeID———--recursion   
), allrecipeitems as (
    Select    
        RecipeID,
        OtherrecipeID
    From preprepprep    
)
Select
    C.RecipeID
    D.OtherRecipeID
From recipe c
Inner recipeitems d on c.recipeid = d.recipeid
Where c.recipe in (##)
——extract unpacked sub recipes based on recipe

如果您认为有超过 99 个级别,请添加选项最大递归 0

我在所有贡献者的帮助下自己解决了这个问题(谢谢)。首先,正如@Charlieface 所提到的,我是从错误的方向来处理这个问题的。有了这个解决方案,我从层次结构的底部开始,直到层次结构中没有更多数据可以获取。

最后一个查询有多个过滤器和分组,以便准确获取我需要的信息。

如果有人感兴趣,这里是解决方案:

WITH MenuPrepOfPreps (recipe_id, otherRecipe_id, depth, recipe_name)

AS (
SELECT
    mria.recipe_id, mria.otherRecipe_id, 0, mr.recipe_name
FROM 
    menu_recipeItems mria, menu_recipes mr
where 
    mria.otherrecipe_id <> 0
    and mria.recipe_id = mr.recipe_id

UNION ALL

SELECT 
    MenuPrepOfPreps.recipe_id, mri.otherrecipe_id, MenuPrepOfPreps.depth+1, mr.recipe_name
FROM 
    menu_recipeItems mri, MenuPrepOfPreps, menu_recipes mr
WHERE MenuPrepOfPreps.otherrecipe_id = mri.recipe_id 
    AND MRI.otherrecipe_id <> 0
    AND mr.recipe_id = mri.recipe_id
)

SELECT 
    mopsA.otherrecipe_id, mopsA.recipe_id, mopsA.depth, mra.recipe_name AS thePrepRecipeName
FROM 
    MenuPrepOfPreps mopsA, MENU_recipes MRA
WHERE 
    mopsA.recipe_id in (#recipelist#)
    AND mopsA.otherRecipe_id = MRA.recipe_id 
    AND MRA.recipeType_id = 2
GROUP BY mra.recipe_name, mopsA.otherrecipe_id, mopsA.recipe_id, mopsA.depth

我将不得不看看它在比我现在拥有的样本集更大的数据集上的表现如何,但到目前为止它非常快。

我将创建一个 table 值函数,该函数 returns 所有叶子 recipeItem 用于单个 recipe_id (扁平化层次结构),没有递归而是循环一次解决一个递归级别(而不是遵循每个分支)。如果你有 100 万个低至 10 级深度的子​​食谱,则需要 12 次迭代才能获取数据(10(深度)+ 1(确定没有任何变化)+ 1(从食谱中获取所有叶子)。

这种方法还有一个好处,就是不需要设置限制,如果数据库中定义了无限循环,我们丝毫不会感到困扰。

这里是 table 值函数的代码:

CREATE FUNCTION [dbo].[GetRecipeItemsFlat](@recipe_id INT)
RETURNS @recipeItems TABLE (recipeItem_id INT NOT NULL)
AS
BEGIN
  DECLARE @recipeList TABLE (recipe_id INT);
  DECLARE @previousCount INT = 0;
  DECLARE @currentCount INT = 1;
  -- Get all recipe_ids recursively until infinity (no endless loop possible)
  INSERT INTO @recipeList SELECT @recipe_id;
  WHILE (@previousCount < @currentCount) BEGIN
    -- Adding not yet added child recipe_ids 
    INSERT INTO @recipeList SELECT DISTINCT otherRecipe_id FROM recipeItem WHERE recipe_id IN (SELECT recipe_id FROM @recipeList) AND (otherRecipe_id IS NOT NULL) AND (otherRecipe_id != 0) AND (otherRecipe_id NOT IN (SELECT recipe_id FROM @recipeList));
    SET @previousCount = @currentCount ;
    SET @currentCount = (SELECT COUNT(*) FROM @recipeList);
  END
  INSERT INTO @recipeItems SELECT recipeItem_ID FROM recipeItem WHERE recipe_id IN (SELECT * FROM @recipeList) AND (rawMaterial_id IS NOT NULL) AND (rawMaterial_id != 0);
  RETURN;
END

然后我将创建一个视图,为 recipe 提供所有叶 recipeItem,如下所示:

CREATE VIEW recipeItemFlat
AS
SELECT c.recipe_id, c.recipeItem_id, c.ItemQuantity, c.rawMaterial_id 
  FROM recipe a 
  CROSS APPLY dbo.GetRecipeItemsFlat(recipe_id) b 
  INNER JOIN recipeItem c ON b.recipeItem_id = c.recipeItem_id;

然后获取配方 3047 的所有项目的查询很简单:

SELECT * FROM recipeItemFlat WHERE recipe_id = 3047

这个解决方案可能存在一个问题,我通常将其用于权利和角色解析以及诸如此类无关紧要的事情,但在这里它可能是:

如果有一个食谱 A 有两个食谱 B 和 C,而 B 和 C 都有一个食谱 D,那么 D 在结果中只会出现一次,将这些数量相加会得出错误的结果。如果这是相关的,请不要使用 DISTINCT 来确定 table 值函数中循环的结束,而是像您在示例中所做的那样使用最大级别。