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

¿Fue útil?

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!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top