Aide sur les requêtes SQL pour les quantités en file d'attente
-
06-07-2019 - |
Question
J'ai une base de données contenant une table de commandes et une table d'inventaire.
La table des articles de commande a une disposition de 1 enregistrement pour 1 quantité, donc si une personne passe une commande pour 7 'ABC et 4' XYZ, je reçois 11 enregistrements dans la table.
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 table d’inventaire a une disposition quantité / article par emplacement. Par conséquent, je peux avoir 20 articles en stock, mais ce peut être (dans le pire des cas) dans 20 emplacements différents. Donc, pour notre exemple, je pourrais avoir l'inventaire suivant:
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
* Les H et J n’ont aucune signification particulière! Je crois qu’ils sont les plus récents
Le jeu de résultats doit d'abord extraire le plus grand nombre possible d'anciens emplacements. Par conséquent, pour cet exemple, je termine avec la "file d'attente de sélection" suivante:
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
J'ai une solution qui implique un grand nombre de points de vue auxquels plusieurs jointures externes et des choses aussi folles sont associées, et je suis simplement curieux de savoir s'il existe une solution simple / élégante à ce problème? Je pourrais le faire en code sans problème, mais en SQL je ne suis pas un gourou.
MSSQL 2008
La solution
Ouf, c'était dur pour moi; Je suis sûr qu'il existe des solutions plus élégantes que cela, mais voici ce que j'ai proposé:
--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
Autres conseils
Après avoir revisité ce problème, j'ai décidé qu'il serait bien plus efficace de créer une fonction table et
La requête actuelle est passée de 1:45 à 0:03. Génial.
Malheureusement, je ne peux pas publier le code, mais le pseudo-code général de la solution est:
Créez une variable de table contenant tous les emplacements de choix disponibles pouvant être liés de quelque manière que ce soit à un ordre ouvert.
Créez une deuxième variable de table pour contenir toutes les commandes en cours. Indiquez les colonnes dont vous avez besoin pour connaître le statut des articles individuels dans chaque commande.
Créez la table de résultats (ou faites-la d'abord si vous utilisez une fonction table-value) qui contient les informations nécessaires à votre processus de prélèvement. (Ainsi, le numéro de commande, le numéro d'article et le numéro d'emplacement à partir duquel vous voulez qu'il soit extrait.)
Itérer:
du nombre d'enregistrements dans les commandes ouvertes à 1 de la table des commandes en cours, rejoindre où l'emplacement a la quantité > 0. Enregistrez chaque passe dans le tableau des résultats.
Réduisez la quantité d'emplacement que vous venez d'insérer dans le tableau des résultats de 1, si vous en aviez un. (Parfois, une commande peut ne pas être sélectionnable en raison de problèmes de quantité ou de statut, mais vous souhaitez néanmoins les inclure dans les résultats à des fins de rapport ou d'attribution.) : Mettre fin à une itération
J'apprécie l'aide de Stuart Ainsworth, je voulais simplement éviter les questions de sous-traitance. J'ai réussi à écrire ceci sans faire aucune jointure aux mêmes tables plus d'une fois et sans sous-requêtes. Vous avez perdu votre verre parce que c’est génial!