Home > Back-end >  SQL - how to combine sequential rows based on next row
SQL - how to combine sequential rows based on next row

Time:02-01

There is a table I need to join back to itself to created a purchased parts report. But, I keep getting duplicated rows.

How to create a sql that will combine rows based on value of the next row?

Order No and Order Line fields uniquely identify a row in a sales order.

CP_COMP_SEQ is simply a list of all the components needed to manufacture Sales Order and Order Line.

MFG_PURCH_FLG is a flag 'M' means it's a Make part. 'P' means it's a part we need to Purchase.

If an M row follows another M row, then the first component has no purchased parts. If one or more P row(s) follow an M row, then all those sequential rows need to be purchased. Meaning we need to add them to a Purchase Parts report for a buyer to fill out a Purchase Order.

So, in the image below, Row 1 needs two purchased parts to complete. Row 4 doesn't need any purchased parts (because it's followed by another M row). Row 5 needs three purchased parts. And row 9 doesn't need any purchased parts. Source table

order_no order_line_no cp_comp_seq inv_item mfg_purch_flg qty
1 2 1 146FV M 2
1 2 2 2085 P 4
1 2 3 2095 P 4
1 2 4 ZBAR007 M 1
1 2 5 1467V M 1
1 2 6 2085 P 2
1 2 7 2095 P 2
1 2 8 3060 P 1
1 2 9 ZBAR007 M 1
2 1 1 xxx x x
2 1 2 xxx x x

I would like to have the results be one row per item that needs to be purchased based on the previous row that is a 'M' make item. Below is what I would like as the result:Result table

order_no order_line_no cp_comp_seq inv_item purchase_item qty
1 2 1 146FV 2085 4
1 2 1 146FV 2095 4
1 2 5 147FV 2085 2
1 2 5 147FV 2095 2
1 2 5 147FV 3060 1

Any help would be greatly appreciated! Thanks in advance.

I tried joining based on rownumber - 1, but that doesn't stop when the Purchase flag changes. I tried looking at Run Groups but was unable to make that work as well.

Any help would be greatly appreciated! Thanks in advance.

CodePudding user response:

Take a look at the cross apply syntax. This syntax allows you to drive the inner query off each row in the other query. select * from AnyTableYouWant T1 cross apply ( select top 1 ID from AnyTableYouWant T2 where T1.ID = T2.ID order by T2.ID desc ) T3

T1 or T2 can be the same or different tables. As long as you come up with join criteria. A cross apply is different from a typical left, right or inner join. There's plenty of literature on these queries. This answer is not meant to be a diatribe on how it works.


After reading this question more closely, it would benefit from just walking through a result set sequentially and not trying to do it all in SQL. If you want to do it all in SQL, lookup up CURSORs. For maintenance reasons using python or C# or any other procedural language. Query all the records from the parts table make sure it's sorted the way you have it listed, then walk through the results one record at a time and apply the business logic you described.

CodePudding user response:

You could use analytic function to get the sequence number of M flag of every P flag:

Select  t.ORDER_NO, t.ORDER_LINE_NO, t.CP_COMP_SEQ, t.INV_ITEM, t.MFG_PURCH_FLG, t.QTY, 
        MAX(CASE WHEN t.MFG_PURCH_FLG = 'M' THEN t.CP_COMP_SEQ END) OVER(Order By t.CP_COMP_SEQ Rows Between Unbounded Preceding And Current Row) "M_SEQ"
From    tbl t

  ORDER_NO ORDER_LINE_NO CP_COMP_SEQ INV_ITEM MFG_PURCH_FLG QTY      M_SEQ
---------- ------------- ----------- -------- ------------- --- ----------
         1             2           1 146FV    M             2            1 
         1             2           2 2085     P             4            1 
         1             2           3 2095     P             4            1 
         1             2           4 ZBAR007  M             1            4 
         1             2           5 1467V    M             1            5 
         1             2           6 2085     P             2            5 
         1             2           7 2095     P             2            5 
         1             2           8 3060     P             1            5 
         1             2           9 ZBAR007  M             1            9 
         2             1          10 xxx      x             x            9 
         2             1          11 xxx      x             x            9

If you put it as a subquery in the FROM clause like here:

SELECT    t.ORDER_NO,   t.ORDER_LINE_NO, 
         (Select CP_COMP_SEQ From tbl Where CP_COMP_SEQ = t.M_SEQ) "CP_COMP_SEQ",
         (Select INV_ITEM From tbl Where CP_COMP_SEQ = t.M_SEQ) "INV_ITEM",
          t.INV_ITEM "PURCHASE_ITEM", t.QTY
FROM    ( Select    t.ORDER_NO, t.ORDER_LINE_NO, t.CP_COMP_SEQ, t.INV_ITEM, t.MFG_PURCH_FLG, t.QTY, 
                    MAX(CASE WHEN t.MFG_PURCH_FLG = 'M' THEN t.CP_COMP_SEQ END) OVER(Order By t.CP_COMP_SEQ Rows Between Unbounded Preceding And Current Row) "M_SEQ"
          From      tbl t
        ) t
WHERE   t.MFG_PURCH_FLG = 'P'
ORDER BY  t.CP_COMP_SEQ

... then, with your sample data ...

WITH
    tbl (ORDER_NO,  ORDER_LINE_NO,  CP_COMP_SEQ,    INV_ITEM,   MFG_PURCH_FLG,  QTY) AS
        (
            Select 1, 2,  1,    '146FV',      'M',  '2' From Dual Union All
            Select 1, 2,  2,    '2085',       'P',  '4' From Dual Union All
            Select 1, 2,  3,    '2095',       'P',  '4' From Dual Union All
            Select 1, 2,  4,    'ZBAR007',  'M',    '1' From Dual Union All
            Select 1, 2,  5,    '1467V',      'M',  '1' From Dual Union All
            Select 1, 2,  6,    '2085',       'P',  '2' From Dual Union All
            Select 1, 2,  7,    '2095',       'P',  '2' From Dual Union All
            Select 1, 2,  8,    '3060',       'P',  '1' From Dual Union All
            Select 1, 2,  9,    'ZBAR007',  'M',    '1' From Dual Union All
            Select 2, 1, 10,    'xxx',        'x',  'x' From Dual Union All
            Select 2, 1, 11,    'xxx',        'x',  'x' From Dual 
        )

... the result would be:

  ORDER_NO ORDER_LINE_NO CP_COMP_SEQ INV_ITEM PURCHASE_ITEM QTY
---------- ------------- ----------- -------- ------------- ---
         1             2           1 146FV    2085          4   
         1             2           1 146FV    2095          4   
         1             2           5 1467V    2085          2   
         1             2           5 1467V    2095          2   
         1             2           5 1467V    3060          1  
  • Related