Pergunta

Eu tenho um banco de dados que possui uma tabela de pedidos e uma tabela de estoque.

A tabela de itens do pedido tem um layout de 1 registro por 1 quantidade, portanto, se uma pessoa fizer um pedido de 7 'ABC's e 4 'XYZ's, recebo 11 registros na tabela.

id, item, qtyNum
01  ABC   1 
02  ABC   2 
03  ABC   3 
04  ABC   4 
05  ABC   5
06  ABC   6
07  ABC   7
08  XYZ   1
09  XYZ   2
10  XYZ   3
11  XYZ   4

A tabela de estoque tem um layout de quantidade/item por local, então posso ter 20 itens em estoque, mas pode estar (na pior das hipóteses) em 20 locais separados.Então, para nosso exemplo, posso ter o seguinte inventário:

Qty, Item,             Loc,          Date
3    'ABC' in Location L1  with date 1990
2    'ABC' in Location L2  with date 1992
5    'ABC' in Location L3  with date 2003
4    'ABC' in Location LH  with date 2004
1    'XYZ' in Location L4  with date 1990
2    'XYZ' in Location L5  with date 1993
9    'XYZ' in Location L6  with date 2001
2    'XYZ' in Location LJ  with date 2004

*O H e o J não têm significado especial!Apenas deixando claro que eles são os mais novos

O conjunto de resultados deve extrair o máximo possível dos locais mais antigos primeiro, então, para este exemplo, acabo com a seguinte 'fila de seleção':

Pick 3 'ABC' from L1
Pick 2 'ABC' from L2
Pick 2 'ABC' from L3
Pick 1 'XYZ' from L4
Pick 2 'XYZ' from L5
Pick 1 'XYZ' from L6

Eu tenho uma solução que envolve muitas visualizações que são unidas várias vezes com junções externas e coisas malucas como essa e estou curioso para saber se existe uma solução simples/elegante para esse problema?Eu poderia fazer isso em código sem problemas, mas em SQL não sou guru.
SQL 2008

Foi útil?

Solução

Uau, essa foi difícil para mim;Tenho certeza de que existem soluções mais elegantes do que essa, mas foi isso que descobri:

--test data
DECLARE @orders TABLE
    (
      ID INT IDENTITY(1, 1) ,
      item CHAR(3) ,
      Qty INT
    )
INSERT  INTO @orders
        ( item, Qty )
VALUES  ( 'abc', 1 ),
        ( 'abc', 2 ),
        ( 'abc', 3 ),
        ( 'abc', 4 ),
        ( 'abc', 5 ),
        ( 'abc', 6 ),
        ( 'abc', 7 ),
        ( 'xyz', 1 ),
        ( 'xyz', 2 ),
        ( 'xyz', 3 ),
        ( 'xyz', 4 )

DECLARE @ItemLoc TABLE
    (
      Qty INT ,
      ITEM CHAR(3) ,
      Loc CHAR(2) ,
      Dt INT
    )
INSERT  INTO @ItemLoc
        ( Qty, ITEM, Loc, Dt )
VALUES  ( 3, 'abc', 'L1', 1990 ),
        ( 2, 'abc', 'L2', 1992 ),
        ( 5, 'abc', 'L3', 2003 ),
        ( 4, 'abc', 'LH', 2004 ),
        ( 1, 'xyz', 'L4', 1990 ),
        ( 2, 'xyz', 'L5', 1993 ),
        ( 9, 'xyz', 'L6', 2001 ),
        ( 2, 'xyz', 'LJ', 2004 ) ;


/*looks complicated, and it is
    I use a cte to try to ease it up a bit,
    but I first identify a running sum of items 
    in the bins, and a pull order based on item 
    and year.
*/  


WITH    cte
          AS ( SELECT   a.Qty ,
                        a.Item ,
                        a.Loc ,
                        a.Dt ,
                        a.RunningSum ,
                        a.PullOrder ,
                        b.Qty AS OrderQty
               FROM     ( SELECT    Qty ,
                                    Item ,
                                    Loc ,
                                    Dt ,
                                    RunningSum = ( SELECT   SUM(Qty)
                                                   FROM     @ItemLoc il1
                                                   WHERE    il1.Item = il.Item
                                                            AND il1.Dt <= il.Dt
                                                 ) ,
                                    PullOrder = ROW_NUMBER() OVER ( PARTITION BY Item ORDER BY Dt )
                          FROM      @ItemLoc il
                        ) a
                        JOIN ( SELECT   item ,
                                        MAX(qty) AS qty
                               FROM     @orders o
                               GROUP BY item
                             ) b ON a.Item = b.item
             )
    /* I then use the cte to a) identify the minimum bin
       which has a RunningSum of items greater than the OrderQty,
       and b) pick all of the items in the bins below that, and 
       c) pick the remaining items from the last bin
   */         


    SELECT  Pick = CASE WHEN RunningSum <= OrderQty THEN Qty
                        ELSE OrderQty - ( SELECT    SUM(Qty)
                                          FROM      cte c3
                                          WHERE     c3.item = c1.ITem
                                                    AND c3.RunningSum < c1.RunningSum
                                        )
                   END ,
            c1.Item ,
            Loc
    FROM    cte c1
            JOIN ( SELECT   Item ,
                            MIN(PullOrder) AS po
                   FROM     cte c2
                   WHERE    RunningSum >= OrderQty
                   GROUP BY Item
                 ) x ON c1.Item = x.Item
                        AND c1.PullOrder <= x.po

Outras dicas

Depois de revisitar esse problema, decidi que seria muito mais eficiente criar uma função com valor de tabela e

A consulta como está agora passou de 1:45 para 0:03.Incrível.

Infelizmente não consigo postar o código, mas o pseudocódigo geral para a solução é:

Crie uma variável de tabela para conter todos os locais de seleção disponíveis que podem ser vinculados de alguma forma a um pedido em aberto.

Crie uma segunda variável de tabela para conter todos os pedidos em aberto.Inclua todas as colunas necessárias para os status dos itens individuais em cada pedido.

Crie a tabela de resultados (ou faça isso primeiro, se estiver usando uma função com valor de tabela) que contém as informações necessárias para o seu processo de seleção.(Portanto, pedido #, item # e de qual local você deseja que ele seja retirado.)

Iterar:

do número de registros em pedidos em aberto até 1 da tabela de pedidos em aberto, juntando onde o local possui quantidade > 0.Armazene cada passagem na tabela de resultados.

Reduza em 1 a quantidade do local que você acabou de inserir na tabela de resultados, se você tiver um local.(Às vezes, um pedido pode não ser escolhido por causa de questões de quantidade ou orderstatus, mas você ainda os deseja nos resultados para fins de relatórios ou alocação.): End Iteate

Agradeço a ajuda Stuart Ainsworth, só queria evitar subconsultas e outras coisas.Consegui escrever isso sem fazer junções às mesmas tabelas mais de uma vez e sem subconsultas.Acertei o seu porque é incrível!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top