Ayuda de consulta SQL para cantidades de cola
-
06-07-2019 - |
Pregunta
Tengo una base de datos que tiene una tabla de pedidos y una tabla de inventario.
La tabla de artículos de pedido tiene un registro de 1 registro por 1 cantidad, por lo que si una persona hace un pedido para 7 'ABC y 4' XYZ, obtengo 11 registros en la tabla.
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
La tabla de inventario tiene un diseño de cantidad / artículo por ubicación, por lo que puedo tener 20 de algo en stock, pero puede estar (en el peor de los casos) en 20 ubicaciones diferentes. Entonces, para nuestro ejemplo, podría tener el siguiente inventario:
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
* ¡La H y J no tienen un significado especial! Solo estoy convencido de que son los más nuevos
El conjunto de resultados debería extraer la mayor cantidad posible de las ubicaciones más antiguas primero, así que para este ejemplo termino con la siguiente 'cola de selección':
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
Tengo una solución que involucra muchas vistas que se unen varias veces con uniones externas y cosas locas como esa y tengo curiosidad si existe una solución simple / elegante para este problema. Podría hacerlo en código, no hay problema, pero en SQL no soy un gurú.
MSSQL 2008
Solución
¡Menos mal, esto fue difícil para mí; Estoy seguro de que hay soluciones más elegantes que esto, pero esto es lo que se me ocurrió:
--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
Otros consejos
Después de revisar este problema, decidí que sería mucho más eficiente crear una función con valores de tabla y
La consulta tal como está ahora pasó de 1:45 a 0:03. Impresionante.
Lamentablemente no puedo publicar el código, pero el pseudo código general para la solución es:
Cree una variable de tabla que contenga todas las ubicaciones de selección disponibles que pueden estar vinculadas de alguna manera a una orden abierta.
Crea una segunda variable de tabla para contener todas las órdenes abiertas. Incluya las columnas que necesite para los estados de los artículos individuales en cada orden.
Cree la tabla de resultados (o haga esto primero, si está utilizando una función con valores de tabla) que contenga la información necesaria para su proceso de selección. (Por lo tanto, el número de pedido, el número de artículo y el número de ubicación desde el que desea obtenerla).
Iterar:
del número de registros en órdenes abiertas a 1 de la tabla de órdenes abiertas, uniéndose donde la ubicación tiene qty > 0. Almacene cada pase en la tabla de resultados.
Reduzca la cantidad de la ubicación que acaba de insertar en la tabla de resultados en 1, si tenía una ubicación. (A veces, un pedido puede no ser seleccionable debido a problemas de cantidad o estado del pedido, pero aún así lo desea en los resultados para fines de informe o asignación). : Fin Iterar
Aprecio la ayuda Stuart Ainsworth, solo quería evitar las consultas secundarias y esas cosas. Logré escribir esto sin hacer ninguna combinación en las mismas tablas más de una vez y sin subconsultas. ¡Golpeó el tuyo porque es increíble!