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

Sql server 在SQL中使用递归CTE获取每磅成本,sql-server,recursion,common-table-expression,Sql Server,Recursion,Common Table Expression,我是一个相当不错的C#程序员,在T-SQL方面有很多经验,但是我很难用递归CTE来解决我的问题 我需要得到每磅食谱的成本 食谱由配料组成,但配料也可以是其他食谱。在配料表中,有一个名为associatedProductID的字段。如果它为空,那么我们只使用该成分的成本每磅(通过获取最新的IngreditStock成本每磅找到)。如果它不为空,那么该成分实际上是另一个配方,我们需要首先找到该另一配方的每磅成本。然后我们总结该成分的总成本(配方中使用的配料数量*该配料的成本/磅),然后最后除以配方的

我是一个相当不错的C#程序员,在T-SQL方面有很多经验,但是我很难用递归CTE来解决我的问题

我需要得到每磅食谱的成本

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

下面是一些简化的表格信息

  • 表[配方]有一个intID
  • 表[RecipeIngCredits]具有intRecipeID、intIngCreditId、十进制数量
  • 表[成分]有intID,int关联产品ID
  • 表[IngredientStock]具有intID、intIngredientID、十进制成本PERLB
示例数据:

[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,也不起作用…)

提前谢谢


-David

因为您可以在SQL中使用递归函数,所以可以使用类似这样的函数-如果关联的产品ID不为NULL,则调用查询以评估其成本-可能是将每磅成本除以/乘以CASE语句中的某个值

这是一个递归函数,与递归CTE相反

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;

)
我猜你可能还需要找到每个配方的总成本和总重量,然后返回总成本/总重量-但这应该是可能的

编辑

也许像是-

您熟悉表之间的联接吗?我不确定表是如何设计为链接在一起的-您可能需要进行更改或提供更多信息-一个好方法是提供一个脚本来创建一小组示例表(尝试删除不必要的字段,因为它们太多了)并给出少量具有代表性的数据

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


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

因为您可以在SQL中使用递归函数,所以可以使用类似这样的函数—如果关联的产品ID不为NULL,则调用查询以计算其成本—可能是将每磅成本除以/乘以CASE语句中的某个值

这是一个递归函数,与递归CTE相反

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;

)
我猜你可能还需要找到每个配方的总成本和总重量,然后返回总成本/总重量-但这应该是可能的

编辑

也许像是-

您熟悉表之间的联接吗?我不确定表是如何设计为链接在一起的-您可能需要进行更改或提供更多信息-一个好方法是提供一个脚本来创建一小组示例表(尝试删除不必要的字段,因为它们太多了)并给出少量具有代表性的数据

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


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

我自己解决了。如果有人想在这方面有所改进,请让我知道…谢谢你的帮助。而且这比我的C#/EF每个2秒的响应速度快8毫秒

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

我自己解决了。如果有人想在这方面有所改进,请让我知道…谢谢你的帮助。而且这比我的C#/EF每个2秒的响应速度快8毫秒

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

你描述你的餐桌的方式——似乎一份食谱是由一系列配料(配料)和这些配料的成本(我猜)包含在配料和IngredientStock中-你能把这4个表连接起来并总结数量吗*CostPerLbI意识到你说的“配料可以是一个配方”-但这在你的表中是如何满足的?有一些表和样本数据将大大改善这个问题。此外,这些标量函数可能会给你带来一些问题严重的性能问题。标量函数的性能非常差。您在此处调用它们的方式意味着它们每行运行一次。Andrew,在配料表中,有一个名为AssociatedProductID的字段。(如问题中所述)如果此值不为空,则该配料实际上是指另一个配方。与物料清单类似,物料清单将是一个产品列表……但可能会有另一个组件(物料清单)在物料清单上被引用为产品,谢谢您的评论。此查询旨在作为一个函数:[fn_getRecipeCostPerLbs](因此使其递归)…但这只是假设性的…并且fn_函数ToGetTotalRecipelbs只在求和后调用一次。我的意图是根本不使用上面的查询..而是获得帮助来构建CTE。正如您描述表的方式-似乎配方是从成分列表(RecipeIngCredits)中生成的,以及这些的成本(我猜)包含在配料和IngredientStock中-你能把这4个表连接起来并总结数量吗*CostPerLbI意识到你说的“配料可以是一个配方”-但这在你的表中是如何满足的?有一些表和样本数据将大大改善这个问题。此外,这些标量函数可能会给你带来一些问题严重的性能问题。标量函数的性能非常差。您在此处调用它们的方式意味着它们每行运行一次。Andrew,在配料表中,有一个名为AssociatedProductID的字段。(如问题中所述)如果该值不为空,那么该配料实际上是指另一个配方。很像物料清单是一个产品列表……但可能有另一个组件(物料清单)引用了一个配方