Guida di query SQL per le quantità di coda
-
06-07-2019 - |
Domanda
Ho un database che ha una tabella degli ordini e una tabella di inventario.
La tabella degli articoli dell'ordine ha un record 1 per 1 qtà layout, quindi se una persona effettua un ordine per 7 'ABC e 4' XYZ, ottengo 11 record nella tabella.
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 tabella di inventario ha un layout quantità / articolo per posizione, quindi posso avere 20 di qualcosa in magazzino, ma può essere (nel peggiore dei casi) in 20 posizioni separate. Quindi, per il nostro esempio, potrei avere il seguente 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 e la J non hanno alcun significato speciale! Sto solo portando il punto a casa che sono i più recenti
Il set di risultati dovrebbe prima prelevare il maggior numero possibile dalle posizioni più vecchie, quindi per questo esempio finisco con la seguente 'pick Queue':
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
Ho una soluzione che coinvolge molte visualizzazioni che vengono unite più volte con join esterni e cose folli come quella e sono solo curioso di sapere se esiste una soluzione semplice / elegante per questo problema? Potrei farlo nel codice senza problemi, ma in SQL non sono un guru.
MSSQL 2008
Soluzione
Accidenti, questo è stato difficile per me; Sono sicuro che ci sono soluzioni più eleganti di questa, ma questo è quello che mi è venuto in mente:
--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
Altri suggerimenti
Dopo aver rivisitato questo problema, ho deciso che sarebbe stato molto più efficiente creare una funzione con valori di tabella e
La query così com'è ora è passata dalle 1:45 alle 0:03. Impressionante.
Sfortunatamente non posso pubblicare il codice, ma lo pseudo codice generale per la soluzione è:
Crea una variabile di tabella per contenere tutte le posizioni di prelievo disponibili che possono essere in qualche modo legate a un ordine aperto.
Crea una seconda variabile di tabella per contenere tutti gli ordini aperti. Includi tutte le colonne necessarie per gli stati dei singoli articoli in ciascun ordine.
Crea la tabella dei risultati (o esegui prima questa operazione, se stai utilizzando una funzione con valori di tabella) che contiene le informazioni necessarie per il processo di selezione. (Quindi ordina #, oggetto # e da quale posizione # desideri estrarre.)
Iterate:
dal numero di record negli ordini aperti a 1 della tabella degli ordini aperti, unendo dove la posizione ha qty > 0. Memorizzare ciascun passaggio nella tabella dei risultati.
Riduci di 1 la quantità della posizione che hai appena inserito nella tabella dei risultati, se avevi una posizione. (a volte un ordine potrebbe non essere selezionabile a causa di problemi relativi alla quantità o allo stato degli ordini, ma li desideri comunque nei risultati a scopo di reportistica o allocazione.) : Termina Iterate
Apprezzo l'aiuto Stuart Ainsworth, volevo solo evitare domande secondarie e cose del genere. Sono riuscito a scrivere questo senza fare alcun join alle stesse tabelle più di una volta e senza sub query. Incontrato il tuo perché è fantastico!