Please Help Improve My While Loop

  • I'm sure this is by far not the best way to do things, buts its been the only way I've worked out so far.

    I have 2 tables - 1 with all the orders to manufacture products and 1 with all the orders from customers. Now based on some logic I need to allocate the orders to manufactured products to customer orders.

    There are just under 1,000,000 rows I'm having to deal with.

    #TempTable1 = Lets say has the manufacture products

    #TempTable2 = Customer Orders lines

    #tempTable3 = Subset of @temptable2 based on #temptable1 when looping through.

    Their is more definition stuff I haven't included, but here is my double look query. It takes about 50 min to run currently (down from 4 hours of my original attempt).

    Any help would be greatly appreciated.

    Thanks in advance.

    SET @qty = 0

    SET @childQty = 0

    DECLARE @TopRow as INT

    SET @TopRow = 1

    DECLARE @count AS INT , @count3 AS INT

    SELECT @count = COUNT(*) from #TmpTable

    WHILE @count>0 --EXISTS (SELECT * FROM #TmpTable)

    BEGIN

    SELECT TOP (@TopRow) @ID=[ID],@Type=[Type],@OrderID=[OrderID],@UniqueOrder=[Unique Order],@Product=[Product],@Qty=[Qty], @TotalQty = Qty,

    @CustomerWH=[Customer WH],@DespatchWH=[Despatch WH],@DeliveryDate=[Delivery Date]

    FROM #TmpTable ORDER BY [Product],[Customer WH],[Despatch WH],[Priority]

    TRUNCATE TABLE #TmpTable3

    INSERT INTO #TmpTable3

    SELECT * FROM #TmpTable2 WHERE NOT [ID]=@ID AND [Product]=@Product AND [Despatch WH]=@CustomerWH

    SELECT @count3 = COUNT(*) FROM #TmpTable3

    WHILE @count3 > 0 --EXISTS (SELECT * FROM #TmpTable3)

    BEGIN

    SELECT TOP (@TopRow) @ChildID=[ID],@ChildOrderID=[OrderID],@ChildUniqueOrder=[Unique Order],@ChildProduct=[Product]

    ,@ChildQty=CASE WHEN @childQty = 0 THEN [Qty] ELSE @ChildQty END,

    @TotalChildQty = Qty,

    @ChildCustomerWH=[Customer WH],@ChildDespatchWH=[Despatch WH],@ChildDeliveryDate=[Delivery Date]

    FROM #TmpTable3 ORDER BY [Product],[Customer WH],[Despatch WH],[Priority]

    IF @ChildQty=@Qty

    BEGIN

    INSERT INTO [BKDataWarehouse].[dbo].[tbl_mc_Log_OrderLinking]

    ([Type],[OrderID],[Unique Order],[Product],[Qty],[AllocatedQty],[Customer WH],[Despatch WH],[Delivery Date]

    ,[Child Type],[Child OrderID],[Child Unique Order],[Child Product],[Child Qty],[Child AllocatedQty],[Child Customer WH],[Child Despatch WH],[Child Delivery Date])

    VALUES

    (@Type,@OrderID,@UniqueOrder,@Product,@TotalQty,@Qty,@CustomerWH,@DespatchWH,@DeliveryDate

    ,@ChildType,@ChildOrderID,@ChildUniqueOrder,@ChildProduct,@TotalChildQty,@ChildQty,@ChildCustomerWH,@ChildDespatchWH,@ChildDeliveryDate)

    --TRUNCATE TABLE #TmpTable3

    DELETE FROM #TmpTable2 WHERE ID = @ChildID

    --DELETE FROM #TmpTable WHERE ID = @ID

    SET @ChildQty = 0

    SET @Qty = 0

    SET @count3 = 0

    END

    ELSE

    BEGIN

    IF @ChildQty>@Qty

    BEGIN

    INSERT INTO [BKDataWarehouse].[dbo].[tbl_mc_Log_OrderLinking]

    ([Type],[OrderID],[Unique Order],[Product],[Qty],[AllocatedQty],[Customer WH],[Despatch WH],[Delivery Date]

    ,[Child Type],[Child OrderID],[Child Unique Order],[Child Product],[Child Qty],[Child AllocatedQty],[Child Customer WH],[Child Despatch WH],[Child Delivery Date])

    VALUES

    (@Type,@OrderID,@UniqueOrder,@Product,@TotalQty,@Qty,@CustomerWH,@DespatchWH,@DeliveryDate

    ,@ChildType,@ChildOrderID,@ChildUniqueOrder,@ChildProduct,@TotalChildQty,@Qty,@ChildCustomerWH,@ChildDespatchWH,@ChildDeliveryDate)

    --TRUNCATE TABLE #TmpTable3

    --DELETE FROM #TmpTable WHERE ID = @ID

    SET @ChildQty = @ChildQty - @Qty

    SET @Qty = 0

    SET @count3= 0

    END

    ELSE

    BEGIN

    INSERT INTO [BKDataWarehouse].[dbo].[tbl_mc_Log_OrderLinking]

    ([Type],[OrderID],[Unique Order],[Product],[Qty],[AllocatedQty],[Customer WH],[Despatch WH],[Delivery Date]

    ,[Child Type],[Child OrderID],[Child Unique Order],[Child Product],[Child Qty],[Child AllocatedQty],[Child Customer WH],[Child Despatch WH],[Child Delivery Date])

    VALUES

    (@Type,@OrderID,@UniqueOrder,@Product,@TotalQty,@ChildQty,@CustomerWH,@DespatchWH,@DeliveryDate

    ,@ChildType,@ChildOrderID,@ChildUniqueOrder,@ChildProduct,@TotalChildQty,@ChildQty,@ChildCustomerWH,@ChildDespatchWH,@ChildDeliveryDate)

    DELETE FROM #TmpTable3 WHERE ID = @ChildID

    DELETE FROM #TmpTable2 WHERE ID = @ChildID

    SET @Qty = @Qty - @ChildQty

    SET @ChildQty = 0

    SET @count3 = @count3-1

    END

    END

    END

    DELETE FROM #TmpTable WHERE ID = @ID

    SET @count = @count-1

    END

  • Quick thought, this code looks very inefficient, while the logic is rather simple, should be straight forward to construct a set based code for the operation. Could you post DDL and sample data necessary to run this code?

    ๐Ÿ˜Ž

  • Not sure if I understand the problem 100% but perhaps you can join the first two tables using a FULL OUTER JOIN?

    You would then see all products that match their respective orders, and you would see any products or orders that do not have a match (NULL)

    SELECT column_names_from_both_tables

    FROM table1

    FULL OUTER JOIN

    table2

    ON table1.column_id1 = table2.column_id1

    AND table1.column_id2 = table2.column_id2

    AND table1.column_id3 = table2.column_id3

    You can get further info on this blog which discusses nested loops

  • Byzza (2/3/2015)


    I'm sure this is by far not the best way to do things, buts its been the only way I've worked out so far.

    I have 2 tables - 1 with all the orders to manufacture products and 1 with all the orders from customers. Now based on some logic I need to allocate the orders to manufactured products to customer orders.

    There are just under 1,000,000 rows I'm having to deal with.

    #TempTable1 = Lets say has the manufacture products

    #TempTable2 = Customer Orders lines

    #tempTable3 = Subset of @temptable2 based on #temptable1 when looping through.

    Their is more definition stuff I haven't included, but here is my double look query. It takes about 50 min to run currently (down from 4 hours of my original attempt).

    Any help would be greatly appreciated.

    Thanks in advance.

    SET @qty = 0

    SET @childQty = 0

    DECLARE @TopRow as INT

    SET @TopRow = 1

    DECLARE @count AS INT , @count3 AS INT

    SELECT @count = COUNT(*) from #TmpTable

    WHILE @count>0 --EXISTS (SELECT * FROM #TmpTable)

    BEGIN

    SELECT TOP (@TopRow) @ID=[ID],@Type=[Type],@OrderID=[OrderID],@UniqueOrder=[Unique Order],@Product=[Product],@Qty=[Qty], @TotalQty = Qty,

    @CustomerWH=[Customer WH],@DespatchWH=[Despatch WH],@DeliveryDate=[Delivery Date]

    FROM #TmpTable ORDER BY [Product],[Customer WH],[Despatch WH],[Priority]

    TRUNCATE TABLE #TmpTable3

    INSERT INTO #TmpTable3

    SELECT * FROM #TmpTable2 WHERE NOT [ID]=@ID AND [Product]=@Product AND [Despatch WH]=@CustomerWH

    SELECT @count3 = COUNT(*) FROM #TmpTable3

    WHILE @count3 > 0 --EXISTS (SELECT * FROM #TmpTable3)

    BEGIN

    SELECT TOP (@TopRow) @ChildID=[ID],@ChildOrderID=[OrderID],@ChildUniqueOrder=[Unique Order],@ChildProduct=[Product]

    ,@ChildQty=CASE WHEN @childQty = 0 THEN [Qty] ELSE @ChildQty END,

    @TotalChildQty = Qty,

    @ChildCustomerWH=[Customer WH],@ChildDespatchWH=[Despatch WH],@ChildDeliveryDate=[Delivery Date]

    FROM #TmpTable3 ORDER BY [Product],[Customer WH],[Despatch WH],[Priority]

    IF @ChildQty=@Qty

    BEGIN

    INSERT INTO [BKDataWarehouse].[dbo].[tbl_mc_Log_OrderLinking]

    ([Type],[OrderID],[Unique Order],[Product],[Qty],[AllocatedQty],[Customer WH],[Despatch WH],[Delivery Date]

    ,[Child Type],[Child OrderID],[Child Unique Order],[Child Product],[Child Qty],[Child AllocatedQty],[Child Customer WH],[Child Despatch WH],[Child Delivery Date])

    VALUES

    (@Type,@OrderID,@UniqueOrder,@Product,@TotalQty,@Qty,@CustomerWH,@DespatchWH,@DeliveryDate

    ,@ChildType,@ChildOrderID,@ChildUniqueOrder,@ChildProduct,@TotalChildQty,@ChildQty,@ChildCustomerWH,@ChildDespatchWH,@ChildDeliveryDate)

    --TRUNCATE TABLE #TmpTable3

    DELETE FROM #TmpTable2 WHERE ID = @ChildID

    --DELETE FROM #TmpTable WHERE ID = @ID

    SET @ChildQty = 0

    SET @Qty = 0

    SET @count3 = 0

    END

    ELSE

    BEGIN

    IF @ChildQty>@Qty

    BEGIN

    INSERT INTO [BKDataWarehouse].[dbo].[tbl_mc_Log_OrderLinking]

    ([Type],[OrderID],[Unique Order],[Product],[Qty],[AllocatedQty],[Customer WH],[Despatch WH],[Delivery Date]

    ,[Child Type],[Child OrderID],[Child Unique Order],[Child Product],[Child Qty],[Child AllocatedQty],[Child Customer WH],[Child Despatch WH],[Child Delivery Date])

    VALUES

    (@Type,@OrderID,@UniqueOrder,@Product,@TotalQty,@Qty,@CustomerWH,@DespatchWH,@DeliveryDate

    ,@ChildType,@ChildOrderID,@ChildUniqueOrder,@ChildProduct,@TotalChildQty,@Qty,@ChildCustomerWH,@ChildDespatchWH,@ChildDeliveryDate)

    --TRUNCATE TABLE #TmpTable3

    --DELETE FROM #TmpTable WHERE ID = @ID

    SET @ChildQty = @ChildQty - @Qty

    SET @Qty = 0

    SET @count3= 0

    END

    ELSE

    BEGIN

    INSERT INTO [BKDataWarehouse].[dbo].[tbl_mc_Log_OrderLinking]

    ([Type],[OrderID],[Unique Order],[Product],[Qty],[AllocatedQty],[Customer WH],[Despatch WH],[Delivery Date]

    ,[Child Type],[Child OrderID],[Child Unique Order],[Child Product],[Child Qty],[Child AllocatedQty],[Child Customer WH],[Child Despatch WH],[Child Delivery Date])

    VALUES

    (@Type,@OrderID,@UniqueOrder,@Product,@TotalQty,@ChildQty,@CustomerWH,@DespatchWH,@DeliveryDate

    ,@ChildType,@ChildOrderID,@ChildUniqueOrder,@ChildProduct,@TotalChildQty,@ChildQty,@ChildCustomerWH,@ChildDespatchWH,@ChildDeliveryDate)

    DELETE FROM #TmpTable3 WHERE ID = @ChildID

    DELETE FROM #TmpTable2 WHERE ID = @ChildID

    SET @Qty = @Qty - @ChildQty

    SET @ChildQty = 0

    SET @count3 = @count3-1

    END

    END

    END

    DELETE FROM #TmpTable WHERE ID = @ID

    SET @count = @count-1

    END

    Can you confirm that this is a bin-packing problem?

    Next, folks will need scripts and sample data for both tables, preferably with a desired output set too. Read this [/url]if you're unsure what that entails.

    โ€œWrite the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.โ€ - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Hi All,

    Thanks for the responses so far. I can't really do any kind of joins, is because I need to be able to allocate manufacture order qty's to customer orders and they must be done in a very specific order.

    It kind of is a Bin Packing problem I suppose. Basically when ever a customer places an order for a product we raise a manufacturing order for that exact qty BUT the customer doesn't always get that allocation of products, but instead based on the customers priority they may end up with the very next qty off the line.

    Eg the last customer might order Qty -25, but because theirs is a rush order, they might get the next 25 off the manufacturing line, which could be a manufacturing order of 5, then 10, then the next 10 of a manufacturing order or 20.

    below is some sample data. I've simplified the tables to just the core columns.

    in the results W_qty_Used should equal c_Qty_Allocated. We have just been keeping both columns for testing

    CREATE Table #TmpTable(

    WID int,

    Product varchar(10),

    Qty int

    )

    CREATE Table #TmpTable2(

    CID int,

    Product varchar(10),

    Qty int

    )

    CREATE Table #results(

    Product varchar(10),

    WID int,

    W_Total_Qty int,

    W_Qty_Used int,

    CID int,

    C_Total_Qty int,

    C_Qty_Allocated int

    )

    INSERT INTO #TmpTable( WID, Product, Qty )

    SELECT 1000, 'ABC123', 50 UNION ALL

    SELECT 1001, 'ABC123', 5 UNION ALL

    SELECT 1002, 'ABC123', 12 UNION ALL

    SELECT 1003, 'ABC123', 50

    INSERT INTO #TmpTable2( CID, Product, Qty )

    SELECT 2000, 'ABC123', 5 UNION ALL

    SELECT 2001, 'ABC123', 10 UNION ALL

    SELECT 2002, 'ABC123', 40 UNION ALL

    SELECT 2003, 'ABC123', 62

    INSERT INTO #results ( Product ,WID ,W_Total_Qty ,W_Qty_Used ,CID ,C_Total_Qty ,C_Qty_Allocated)

    SELECT 'ABC123', 1000,50,5,2000,5,5 UNION ALL

    SELECT 'ABC123', 1000,50,10,2001,10,10 UNION ALL

    SELECT 'ABC123', 1000,50,35,2002,40,35 UNION ALL

    SELECT 'ABC123', 1001,5,5,2002,40,5 UNION ALL

    SELECT 'ABC123', 1002,12,12,2003,62,12 UNION ALL

    SELECT 'ABC123', 1003,50,50,2003,62,50

    SELECT * FROM #TmpTable

    SELECT * FROM #TmpTable2

    SELECT * FROM #results

    DROP TABLE #TmpTable

    DROP TABLE #TmpTable2

    DROP TABLE #results

  • Byzza (2/4/2015)


    Hi All,

    Thanks for the responses so far. I can't really do any kind of joins, is because I need to be able to allocate manufacture order qty's to customer orders and they must be done in a very specific order.

    It kind of is a Bin Packing problem I suppose. Basically when ever a customer places an order for a product we raise a manufacturing order for that exact qty BUT the customer doesn't always get that allocation of products, but instead based on the customers priority they may end up with the very next qty off the line.

    Eg the last customer might order Qty -25, but because theirs is a rush order, they might get the next 25 off the manufacturing line, which could be a manufacturing order of 5, then 10, then the next 10 of a manufacturing order or 20.

    below is some sample data. I've simplified the tables to just the core columns.

    in the results W_qty_Used should equal c_Qty_Allocated. We have just been keeping both columns for testing

    CREATE Table #TmpTable(

    WID int,

    Product varchar(10),

    Qty int

    )

    CREATE Table #TmpTable2(

    CID int,

    Product varchar(10),

    Qty int

    )

    CREATE Table #results(

    Product varchar(10),

    WID int,

    W_Total_Qty int,

    W_Qty_Used int,

    CID int,

    C_Total_Qty int,

    C_Qty_Allocated int

    )

    INSERT INTO #TmpTable( WID, Product, Qty )

    SELECT 1000, 'ABC123', 50 UNION ALL

    SELECT 1001, 'ABC123', 5 UNION ALL

    SELECT 1002, 'ABC123', 12 UNION ALL

    SELECT 1003, 'ABC123', 50

    INSERT INTO #TmpTable2( CID, Product, Qty )

    SELECT 2000, 'ABC123', 5 UNION ALL

    SELECT 2001, 'ABC123', 10 UNION ALL

    SELECT 2002, 'ABC123', 40 UNION ALL

    SELECT 2003, 'ABC123', 62

    INSERT INTO #results ( Product ,WID ,W_Total_Qty ,W_Qty_Used ,CID ,C_Total_Qty ,C_Qty_Allocated)

    SELECT 'ABC123', 1000,50,5,2000,5,5 UNION ALL

    SELECT 'ABC123', 1000,50,10,2001,10,10 UNION ALL

    SELECT 'ABC123', 1000,50,35,2002,40,35 UNION ALL

    SELECT 'ABC123', 1001,5,5,2002,40,5 UNION ALL

    SELECT 'ABC123', 1002,12,12,2003,62,12 UNION ALL

    SELECT 'ABC123', 1003,50,50,2003,62,50

    SELECT * FROM #TmpTable

    SELECT * FROM #TmpTable2

    SELECT * FROM #results

    DROP TABLE #TmpTable

    DROP TABLE #TmpTable2

    DROP TABLE #results

    You did a nice job of posting readily consumable data but I suspect the reason why no one has responded is that they can't figure out which table is for what. For example, we don't know what a WID or a CID is and the table names don't give a clue as to the type of data.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

Viewing 6 posts - 1 through 5 (of 5 total)

You must be logged in to reply to this topic. Login to reply