在SQL中使用递归CTE获取每磅成本

时间:2016-10-24 13:58:59

标签: sql-server recursion common-table-expression

我是一个相当不错的C#程序员,并且在T-SQL方面有很多经验,但我无法整理递归CTE来解决我的问题。

我需要获得食谱的每磅成本。

食谱由配料组成,但配料也可以是其他食谱。在Ingredient表中,有一个名为associatedProductID的字段。如果它为null,那么我们只需使用该成分的CostPerLb(通过获取最新的IngredientStock CostPerLb找到。如果它不为空,那么该成分实际上是另一个配方,我们需要首先找到其他成分的每磅成本然后我们总结该成分的总成本(配方中使用的成分的数量*该成分的costPerLb),然后最后除以配方的总Lbs以获得配方的CostPerLbl

这是一些简化的表格信息。

  • 表格[食谱]有一个int ID
  • table [ RecipeIngredints ]具有int RecipeID ,int IngredientID ,十进制数量
  • 表[成分]具有int ID ,int AssociatedProductID
  • 表[ IngredientStock ]具有int ID ,int IngredientID ,decimal CostPerLb

示例数据:

[Recipes]
+------+
| ID   |
+------+
| 11   |
+------+
| 465  |
+------+
| 356  |
+------+
| 1617 |
+------+

[Ingredients]
+--------------+--------------------+
| IngredientID | AssociatedPrductId |
+--------------+--------------------+
| 213          | NULL               |
+--------------+--------------------+
| 214          | NULL               |
+--------------+--------------------+
| 216          | NULL               |
+--------------+--------------------+
| 218          | NULL               |
+--------------+--------------------+
| 219          | 465                |
+--------------+--------------------+
| 225          | 356                |
+--------------+--------------------+
| 150          | NULL               |
+--------------+--------------------+
| 213          | NULL               |
+--------------+--------------------+
| 692          | 1617               |
+--------------+--------------------+
| 172          | NULL               |
+--------------+--------------------+
| 218          | NULL               |
+--------------+--------------------+
| 4            | NULL               |
+--------------+--------------------+
| 691          | NULL               |
+--------------+--------------------+

[RecipeIngredients]
+----------+--------------+-------------+
| RecipeID | IngredientID | Quanity     |
+----------+--------------+-------------+
| 11       | 213          | 2           |
+----------+--------------+-------------+
| 11       | 214          | 1           |
+----------+--------------+-------------+
| 11       | 216          | 4.31494     |
+----------+--------------+-------------+
| 11       | 218          | 10.4125     |
+----------+--------------+-------------+
| 11       | 219          | 10.37085    |
+----------+--------------+-------------+
| 11       | 225          | 3.141971875 |
+----------+--------------+-------------+
| 465      | 150          | 0.0995      |
+----------+--------------+-------------+
| 465      | 213          | 0.25        |
+----------+--------------+-------------+
| 465      | 692          | 6.752298547 |
+----------+--------------+-------------+
| 356      | 172          | 200         |
+----------+--------------+-------------+
| 356      | 218          | 249.9       |
+----------+--------------+-------------+
| 1617     | 4            | 26.59274406 |
+----------+--------------+-------------+
| 1617     | 691          | 0.743192188 |
+----------+--------------+-------------+

在我的脑海中,一般的递归函数可能看起来像这样......(这显然不是CTE,也不起作用......)

 FUNCTION [dbo].[fn_getRecipeCostPerLbs]
 (@ID INT = 0)
RETURNS DECIMAL(24,12)
AS
BEGIN select
 sum(
        --get the actual amount of ingredient used in the recipe (in lbs)
        ri.Quantity

        --get the cost of the ingredient (in $ / lbs)
        case 
          --when associated product id is null then use the latest ingredient stock cost / lbs
          --when its not null then use the cost / lbs of the recipe that it relates too
        when  i.AssociatedProductID is null then 
            --pull the cost/lbs of the newest stock in warehouse
            isNull((select top 1 ist.CostPerLb from IngredientStock ist where ist.IngredientID = i.id order by ist.id desc),0)
        else
            [dbo].[fn_getRecipeCostPerLbs](@ID)
        end
    )
    / 
    dbo.fn_FunctionToGetTotalRecipeLbs(@ID) -- divide by total lbs of the recipe to get cost Per Lbs
    as CostPerLbs


from RecipeIngredients ri
inner join Ingredients i on i.id = ri.IngredientID
where ri.RecipeID = @ID
)

提前致谢!

-David

2 个答案:

答案 0 :(得分:0)

因为您可以在SQL中使用递归函数,所以使用类似这样的东西 - 如果关联产品ID为NOT NULL,则调用查询来评估其成本 - 可能将每磅成本除以/乘以CASE中的内容声明

这是一个递归函数,而不是递归CTE

j==100

我想你也可能需要找到每个食谱的总成本和总重量,然后返回总成本/总重量 - 但这应该是可能的

修改

可能像 -

你熟悉表之间的连接吗?我不确定你的表是如何设计链接在一起的 - 你可能需要进行更改或提供更多信息 - 这样做的一个好方法是给脚本制作一小组样本表(尝试和删除不必要的字段,id太多了,并提供少量的代表性数据

CREATE FUNCTION [dbo].[fn_getRecipeCostPerLbs]
 (@ID INT)
RETURNS DECIMAL(24,12)
AS
BEGIN

DECLARE @COST DECIMAL(24,12) = 0;


SELECT @COST = SUM(CASE WHEN IngredientStock.AssociatedProductID IS NULL 
                                                    THEN 
                                                        IngredientStock.CostPerLb 
                                                    ELSE  
                                                        [dbo].[fn_getRecipeCostPerLbs](IngredientStock.AssociatedProductID)
                                                    END
                    )

                                     FROM RecipeIngredints  
                                      join Ingredients 
                                         on RecipeIngredints.RecipeID = @ID 
                                        AND 
                                        Ingredients.id = RecipeIngredints.IngredientID
                                     join IngredientStock 
                                         on 
                                         IngredientStock.ID = Ingredients.AssociatedProductID

    RETURN @COST;

)

答案 1 :(得分:0)

自己解决了。如果有人想改进这一点请告诉我...感谢您的帮助。这也比我的C#/ EF 2 SEC更好地响应8ms。

CREATE FUNCTION [dbo].[fn_getRecipeCostPerPound]
(
@ID INT = 0
)
RETURNS DECIMAL(24,12)
AS
BEGIN

Declare @Ret decimal(24,12) = 0;
Declare @TotalLbs decimal(24,12) =  [dbo].[fn_RecipeLbs](@ID)


    DECLARE @MyCTETempTable TABLE (RecipeID int,
    IngredientID int,
    AssociatedProductID int,
    Quantity decimal(24,12),
    level int,
    CostPerLbs decimal(24,12)
    )


; with  CTE as 
        (
            select  
              ri.RecipeID
              ,ri.IngredientID
              ,i.AssociatedProductID
             ,dbo.fn_ConvertUomToPounds(ri.Quantity,ri.UnitOfMeasureID,i.specificgravity) as Quantity
              ,1 as level
            from    RecipeIngredients ri inner join Ingredients i on i.id = ri.IngredientID 
            where   ri.RecipeID = @ID
            union all
            select  
               ri_child.RecipeID
              ,ri_child.IngredientID
              ,i_child.AssociatedProductID
              ,dbo.fn_ConvertUomToPounds(ri_child.Quantity,ri_child.UnitOfMeasureID,i_child.specificgravity) as Quantity
              ,level + 1
            from     RecipeIngredients ri_child inner join Ingredients i_child on i_child.id = ri_child.IngredientID --and i_child.AssociatedProductID is null
            join    CTE parent
            on      parent.AssociatedProductID = ri_child.RecipeID
        )

INSERT INTO @MyCTETempTable (
RecipeID,
IngredientID,
AssociatedProductID,
Quantity,
level,
CostPerLbs
)  

select 
cte.*,  isNull((select top 1 ist.CostPerLb from IngredientStock ist where ist.IngredientID = cte_i.id order by ist.id desc),0) 
from    CTE
inner join Ingredients cte_i on cte_i.id = CTE.IngredientID

SET @Ret = (select sum(Quantity * CostPerLbs) / @TotalLbs
from @MyCTETempTable tt 
where RecipeID = @ID
group by RecipeID)



return @Ret

END