Ajuda de consulta SQL para quantidades de filas
-
06-07-2019 - |
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
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!