处理多个托盘和生产批次的SQL递归

huangapple go评论62阅读模式
英文:

Handle SQL recursion for multiple pallets and production runs

问题

我需要追踪生产批次的成本,并分摊到原始接收的托盘上。但是,一旦原始托盘经过生产批次,它们会与来自该生产批次的所有其他输入托盘混合在一起,然后创建多个输出托盘。理论上,这些输出托盘可以出售,也可以回到另一个生产批次,然后以完全不同的托盘再次出现,这个过程可以重复发生未知次数。表结构还增加了一层复杂性。所有托盘都在一个表中创建。

托盘表包括两种类型:

  • N:最初收到的托盘
  • P:从生产批次生产的托盘,并且ProdOrd字段填充了批次号

每当我尝试为这种情况建立CTE递归时,我都会遇到递归的无限循环,因为这是一对多关系。我认为SQL服务器在这个层次上无法处理它。我们有数千个原始托盘,然后它们在混洗中混合在一起。

我希望得到类似这样的结果:

托盘 成本类型 成本 类型
1234 生产批次 3.00 N
1234 标签 0.25 N
1235 生产批次 3.00 N
1235 标签 0.25 N

成本是分摊到原始输入托盘的所有生产批次的总和。

英文:

I need to trace the costs of production runs and apportion back to the original received pallets. However, once the original pallets goes through a production run, they are mixed with all the other input pallets from that production run and they create multiple output pallets. Those output pallets in theory could be sold OR go back into another production run and come out again as completely different pallets and this process can repeat itself an unknown number of times. The table structure also adds a layer of complication. All of the pallets are created in one table.
Basic Hierarchal Structure

The Pallets table includes two types:

  • N: The originally received Pallet
  • P: Pallet produced from a Production run and ProdOrd field is populated with the Run Number

Every time I try to build out the CTE recursion for this scenario, I run into a indefinite loop of the recursion because it is a many to many relationship. I'm thinking that SQL server can't handle it at this level. We have thousands of original pallets and then they get mixed in the shuffle.

What I'm hoping for is something like this:

Pallet CostType Cost Type
1234 Run 3.00 N
1234 Labeling 0.25 N
1235 Run 3.00 N
1235 Labeling 0.25 N

The cost is the Sum of all the production runs apportioned to the original input pallet.

答案1

得分: 0

以下是您要的翻译:

我认为我已经捕捉到递归CTE计算托盘内容在各种生产过程运行中传播的意图。

为了计算托盘内容在整个生产系列中的传播,我们首先必须计算每个步骤中从每个输入托盘到每个输出托盘的传播。该计算结果是输入托盘的数量、按批次的总输入、按批次的总输出和输出托盘的数量的复杂组合。

以下是我在几个步骤中提出的解决方案。

首先,计算每个生产运行和批次的输入和输出总数。我们将在以下步骤的计算中需要这些值。

接下来,计算与每个生产运行、输入和输出托盘组合相关的输入和输出。同一批次的多个输入托盘中剩余的材料被假定均匀混合并重新分配到输出托盘。

现在,我们可以使用递归CTE检索初始托盘信息,并针对每个托盘,跟随生产运行的路径,以计算原始托盘的内容如何在系列中传播。

有关演示,请参见此数据库示例

这在某种程度上有点过于复杂,因为有一些中间计算实际上没有被使用。如果您有问题,请随时提问,但如果您愿意,我将把简化、增强或其他适应您需求的任务留给您。

将成本计算应用到附加步骤是后续练习。

英文:

I think I have captured the intent up to the point where the recursive CTE calculates the propagation of pallet contents through the various process runs.

In order to calculate the propagation of pallet content through the entire production series, we must first calculate the propagation from each input pallet to each output pallet at each step. That calculation turns out to be a non-trivial combination of quantity per input pallet, total input by lot, total output by lot, and quantity per output pallet.

The following is what I came up with across several steps.

First, calculate input and output totals for each production run and lot. We will need these values in the calculations of the following step

CREATE TABLE #InOutQty(
  ProdOrd NVARCHAR(10), Lot NVARCHAR(12),
  InTotalQty INT, OutTotalQty INT,
  PassThroughFraction FLOAT, ConsumedFraction FLOAT
)
  
INSERT #InOutQty
    SELECT IQ.ProdOrd, IQ.Lot, IQ.InTotalQty, ISNULL(OQ.OutTotalQty, 0),
        A1.PassThroughFraction,
        (1e0 - A1.PassThroughFraction) AS ConsumedFraction
    FROM (
       -- Total up inputs by production run and lot
       SELECT I.ProdOrd, P.Lot, Sum(I.QTY) AS InTotalQty
       FROM ProdInput I
       JOIN Pallets P ON P.Pallet = I.Pallet
       GROUP BY I.ProdOrd, P.Lot
    ) IQ
    LEFT JOIN(
       -- Total up inputs by production run and lot (might be none for certain inputs) 
       SELECT P.ProdOrd, P.Lot, Sum(P.OrgQTY) AS OutTotalQty
       FROM Pallets P
       WHERE P.Type = 'P'
       GROUP BY P.ProdOrd, P.Lot
    ) OQ
        ON OQ.ProdOrd = IQ.ProdOrd
        AND OQ.Lot = IQ.Lot
    CROSS APPLY (
        -- Calculate how much is unused and repacked on a new pallet 
        SELECT (1e0 * ISNULL(OQ.OutTotalQty, 0) / IQ.InTotalQty) AS PassThroughFraction
    ) A1

Next calculate the input and output attributable to each combination of production run, input, and output pallets. Leftover materials from multiple input pallets of the same lot are assumed to be mixed evenly and reallocated to the output pallets.

CREATE TABLE #PalletInOutQty (
    ProdOrd NVARCHAR(10), Lot NVARCHAR(12),
    InputPallet NVARCHAR(12), OutputPallet NVARCHAR(12),
    InputQty FLOAT, OutputQty FLOAT,
    InputPalletFraction FLOAT, PassThroughFraction FLOAT
)
  
INSERT #PalletInOutQty
SELECT IOQ.ProdOrd, IOQ.Lot, IP.Pallet, OP.Pallet,
    A1.InputQty,
    (A1.InputQty * IOQ.PassThroughFraction) AS OutputQty,
    (A1.InputQty * 1e0 / IP.OrgQty) AS InputPalletFraction,
    IOQ.PassThroughFraction
FROM #InOutQty IOQ
JOIN ProdInput PI ON PI.ProdOrd = IOQ.ProdOrd
JOIN Pallets IP ON IP.Pallet = PI.Pallet AND IP.Lot = IOQ.Lot
JOIN Pallets OP ON OP.ProdOrd = IOQ.ProdOrd AND OP.Lot = IOQ.Lot
CROSS APPLY (
    -- Input quantity for this in/out pallet combination is calculated as the
    -- the selected input pallet qty times the fraction of the total output
    -- included in the selected output pallet.
    SELECT (PI.Qty * (OP.OrgQty * 1e0 / IOQ.OutTotalQty)) AS InputQty
) A1

Now we can use a recursive CTE to retrieve the initial pallet information, and for each pallet, follow the path through the production run to calculate how the content of that original pallet propagates through the series.

IF OBJECT_ID('tempdb..#PalletLineage') IS NOT NULL
    DROP TABLE #PalletLineage
CREATE TABLE #PalletLineage (
    Pallet NVARCHAR(12),
    OriginPallet NVARCHAR(12),
    Lineage NVARCHAR(1000),
    Qty FLOAT,
    OriginPalletFraction FLOAT
)

;WITH cte AS (
    SELECT
        P.Pallet,
        P.Pallet AS OriginPallet, 
        CAST(P.Pallet AS NVARCHAR(MAX)) AS Lineage,
        CAST(P.OrgQty AS Float) AS Qty,
        1.0e0 AS OriginPalletFraction
    FROM Pallets P
    WHERE P.Type = 'N'

    UNION ALL

    SELECT
        PIOQ.OutputPallet AS Pallet,
        cte.OriginPallet AS OriginPallet,
        CONCAT(cte.Lineage, ' > ', PIOQ.OutputPallet) AS Lineage,
        (cte.Qty / P.OrgQty * PIOQ.OutputQty) AS Qty,
        (cte.OriginPalletFraction * PIOQ.InputPalletFraction
            * PIOQ.PassThroughFraction) AS OriginPalletFraction
    FROM cte
    JOIN #PalletInOutQty PIOQ ON PIOQ.InputPallet = cte.Pallet
    JOIN pallets P ON P.Pallet = cte.Pallet
)
INSERT #PalletLineage
SELECT *
FROM cte

See this db<>fiddle for a demo.

This is somewhat over-engineered in that several intermediate calculations are not really used. Feel free to ask questions if you have them, but I'll leave it to you to strip it down, enhance it, or otherwise adapt to your needs if you wish.

Applying additional steps to apply cost calculations is left as a follow-up exercise.

huangapple
  • 本文由 发表于 2023年3月1日 08:38:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/75598616.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定