Ich arbeite mit einem Lebensmitteleinkaufs- / Rechnungssystem in MS Access 2013 und versuche, eine SQL-Abfrage zu erstellen, die den letzten Kaufpreis für jedes einzelne Lebensmittel zurückgibt.
Hier ist ein Diagramm der Tabellen, mit denen ich arbeite:
Mein Verständnis von SQL ist sehr grundlegend, und ich habe die folgende (falsche) Abfrage versucht, in der Hoffnung, dass nur ein Datensatz pro Artikel (aufgrund des DISTINCT
Operators) zurückgegeben wird und nur der letzte Kauf zurückgegeben wird (seit ich dies getan habe) 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;
Die obige Abfrage gibt jedoch einfach alle Lebensmitteleinkäufe zurück (dh mehrere Datensätze für jeden Datensatz in [Food items]
), wobei die Ergebnisse nach Datum sortiert sind. Kann mir jemand erklären, was ich über den DISTINCT
Betreiber falsch verstehe? Das heißt, warum wird nicht nur ein Datensatz für jeden Artikel in zurückgegeben [Food items]
?
Und mehr auf den Punkt gebracht - was ist für mich die einfachste Möglichkeit, die neuesten Daten zum Lebensmitteleinkauf für jedes einzelne Lebensmittel zu ermitteln, wenn man die oben gezeigte Tabellenstruktur berücksichtigt ? Effizienz ist mir weniger wichtig als Einfachheit (die Datenbank, mit der ich arbeite, ist eher klein - es wird Jahre dauern, bis sie überhaupt im Bereich von Zehntausenden von Datensätzen liegt). Es ist mir wichtiger, dass die Abfrage für jemanden mit geringen SQL-Kenntnissen verständlich ist.
UPDATE: Also habe ich versucht, beide unten vorgeschlagenen Antworten zu verwenden, und keine funktioniert (sie werfen nur Syntaxfehler auf).
Basierend auf den folgenden Vorschlägen und der Online-Lektüre habe ich die folgende neue Abfrage unter Verwendung der Aggregatfunktion max()
und einer GROUP BY
Klausel geschrieben:
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];
Aber ich habe immer noch das gleiche Problem: Das heißt, ich sehe immer noch mehr als ein Ergebnis für jedes Lebensmittel. Kann jemand erklären, warum diese Abfrage nicht nur den letzten Kauf für jedes Lebensmittel zurückgibt?
UPDATE 2 (Gelöst!) :
Keine der folgenden Antworten hat ganz geklappt, aber aufgrund einer starken Änderung der Antwort von Vladimir konnte ich die folgenden Abfragen erstellen, die anscheinend die richtigen Ergebnisse liefern.
Zuerst habe ich diese Ansicht erstellt und sie "LatestInvoices" genannt:
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
Dann schrieb ich eine weitere Abfrage, um die Felder einzugeben, die ich brauchte:
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;
Vielen Dank an alle, die sich die Zeit genommen haben, mir dabei zu helfen!
DISTINCT
Gibt Zeilen zurück, die für alle Spalten in der Zeile unterschiedlich sind, nicht für einzelne Spalten.[
und]
ID
Spalten aufzunehmen, damitID
in dieInvoices
Tabelle wirdInvoiceID
.DISTINCT
das wäre aus einzelnen Spalten. Gibt es einen analogen Operator, der nur anhand der Eindeutigkeit in einer einzelnen Spalte auswählt? Vielen Dank auch für die Tipps zu Namenskonventionen - ja, es ist sehr ärgerlich, sie[ ... ]
überall verwenden zu müssen ... Und ich kann sehen, wie die Aufnahme des Tabellennamens in die ID-Spalte die Lesbarkeit verbessern würde.Antworten:
MS Access ist eher begrenzt.
Ich gehe davon aus, dass es möglich ist, mehr als eine Rechnung für dasselbe Datum zu haben. In diesem Fall wähle ich eine Rechnung mit der höchsten ID.
Zuerst finden wir das maximale Rechnungsdatum für jedes Lebensmittel.
Da es möglich ist, dass es mehrere Rechnungen für das gefundene maximale Datum gibt, wählen wir eine Rechnung mit der maximalen ID pro Artikel aus
Basierend auf der MS Access-Syntax verschachtelter Joins und anhand dieses Beispiels aus den Dokumenten:
Versuchen wir es zusammenzusetzen:
Jetzt haben wir sowohl die Artikel-ID als auch die ID der letzten Rechnung für diesen Artikel. Verbinden Sie dies mit Originaltabellen, um weitere Details (Spalten) abzurufen.
In der Praxis würde ich eine Ansicht für die erste Abfrage mit einem einzelnen Join erstellen. Dann würde ich eine zweite Ansicht erstellen, die die erste Ansicht mit den Tabellen verbindet, dann die dritte Ansicht usw., um die verschachtelten Verknüpfungen zu vermeiden oder sie zu minimieren. Die Gesamtabfrage wäre leichter zu lesen.
Bearbeiten Sie , um zu klären, was ich meine, basierend auf Ihrer endgültigen Lösung, die Sie in die Frage gestellt haben.
Ein letzter Versuch, meine Botschaft zu übermitteln.
Dies ist, was Sie basierend auf meinen obigen Vorschlägen geschrieben haben:
Das habe ich gemeint:
Sehen Sie den Unterschied?
Die
InvoicesMaxDate
gibt MAXInvoice Date
für jeden zurückFood item ID
. Wenn es zwei Rechnungen für dieselbeFood item ID
mit derselben MAX gibtInvoice Date
, sollten wir eine Rechnung unter ihnen auswählen. Dies erfolgt durch Gruppieren nachInvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate
. Hier sollte es keine Gruppierung gebenInvoices.[Invoice ID]
, da wir die Rechnung mit der maximalen ID auswählen möchten.Sobald Sie diese Abfrage als
LatestInvoices
Ansicht gespeichert haben , wird sie weiter verwendet, wie Sie sie richtig geschrieben haben (beachten Sie, dass die endgültige AbfrageLatestInvoices.[Invoice ID]
und verwendetLatestInvoices.ItemID
, aber nicht verwendetLatestInvoices.MaxDate
):Warum gibt Ihre letzte Abfrage in der Frage mehrere Zeilen pro Element zurück:
Sie gruppieren hier nach
[Food item ID]
und[Price per unit]
, sodass Sie so viele Zeilen erhalten, wie es eindeutige Kombinationen dieser beiden Spalten gibt.Die folgende Abfrage würde eine Zeile pro zurückgeben
[Food item ID]
.Eine Randnotiz, die Sie wirklich explizit
INNER JOIN
anstelle von verwenden sollten,
. Diese Syntax ist 20 Jahre alt.quelle
"Syntax error (missing operator) in query expression"
den AusdruckINNER JOIN Invoices AS I2 ON I2.ID = FPD2.[Invoice ID]
... Ich werde mehr damit herumspielen, um zu sehen, ob ich es zum Laufen bringen kann.(
und)
wenn Abfrage mehrere verwendet schließt sich und die bewegenON
Klausel ein wenig herum. Ich habe keinen Zugriff zum Überprüfen, aber ich kann versuchen, die richtige Syntax zu erraten, indem ich die Dokumente später heute lese.LatestInvoices
: Das FinaleGROUP
sollteBY InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate
nur ohne seinInvoices.[Invoice ID]
. In demSELECT
Teil sollte es seinMAX(Invoices.[Invoice ID]) AS [Invoice ID]
. Das ist der springende Punkt. Zuerst (in der inneren Abfrage) finden wirGROUP BY [Food item ID]
das maximale Rechnungsdatum. Es kann mehrere Rechnungen mit diesem Datum geben, daher gibt es eine Sekunde ZeitGROUP BY
, um eine Rechnung mit maximaler ID auszuwählen.ItemID
mit demselben großen Datum hinzu und versuchen Sie beide Abfragen.Eine Abfrage, die sofort funktioniert:
quelle
Ich könnte es mit der folgenden Abfrage lösen:
Da ich keinen Zugriff habe, habe ich dies auf SQL Server getestet. Ich hoffe das wird für dich funktionieren.
Bearbeiten / Zusätzliche Abfrage : Um die anderen Spalten der Lebensmittelelementtabelle hinzuzufügen, habe ich die Abfrage geändert. Ich habe es auf eine Weise gemacht, die ich nicht wirklich mag. Ob es für Sie in Ordnung ist, hängt von Ihren Daten und Anforderungen ab. Ich habe mich mit dem Bestelldatum wieder der Tabelle INVOICES angeschlossen. Falls dies ein Datum ist, einschließlich der Zeit, zu der ich trainiere, beachten Sie dies bitte. Ich sehe keinen anderen Weg in Ihrem Szenario. Vielleicht gibt es eine bessere Lösung mit rekursiven Abfragen ...?
Bitte probieren Sie es aus und lassen Sie mich wissen, ob es funktioniert:
quelle
Item
,Price per unit
usw.)?Ich glaube, das Folgende sollte funktionieren.
Warum Ihre Abfrage nicht die gewünschten Ergebnisse zurückgibt:
Das größte Problem, das ich sehe, ist, dass Sie nicht wirklich etwas tun, um sich Ihren Tischen anzuschließen. Der implizite "Join", der durch einfaches Auflisten beider in Ihrer FROM-Klausel vorhanden ist, gibt Ihnen ein kartesisches Produkt. Grundsätzlich wird jede mögliche Kombination in Ihrer Datenbank für die Felder zurückgegeben, die Sie abfragen.
Wenn die beiden Tabellen beispielsweise jeweils 3 Datensätze enthalten, anstatt das letzte Datum zurückzugeben, würde Ihre Abfrage etwa Folgendes zurückgeben: 1,1 1,2 1,3 2,1 2,2 2,3 3,1 3,2 3 ,3
Es ist sehr wichtig, dass Sie Ihre Joins explizit deklarieren. Sie können dies in Ihrer Abfrage auf zwei Arten tun:
ODER
Aktualisierte Abfragen: Wenn diese immer noch nicht funktionieren, entfernen Sie die Aliase und verwenden Sie die vollständig qualifizierten Spaltennamen.
quelle
Ich stimme den Vorschlägen von Max zu Ihrem Datenmodell zu. Durch die Implementierung dieser wird Ihr SQL auf lange Sicht besser lesbar.
Vor diesem Hintergrund zeigt DISTINCT eindeutige Zeilen an. Um nur die aktuellsten anzuzeigen, müssen Sie die angezeigten Spalten einschränken.
Versuchen Sie etwas wie:
(Übersetzung: Zeigen Sie für jeden Artikel im Geschäft das letzte Rechnungsdatum an.)
Sie können dies als Ansicht speichern und in einer anderen Abfrage wie in einer Tabelle verwenden. Sie können also einen inneren Join auf der Rechnung für den Kaufpreis durchführen und sich auf den anderen Tabellen verbinden, wenn Sie diese Details benötigen.
(Theoretisch könnten Sie auch eine verschachtelte Abfrage durchführen, aber da Sie einfach angefordert haben, ist eine gespeicherte Abfrage einfacher.)
UPDATE basierend auf Ihrem Update:
Ich werde WHERE-Klauseln anstelle von JOINS verwenden, da ich MS Access nicht zur Hand habe. Sie sollten in der Lage sein, die GUI zu verwenden, um die Verbindungen zwischen den Tabellen in MS Access basierend auf diesen Informationen herzustellen. (Bitte geben Sie eine SQLFiddle an, wenn Sie wirklich Hilfe bei der weiteren Fehlerbehebung benötigen.)
Schritt 1: Speichern Sie dies als ANSICHT (z. B. "MostRecentInvoice")
Schritt 2: Verwenden Sie die Ansicht in einer zweiten Abfrage
... und um Ihre Frage zu beantworten: Die zweite Abfrage im Update funktioniert nicht, da sich die Spalte [Preis pro Einheit] in Ihren Anweisungen SELECT und GROUP BY befindet. Dies bedeutet im Wesentlichen, dass Sie ALLE möglichen Werte von [Preis pro Einheit] anzeigen möchten, obwohl Sie wirklich nur einen Wert wünschen: den neuesten Wert.
quelle
WHERE [Food purchase data].[Food item ID] = Invoices.ID
... Ich nehme an, Sie haben gemeint,WHERE [Food purchase data].[Invoice ID] = Invoices.[Invoice ID]
aber das gibt immer noch mehrere Daten pro Lebensmittel zurück, anstatt nur die aktuellsten.