根据条件将单个数据行拆分为多个数据行的 SQL 脚本

Posted

技术标签:

【中文标题】根据条件将单个数据行拆分为多个数据行的 SQL 脚本【英文标题】:SQL Script to split single data rows into multiple data rows based on a condition 【发布时间】:2013-08-27 04:49:51 【问题描述】:

我想根据批次数量拆分客户订单。

这是客户订单表。

Product           Size    Purch. Qty    Lot Qty 
-----------------------------------------------
Addidas Shoe       8         30         25  
Addidas Shoe       9         15         25
Addidas Shoe      10         50         25
Puma Shoe          7         40         30  
Puma Shoe          8         60         30

我有 2 种产品 Addidas 鞋根据其 Lot qty 25 拆分为多行,Puma 鞋根据其 Lot qty 30 拆分为多行,如下所示。

批号 SL。没有产品尺寸数量 1000 1 阿迪达斯鞋 8 25 1001 1 阿迪达斯鞋 8 5 1001 2 阿迪达斯鞋 9 15 1001 3 阿迪达斯鞋 10 5 1002 1 阿迪达斯鞋 10 25 1003 1 阿迪达斯鞋 10 20 1004 1 彪马鞋 7 30 1005 1 彪马鞋 7 10 1005 2 彪马鞋 8 20 1006 1 彪马鞋 8 30 1007 1 彪马鞋 8 10

请帮我搞定这个。我使用的是 Sql server 2005。

【问题讨论】:

你使用什么 rdbms? SQL Server、PostgreSQL、mysql?如果您显示给定的输入和所需的输出,它也会有所帮助 为什么每个Product 中只有第一个有Lot Qty?看来您想要获取Purch. Qty 并将该产品的所有订单拆分为Lot Qty 的批次,尽管我在您的输出中看到了多种模式,因此很难弄清楚您的实际规则是什么。跨度> 产品“Addidas shoe”根据其批次 25 拆分为多行,“Puma shoe”根据其批次 30 拆分为多行。 【参考方案1】:

我相信这不会给我很多支持,因为它很复杂,但这仍然是一项有趣的任务。我认为这可以通过游标方法来完成,但也可以通过递归 CTE 来完成。我会尽量让它可读,用 outer apply 预先计算值肯定会有所帮助。

我稍微修改了你的架构。我认为您必须将Lot Qty 存储在不同的表中,例如:

create table Products ([Product] varchar(12), [Lot Qty] int);
insert into Products
values
('Addidas Shoe', 25),
('Puma Shoe', 30);

您仍然可以将Lot Qty 存储在您的订单表中,但它没有被规范化。 至于这个问题,我认为递归 CTE 可以在这里为您提供帮助:

with cte1 as (
   select
       row_number() over(partition by c.[Product] order by c.size) as row_num,
       c.*,
       P.[Lot Qty]
   from CustOrder as c
       inner join Products as P on P.[Product] = c.Product
), cte2 as (
    select
        c.Product, c.Size,
        0 as [Lot No],
        1 as [Sl. No],
        c.[Purch Qty] - c.[Lot Qty] as [Rem Qty],
        case when c.[Purch Qty] > c.[Lot Qty] then c.[Lot Qty] else c.[Purch Qty] end as Qty,
        case when c.[Purch Qty] > c.[Lot Qty] then c.row_num else c.row_num + 1 end as row_num2,
        c.[Lot Qty]
    from cte1 as c
    where c.row_num = 1

    union all

    select
        c.Product, c.Size,
        CALC.[Lot No],
        CALC.[Sl. No],
        CALC.[Purch Qty] - CALC.[Lot Qty] as [Rem Qty],
        case when CALC.[Purch Qty] > CALC.[Lot Qty] then CALC.[Lot Qty] else CALC.[Purch Qty] end as Qty,
        case when CALC.[Purch Qty] > CALC.[Lot Qty] then c.row_num else c.row_num + 1 end as row_num2,
        c.[Lot Qty]
    from cte1 as c
        inner join cte2 as c2 on c2.row_num2 = c.row_num and c2.Product = c.product
        outer apply (
             select
                 case when c2.[Rem Qty] < 0 then c2.[Lot No] else c2.[Lot No] + 1 end as [Lot No],
                 case when c2.[Rem Qty] < 0 then c2.[Sl. No] + 1 else 1 end as [Sl. No],
                 case when c2.[Rem Qty] < 0 and abs(c2.[Rem Qty]) < c.[Lot Qty] then abs(c2.[Rem Qty]) else c.[Lot Qty] end as [Lot Qty],
                 case when c2.[Rem Qty] > 0 and c2.[Rem Qty] < c.[Purch Qty] then c2.[Rem Qty] else c.[Purch Qty] end as [Purch Qty]
        ) as CALC

)
select
    999 + dense_rank() over(order by c.Product, c.[Lot No]) as [Lot No],
    c.[Sl. No],
    c.Product, c.Size, c.Qty
from cte2 as c
order by product, [Lot No]

sql fiddle demo

基于光标的解决方案:

declare @Lot table (ItemSize int, Product varchar(20), Qty int)

declare @RemQty int = 0, @curQty int, @curLotQty int
declare @ItemSize int, @Product varchar(20), @Qty int, @lotQty int, @oldProduct varchar(20)

declare table_cursor cursor local fast_forward for    
    select
       ItemSize, Product, Qty, lotQty
    from Custorders
    order by Product, Itemsize

open table_cursor
while 1 = 1
begin
    if @RemQty <= 0
    begin
        fetch table_cursor into @ItemSize, @Product, @Qty, @lotQty
        if @@fetch_status <> 0 break

        if @oldProduct <> @Product or @oldProduct is null
        begin
            select @oldProduct = @Product
            select @RemQty = 0
        end
    end

    if @RemQty < 0 and abs(@RemQty) < @lotQty
        select @curLotQty = abs(@RemQty)
    else
        select @curLotQty = @lotQty

    if @RemQty > 0 and @RemQty < @Qty
        select @curQty = @RemQty
    else
        select @curQty = @Qty

    select @RemQty = @curQty - @curLotQty

    insert into @Lot 
    select @ItemSize, @Product, case when @curQty > @curLotQty then @curLotQty else @curQty end
end
close table_cursor
deallocate table_cursor

select * from @Lot order by Product, Itemsize

sql fiddle demo

【讨论】:

产品“Puma Shoe”的一个小问题 [Lot No..] 从 1000 开始 非常感谢您的解决方案。它按照我的要求工作 为什么?你不喜欢这个吗? 能否提供基于光标的解决方案? @SusannaFloora 我会在大约三个小时后提供,现在没时间

以上是关于根据条件将单个数据行拆分为多个数据行的 SQL 脚本的主要内容,如果未能解决你的问题,请参考以下文章

如何将数据集拆分为两个具有唯一和重复行的数据集?

如何根据 Oracle SQL 中的某些条件将列拆分为 2?

Pandas:按行数将数据帧拆分为多个数据帧

使用 WHERE IN SQL 子句将字符串值从单个值拆分为多个值以获取数据

在拆分为多个文件的大型数据框中查找重复行和包含重复行的文件

如何阻止查询结果在列之间拆分单个行的数据?