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

Était-ce utile?

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!

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top