简体   繁体   English

Sql 根据多列的固定值将行拆分为多行

[英]Sql to split row to multiple rows based on a fixed value of multiple columns

create table qty_split 
(
    order varchar2(4), 
    article_code varchar2(4), 
    size_1 number(3,0), 
    size_2 number(3,0), 
    size_3 number(3,0)
); 

insert into qty_split values ('a001', '1111', 123, 165, 85);

commit;


select * from qty_split;

'a001'  '1111'  123  165  85

Now I need an Oracle SQL query to split the above row based on upper limit of quantity 99 on columns size_1, size_2 and size_3.现在我需要一个 Oracle SQL 查询来根据列 size_1、size_2 和 size_3 上数量 99 的上限拆分上述行。

So expected result would be:所以预期的结果是:

'a001'  '1111'  99  99  85
'a001'  '1111'   24  66  0

You can use a recursive sub-query factoring clause to split the data for any amount:您可以使用递归子查询因式分解子句将数据拆分为任意数量:

WITH data ( "ORDER", article, size_1, size_2, size_3 ) AS (
  SELECT * FROM qty_split
UNION ALL
  SELECT "ORDER",
         article,
         GREATEST( size_1 - 99, 0 ),
         GREATEST( size_2 - 99, 0 ),
         GREATEST( size_3 - 99, 0 )
  FROM   data
  WHERE  size_1 > 99
  OR     size_2 > 99
  OR     size_3 > 99
)
SELECT "ORDER",
       article,
       LEAST( size_1, 99 ) AS size_1,
       LEAST( size_2, 99 ) AS size_2,
       LEAST( size_3, 99 ) AS size_3
FROM   data
ORDER BY
       "ORDER",
       article,
       ROWNUM;

Which, for the sample data:其中,对于样本数据:

create table qty_split (
  "ORDER" varchar2(4),
  article_code varchar2(4), 
  size_1 number(3,0),
  size_2 number(3,0),
  size_3 number(3,0)
);

insert into qty_split 
SELECT 'a001', '1111', 123, 165, 85 FROM DUAL UNION ALL
SELECT 'b002', '2222', 312, 45, 17 FROM DUAL UNION ALL
SELECT 'c003', '3333', 0, 0, 417 FROM DUAL;

Outputs:输出:

 ORDER |订购 | ARTICLE |文章 | SIZE_1 | SIZE_1 | SIZE_2 |尺寸_2 | SIZE_3:---- |:------ | SIZE_3:---- |:------ | -----: | -----: | -----: | -----: | -----: a001 | -----: a001 | 1111 | 1111 | 99 | 99 | 99 | 99 | 85 a001 | 85 a001 | 1111 | 1111 | 24 | 24 | 66 | 66 | 0 b002 | 0 b002 | 2222 |第2222章99 | 99 | 45 | 45 | 17 b002 | 17 b002 | 2222 |第2222章99 | 99 | 0 | 0 | 0 b002 | 0 b002 | 2222 |第2222章99 | 99 | 0 | 0 | 0 b002 | 0 b002 | 2222 |第2222章15 | 15 | 0 | 0 | 0 c003 | 0 c003 | 3333 |第3333章0 | 0 | 0 | 0 | 99 c003 | 99 c003 | 3333 |第3333章0 | 0 | 0 | 0 | 99 c003 | 99 c003 | 3333 |第3333章0 | 0 | 0 | 0 | 99 c003 | 99 c003 | 3333 |第3333章0 | 0 | 0 | 0 | 99 c003 | 99 c003 | 3333 |第3333章0 | 0 | 0 | 0 | 21 21

db<>fiddle here db<> 在这里摆弄

You can solve this problem with a CONNECT BY query, which may prove faster*** than a recursive query (recursive WITH clause).您可以使用 CONNECT BY 查询来解决此问题,这可能比递归查询(递归 WITH 子句)更快***。

In the query below I assume you must do this for one row at a time.在下面的查询中,我假设您必须一次为一行执行此操作。 If you have a table with many rows that must be processed similarly at the same time, that can be arranged easily.如果您有一张表,其中包含许多必须同时进行类似处理的行,则可以轻松安排。 The most efficient way to do that depends on your Oracle version;最有效的方法取决于您的 Oracle 版本; in Oracle 12.1 or higher that can be done very efficiently with the LATERAL or CROSS APPLY clause, but there is also a different technique that works in all older Oracle versions.在 Oracle 12.1 或更高版本中,可以使用 LATERAL 或 CROSS APPLY 子句非常有效地完成,但还有一种不同的技术适用于所有较旧的 Oracle 版本。

*** EDIT - just out of curiosity, I set up a table with 300,000 rows (combinations of ord and article_code ) and values up to 930 in the three "size" columns, resulting in an output of 3 million rows (ten output rows for each input row). ***编辑- 只是出于好奇,我设置了一个表,其中包含 300,000 行( ordarticle_code的组合),三个“大小”列中的值高达 930,导致 output 为 300 万行(十行 Z78E6221F6393D1356681DB398F14CED对于每个输入行)。 The query using the recursive WITH clause runs in 43 seconds on my machine.使用递归 WITH 子句的查询在我的机器上运行了 43 秒。 The CONNECT BY solution runs in 11 seconds. CONNECT BY 解决方案在 11 秒内运行。 END EDIT结束编辑

So, here's the CONNECT BY query (for a single row):因此,这是 CONNECT BY 查询(针对单行):

select  ord, article_code, level as tranche,
        case when level < ceil(size_1/99) then 99
             when level > ceil(size_1/99) then  0
             else mod(size_1 - 1, 99) + 1
        end  as size_1_chunk,
        case when level < ceil(size_2/99) then 99
             when level > ceil(size_2/99) then  0
             else mod(size_2- 1, 99) + 1
        end  as size_2_chunk,
        case when level < ceil(size_3/99) then 99
             when level > ceil(size_3/99) then  0
             else mod(size_3 - 1, 99) + 1
        end  as size_3_chunk
from    qty_split
connect by level <= ceil(greatest(size_1, size_2, size_3)/99)
order   by ord, article_code, tranche
;

ORD  ARTICLE_CODE TRANCHE SIZE_1_CHUNK SIZE_2_CHUNK SIZE_3_CHUNK
---- ------------ ------- ------------ ------------ ------------
a001 1111               1           99           99           85
a001 1111               2           24           66            0

Note a few changes I made: I changed order to ord (since order is a reserved keyword, which will not work as a column name);请注意我所做的一些更改:我将order更改为ord (因为order是保留关键字,不能用作列名); I changed the column names in the output to size_1_chunk etc., and I added a column tranche to keep track of each tranche or slice in the process;我将 output 中的列名更改为size_1_chunk等,并添加了一个列tranche以跟踪过程中的每个 tranche 或 slice; not sure if you will need to refer to this column in further processing.不确定您是否需要在进一步处理中参考此列。

Using CASE and UNION使用 CASE 和 UNION
Note that I changed name of column order to ordern because order is a reserved word.请注意,我将列order的名称更改为ordern ,因为order是保留字。

select ordern, article_code,
       case when size_1 > 99 then 99 else size_1 end as size_1,
       case when size_2 > 99 then 99 else size_2 end as size_2,
       case when size_3 > 99 then 99 else size_3 end as size_3
  from qty_split
union
select ordern, article_code,
       case when size_1 > 99 then (size_1 - 99) else 0 end as size_1,
       case when size_2 > 99 then (size_2 - 99) else 0 end as size_2,
       case when size_3 > 99 then (size_3 - 99) else 0 end as size_3
  from qty_split

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM