web-dev-qa-db-fra.com

Requêtes SQL pour afficher uniquement les enregistrements d'achat les plus récents pour des aliments individuels

Je travaille avec un système d'achat/de facturation d'aliments dans MS Access 2013 et j'essaie de créer une requête SQL qui retournera le prix d'achat le plus récent pour chaque aliment individuel.

Voici un schéma des tables avec lesquelles je travaille: Tables in MS Access database

Ma compréhension de SQL est très basique, et j'ai essayé la requête (incorrecte) suivante, dans l'espoir qu'elle ne renverrait qu'un seul enregistrement par élément (à cause de l'opérateur DISTINCT) et qu'elle ne renverrait que le plus achat récent (depuis que j'ai fait ORDER BY [Invoice Date] DESC)

SELECT DISTINCT ([Food items].Item), 
    [Food items].Item, [Food purchase data].[Price per unit], [Food purchase data].[Purchase unit], Invoices.[Invoice Date]
FROM Invoices
INNER JOIN ([Food items] 
    INNER JOIN [Food purchase data] 
    ON [Food items].ID = [Food purchase data].[Food item ID]) 
ON Invoices.ID = [Food purchase data].[Invoice ID]
ORDER BY Invoices.[Invoice Date] DESC;

Cependant, la requête ci-dessus renvoie simplement tous les achats de nourriture (c'est-à-dire plusieurs enregistrements pour chaque enregistrement dans [Food items]), Avec les résultats triés par date. Quelqu'un peut-il m'expliquer ce que je comprends mal à propos de l'opérateur DISTINCT? Autrement dit, pourquoi ne retourne-t-il pas un seul enregistrement pour chaque élément dans [Food items]?

Et plus précisément - quelle est la manière la plus simple pour moi de simplement extraire les données d'achat les plus récentes pour chaque aliment, compte tenu de la structure du tableau montré ci-dessus ? Je ne me soucie pas vraiment de l'efficacité autant que de la simplicité (la base de données avec laquelle je travaille est plutôt petite - il faudra des années avant qu'elle ne soit même dans les dizaines de milliers d'enregistrements). Je m'inquiète davantage que la requête soit compréhensible pour quelqu'un qui a peu de connaissances en SQL.

MISE À JOUR: J'ai donc essayé, les deux réponses suggérées ci-dessous, et aucune ne fonctionne (elles ne font que lancer des erreurs de syntaxe).

Sur la base des suggestions ci-dessous et après avoir lu en ligne, j'ai écrit la nouvelle requête suivante, en utilisant la fonction d'agrégation max() et une clause GROUP BY:

SELECT [Food purchase data].[Food item ID], [Food purchase data].[Price per unit], max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM [Food purchase data], Invoices
GROUP BY [Food purchase data].[Food item ID], [Food purchase data].[Price per unit];

Mais j'ai toujours le même problème: c'est-à-dire que je vois toujours plus d'un résultat pour chaque aliment. Quelqu'un peut-il expliquer pourquoi cette requête ne renvoie pas seulement l'achat le plus récent pour chaque aliment?

MISE À JOUR 2 (RÉSOLU!) :

Aucune des réponses ci-dessous n'a tout à fait fonctionné, mais basée sur une modification importante de Vladimir réponse ci-dessous , j'ai pu créer les requêtes suivantes, qui semblent donner les bons résultats.

Tout d'abord, j'ai créé cette vue et l'ai nommée "LatestInvoices":

SELECT InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate, InvoicesMaxDate.MaxID
FROM [Food purchase data], Invoices, (SELECT [Food purchase data].[Food item ID] AS ItemID, MAX(Invoices.[Invoice Date]) AS MaxDate, MAX(Invoices.[Invoice ID]) AS MaxID
                FROM [Food purchase data], Invoices
                WHERE Invoices.[Invoice ID] = [Food purchase data].[Invoice ID]
                GROUP BY [Food purchase data].[Food item ID]
         )  AS InvoicesMaxDate
WHERE InvoicesMaxDate.MaxID = [Food purchase data].[Invoice ID] AND
                      InvoicesMaxDate.ItemID = [Food purchase data].[Food item ID] AND 
                      InvoicesMaxDate.MaxDate = Invoices.[Invoice Date]
GROUP BY InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate,  InvoicesMaxDate.MaxID

Ensuite, j'ai écrit une autre requête pour extraire les champs dont j'avais besoin:

SELECT [Food items].ID AS FoodItemID, [Food items].Item AS FoodItem, [Food purchase data].[Price], [Food purchase data].[Price per unit], [Food purchase data].[Purchase unit], LatestInvoices.MaxDate as InvoiceDate
FROM [Food items], [Food purchase data], LatestInvoices
WHERE LatestInvoices.[MaxID] = [Food purchase data].[Invoice ID] AND
             LatestInvoices.ItemID = [Food purchase data].[Food item ID] AND
             LatestInvoices.ItemID = [Food items].ID
ORDER BY [Food items].Item;

Merci à tous ceux qui ont pris le temps de m'aider!

8
J. Taylor

MS Access est plutôt limité.

Je suppose qu'il est possible d'avoir plusieurs factures pour la même date. Dans ce cas, je choisirai une facture avec l'ID le plus élevé.

Au début, nous trouverons la date de facturation maximale pour chaque aliment.

SELECT
    FPD1.[Food item ID] AS ItemID
    ,MAX(I1.[Invoice Date]) AS MaxDate
FROM
    [Food purchase data] AS FPD1
    INNER JOIN Invoices AS I1 ON I1.ID = FPD1.[Invoice ID]
GROUP BY
    FPD1.[Food item ID]

Puisqu'il est possible qu'il y ait plusieurs factures pour la date max trouvée, nous choisirons une facture avec l'ID max par article

Basé sur la syntaxe MS Access des jointures imbriquées et en utilisant cet exemple à partir des documents:

SELECT fields 
FROM 
  table1 INNER JOIN 
  (
      table2 INNER JOIN 
      (
          table3 INNER JOIN tablex ON table3.field3 = tablex.fieldx
      ) ON table2.field2 = table3.field3
  ) ON table1.field1 = table2.field2
;

Essayons de le mettre ensemble:

SELECT
    InvoicesMaxDate.ItemID
    ,InvoicesMaxDate.MaxDate
    ,MAX(I2.ID) AS MaxInvoiceID
FROM
    (
        SELECT
            FPD1.[Food item ID] AS ItemID
            ,MAX(I1.[Invoice Date]) AS MaxDate
        FROM
            [Food purchase data] AS FPD1
            INNER JOIN Invoices AS I1 ON I1.ID = FPD1.[Invoice ID]
        GROUP BY
            FPD1.[Food item ID]
    ) AS InvoicesMaxDate INNER JOIN
    (
        [Food purchase data] AS FPD2 
        INNER JOIN Invoices AS I2 ON I2.ID = FPD2.[Invoice ID]
    ) ON
        InvoicesMaxDate.ItemID = FPD2.[Food item ID] AND
        --- you may need to put extra "ON" here as well, not sure
        InvoicesMaxDate.MaxDate = I2.[Invoice Date]
GROUP BY
    InvoicesMaxDate.ItemID
    ,InvoicesMaxDate.MaxDate

Nous avons maintenant à la fois ItemID et ID de la dernière facture pour cet article. Joignez-le aux tableaux d'origine pour récupérer d'autres détails (colonnes).

SELECT
    FI3.Item
    ,FI3.Item
    ,FPD3.[Price per unit]
    ,FPD3.[Purchase unit]
    ,I3.[Invoice Date]
FROM
    (
        SELECT
            InvoicesMaxDate.ItemID
            ,InvoicesMaxDate.MaxDate
            ,MAX(I2.ID) AS MaxInvoiceID
        FROM
            (
                SELECT
                    FPD1.[Food item ID] AS ItemID
                    ,MAX(I1.[Invoice Date]) AS MaxDate
                FROM
                    [Food purchase data] AS FPD1
                    INNER JOIN Invoices AS I1 ON I1.ID = FPD1.[Invoice ID]
                GROUP BY
                    FPD1.[Food item ID]
            ) AS InvoicesMaxDate INNER JOIN
            (
                [Food purchase data] AS FPD2 
                INNER JOIN Invoices AS I2 ON I2.ID = FPD2.[Invoice ID]
            ) ON
                InvoicesMaxDate.ItemID = FPD2.[Food item ID] AND
                InvoicesMaxDate.MaxDate = I2.[Invoice Date]
        GROUP BY
            InvoicesMaxDate.ItemID
            ,InvoicesMaxDate.MaxDate
    ) AS LastInvoices INNER JOIN
    (
        [Food items] AS FI3 INNER JOIN
        (
            [Food purchase data] AS FPD3
            INNER JOIN Invoices AS I3 ON I3.ID = FPD3.[Invoice ID]
        ) ON FI3.ID = FDP3.[Food item ID]
    ) ON
        LastInvoices.MaxInvoiceID = I3.ID AND
        LastInvoices.ItemID = FI3.ID

En pratique, je créerais une vue pour la première requête avec une seule jointure. Ensuite, je créais une deuxième vue qui joint la première vue aux tables, puis la troisième vue et ainsi de suite, pour éviter les jointures imbriquées ou les minimiser. La requête globale serait plus facile à lire.


Modifier pour clarifier ce que je veux dire en fonction de votre solution finale que vous avez mise dans la question.

Une dernière tentative pour transmettre mon message.

Voici ce que vous avez écrit sur la base de mes suggestions ci-dessus:

SELECT
    InvoicesMaxDate.ItemID
    ,InvoicesMaxDate.MaxDate
    ,Invoices.[Invoice ID]
FROM [Food purchase data], Invoices, 
    (
        SELECT 
            [Food purchase data].[Food item ID] AS ItemID
            ,MAX(Invoices.[Invoice Date]) AS MaxDate
        FROM [Food purchase data], Invoices
        WHERE Invoices.[Invoice ID] = [Food purchase data].[Invoice ID]
        GROUP BY [Food purchase data].[Food item ID]
    )  AS InvoicesMaxDate
WHERE
    Invoices.[Invoice ID] = [Food purchase data].[Invoice ID] AND
    InvoicesMaxDate.ItemID = [Food purchase data].[Food item ID] AND 
    InvoicesMaxDate.MaxDate = Invoices.[Invoice Date]
GROUP BY InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate, Invoices.[Invoice ID];

C'est ce que [~ # ~] i [~ # ~] voulait dire:

SELECT
    InvoicesMaxDate.ItemID
    ,InvoicesMaxDate.MaxDate
    ,MAX(Invoices.[Invoice ID]) AS [Invoice ID]
FROM [Food purchase data], Invoices, 
    (
        SELECT
            [Food purchase data].[Food item ID] AS ItemID
            ,MAX(Invoices.[Invoice Date]) AS MaxDate
        FROM [Food purchase data], Invoices
        WHERE Invoices.[Invoice ID] = [Food purchase data].[Invoice ID]
        GROUP BY [Food purchase data].[Food item ID]
    )  AS InvoicesMaxDate
WHERE
    Invoices.[Invoice ID] = [Food purchase data].[Invoice ID] AND
    InvoicesMaxDate.ItemID = [Food purchase data].[Food item ID] AND 
    InvoicesMaxDate.MaxDate = Invoices.[Invoice Date]
GROUP BY InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate;

Voyez-vous la différence?

InvoicesMaxDate renvoie MAX Invoice Date Pour chaque Food item ID. S'il y a deux factures pour le même Food item ID Avec le même MAX Invoice Date, Nous devons choisir une facture parmi elles. Cela se fait en regroupant par InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate. Il ne devrait y avoir aucun regroupement par Invoices.[Invoice ID] Ici, car nous voulons sélectionner la facture avec l'ID maximum.

Une fois que vous avez enregistré cette requête en tant que vue LatestInvoices, elle est utilisée plus loin, comme vous l'avez correctement écrit (notez que la requête finale utilise LatestInvoices.[Invoice ID] Et LatestInvoices.ItemID, Mais ne le fait pas). utilisez LatestInvoices.MaxDate):

SELECT 
    [Food items].ID as FoodItemID
    ,[Food items].Item as FoodItem
    ,[Food purchase data].[Price]
    ,[Food purchase data].[Price per unit]
    ,[Food purchase data].[Purchase unit]
    ,Invoices.[Invoice Date]
FROM [Food items], [Food purchase data], Invoices, LatestInvoices
WHERE 
    Invoices.[Invoice ID] = [Food purchase data].[Invoice ID] AND
    [Food items].ID = [Food purchase data].[Food item ID] AND
    LatestInvoices.[Invoice ID] = Invoices.[Invoice ID] AND 
    LatestInvoices.ItemID = [Food items].ID
ORDER BY [Food items].Item

Quant à, pourquoi votre dernière requête dans la question renvoie plusieurs lignes par article:

SELECT 
    [Food purchase data].[Food item ID]
    , [Food purchase data].[Price per unit]
    , max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM [Food purchase data], Invoices
GROUP BY [Food purchase data].[Food item ID], [Food purchase data].[Price per unit];

Vous regroupez ici par [Food item ID] Et [Price per unit], Vous obtiendrez donc autant de lignes qu'il y a de combinaisons uniques de ces deux colonnes.

La requête suivante retournerait une ligne par [Food item ID].

SELECT 
    [Food purchase data].[Food item ID]
    , max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM [Food purchase data], Invoices
GROUP BY [Food purchase data].[Food item ID];

Remarque: vous devez vraiment utiliser INNER JOIN Explicite au lieu de ,. Cette syntaxe a 20 ans.

SELECT 
    [Food purchase data].[Food item ID]
    , max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM
    [Food purchase data]
    INNER JOIN Invoices ON Invoices.ID = [Food purchase data].[Invoice ID]
GROUP BY [Food purchase data].[Food item ID];
7
Vladimir Baranov

Une requête qui fonctionne juste hors de la boîte:

SELECT Fi.Item, Fpd.[Price per unit], Fpd.[Purchase unit]
FROM [Food items] Fi INNER JOIN [Food purchase data] Fpd
ON Fpd.[Food item ID] = Fi.ID
WHERE Fpd.[Invoice ID] = (
  SELECT TOP 1 I.ID 
  FROM Invoices I INNER JOIN [Food purchase data] Fpd2
  ON Fpd2.[Invoice ID] = I.ID
  WHERE Fpd2.[Food item ID] = Fpd.[Food item ID]
  ORDER BY I.[Invoice Date] DESC
)
3
aksenoff

Je pourrais le résoudre avec la requête suivante:

Select MAX(AllItemBuyings.[invoice date]) as RecentBuyingDate, AllItemBuyings.[Food Item Id]  From 
(    
    select fpd.[Invoice Id], fpd.[Food Item Id], I.[invoice date] From [Food purchase data]as fpd 
    inner join invoices I on fpd.[Invoice Id] = I.ID

) as AllItemBuyings    
Group By AllItemBuyings.[Food Item Id]

Parce que je n'ai pas d'accès, j'ai testé cela sur SQL Server. J'espère que cela fonctionnera pour vous.

Édition/Requête supplémentaire: Afin d'ajouter les autres colonnes de la table des aliments, j'ai changé la requête. Je l'ai fait d'une manière que je n'aime pas vraiment. Si cela vous convient, cela dépend de vos données et de vos besoins. J'ai de nouveau rejoint la table INVOICES en utilisant la date de commande. Dans le cas où il s'agit d'une date, y compris l'heure de mon travail, veuillez en être conscient. Je ne vois pas d'autre moyen dans votre scénario. Peut-être existe-t-il une meilleure solution en utilisant la requête récursive ...?

Veuillez essayer et faites-moi savoir si cela fonctionne:

Select Recents.RecentBuyingDate, pd.* From 
(

   Select MAX(AllItemBuyings.[invoice date]) as RecentBuyingDate, AllItemBuyings.[Food Item Id]    From 
    (    
        select fpd.[Invoice Id], fpd.[Food Item Id], I.[invoice date], fpd.ID From [Food purchase data]as fpd 
        inner join invoices I on fpd.[Invoice Id] = I.ID

    ) as AllItemBuyings    
    Group By AllItemBuyings.[Food Item Id]

    ) as Recents    
    Join Invoices i on i.[invoice date] = Recents.RecentBuyingDate
    Join [Food purchase data] pd ON pd.[Invoice Id] = i.ID AND pd.[Food Item Id] = Recents.[Food Item Id]
3
Magier

Je crois que ce qui suit devrait fonctionner.

SELECT fi.[Item], fd.[Price per unit], MAX(i.[Invoice Date])
FROM [Invoices] AS i
INNER JOIN [Food Purchase Data] AS fd
    ON i.ID = fd.[Invoice ID]
INNER JOIN [Food items] AS fi
    ON fd.[Food item ID] = fi.ID
GROUP BY fi.Item, fd.[Price per unit]
ORDER BY i.[Invoice Date] DESC

Quant à savoir pourquoi votre requête ne renvoie pas les résultats que vous souhaitez:

SELECT [Food purchase data].[Food item ID], [Food purchase data].[Price per unit], max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM [Food purchase data], Invoices
GROUP BY [Food purchase data].[Food item ID], [Food purchase data].[Price per unit];

Le plus gros problème que je vois est que vous ne faites rien pour rejoindre vos tables. La "jointure" implicite qui est présente en listant simplement les deux dans votre clause FROM vous donne un produit cartésien. Fondamentalement, il renverra toutes les combinaisons possibles dans votre base de données pour les champs que vous interrogez.

Par exemple, si les deux tables ont chacune 3 enregistrements au lieu de renvoyer la date la plus récente, votre requête renvoie quelque chose comme: 1,1 1,2 1,3 2,1 2,2 2,3 3,1 3,2 3 , 3

Il est très important que vous déclariez explicitement vos jointures. Vous pouvez le faire de deux manières dans votre requête:

FROM [Food purchase data] AS fd, [Invoices] AS i
WHERE fd.[Invoice ID] = i.[ID]

OR

FROM [Food purchase data] AS fd
INNER JOIN [Invoices] AS i
    ON fd.[Invoice ID] = i.[ID]

Requêtes mises à jour, si celles-ci ne fonctionnent toujours pas, essayez de supprimer les alias et d'utiliser les noms de colonne complets.

2
agpoweredmg

Je suis d'accord avec les suggestions de Max concernant votre modèle de données. Leur mise en œuvre rendra votre SQL plus lisible à long terme.

Cela dit, DISTINCT affichera des lignes uniques. Donc, pour ne montrer que les plus récents, vous devez limiter les colonnes affichées.

Essayez quelque chose comme:

SELECT [Food purchase data].[Food item ID], max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM Invoices 
INNER JOIN ([Food items] ON [Food items].ID = [Food purchase data].[Food item ID]) 
GROUP BY [Food purchase data].[Food item ID]

(Traduction: pour chaque article du magasin, affichez sa date de facture la plus récente.)

Vous pouvez l'enregistrer en tant que vue et l'utiliser dans une autre requête comme vous le feriez pour une table. Vous pouvez donc faire une jointure interne sur la facture pour le prix d'achat et les jointures sur les autres tables si vous avez besoin de ces détails.

(Théoriquement, vous pouvez également effectuer une requête imbriquée, mais comme vous avez demandé une requête simple, une requête enregistrée est plus simple.)

MISE À JOUR en fonction de votre mise à jour:

Je vais utiliser des clauses WHERE au lieu de JOINS car je n'ai pas MS Access à portée de main. Vous devriez pouvoir utiliser l'interface graphique pour établir les connexions entre les tables dans MS Access en fonction de ces informations. (Veuillez fournir un SQLFiddle si vous avez vraiment besoin d'aide pour un dépannage supplémentaire.)

Étape 1: enregistrer ceci en tant que VUE (par exemple, "MostRecentInvoice")

SELECT [Food purchase data].[Food item ID] AS FoodItemID, max(Invoices.[Invoice Date]) AS MostRecentInvoiceDate
FROM [Food purchase data], Invoices
WHERE [Food purchase data].[Food item ID] = Invoices.ID
GROUP BY [Food purchase data].[Food item ID];

Étape 2: utiliser la vue dans une 2e requête

SELECT (list all the fields you need here)
FROM MostRecentInvoice, Invoices, etc...
WHERE MostRecentInvoice.FoodItemID = [Food purchase data].[Food item ID] 
AND MostRecentInvoice.MostRecentInvoiceDate = Invoices.[Invoice Date]
AND (whatever else joins you'll need for the other tables)

... et pour répondre à votre question: La 2e requête de la mise à jour ne fonctionne pas car la colonne [Prix par unité] se trouve dans vos instructions SELECT et GROUP BY. Cela signifie essentiellement que vous demandez à voir TOUTES les valeurs possibles de [Prix par unité] même si ce que vous voulez vraiment n'est qu'une seule: la valeur la plus récente.

0
chabzjo