個々の食品の最新の購入記録のみを表示するSQLクエリ


8

MS Access 2013で食品購入/請求システムを使用していて、個々の食品の最新の購入価格を返すSQLクエリを作成しようとしています。

ここに私が作業しているテーブルの図があります: MS Accessデータベースのテーブル

SQLについての私の理解は非常に基本的です。次の(正しくない)クエリを試してみました。(DISTINCT演算子のため)アイテムごとに1つのレコードのみが返され、最新の購入のみが返されることを期待しています(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;

ただし、上記のクエリは単にすべての食品購入(つまり、の各レコードの複数のレコード)を返し[Food items]、結果は日付の降順でソートされます。誰かがDISTINCTオペレーターについて誤解していることを私に説明できますか?つまり、なぜ各アイテムのレコードが1つだけ返されないの[Food items]ですか?

そしてもっと重要な点- 上記のテーブル構造を前提として、個々の食品の最新の食品購入データを取得する最も簡単な方法は何ですか?単純さほど効率についてはあまり気にしません(私が使用しているデータベースはかなり小さいです-数万のレコードの範囲になるまでには数年かかります)。SQLの知識がほとんどない人にとっても、クエリが理解可能であることを重視しています。

更新: それで私は試してみました、両方の答えは以下に提案されていて、どちらも機能しません(構文エラーをスローするだけです)。

以下の提案に基づいて、さらにオンラインで読んで、集約関数max()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];

しかし、私はまだ同じ問題を抱えています。つまり、各食品に対して複数の結果が表示されています。このクエリが各食品の最新の購入を返すだけではない理由を誰かが説明できますか?

アップデート2(解決済み!)

以下の回答はどれもうまくいきませんでしたが、以下のウラジミールの回答の大幅な変更に基づいて、正しい結果を提供しているように見える次のクエリを作成できました。

まず、このビューを作成し、「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

次に、必要なフィールドを取得するための別のクエリを作成しました。

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;

これを手伝ってくれてありがとうございました!


2
DISTINCT単一の列ではなく、行内のすべての列で異なる行を返します。
Max Vernon

2
一言アドバイスとして、テーブル名と列名にスペースを使用しないでください。その後、サラウンドですべてに必要ではないだろう[]
マックス・バーノン

1
そして、(間違いなく)すべてのID列にテーブルの名前を含めるのが最善であるためIDInvoicesテーブルではになりInvoiceIDます。
Max Vernon

ああ、それは理にかなっている-私はそれDISTINCTが単一のカラムによるものだと思った 単一の列の一意性のみに基づいて選択する類似の演算子はありますか?また、命名規則のヒントに感謝します-はい、[ ... ]どこでも使用するのは非常に面倒です...そして、ID列にテーブル名を含めると読みやすさが向上することがわかります。
J.テイラー

回答:


7

MS Accessはかなり制限されています。

同じ日付で複数の請求書を作成することは可能だと思います。この場合、最も高いIDの請求書を選びます。

最初に、各食品の最大請求日を見つけます。

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]

見つかった最大日付の請求書が複数ある可能性があるため、アイテムごとに最大IDの請求書を1つ選びます。

ネストされた結合のMS Access構文に基づいており、ドキュメントのこの例を使用します

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
;

まとめてみましょう:

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

これで、ItemIDとそのアイテムの最後の請求書のIDの両方が取得されました。これを元のテーブルに結合して、他の詳細(列)をフェッチします。

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

実際には、1つの結合で最初のクエリのビューを作成します。次に、最初のビューをテーブルに結合する2番目のビューを作成し、次に3番目のビューを作成して、ネストされた結合を回避するか最小化します。全体的なクエリは読みやすくなります。


編集して、質問に回答した最終的なソリューションに基づいて私が何を意味するかを明確にします。

私のメッセージを伝える最後の試み。

これは上記の私の提案に基づいてあなたが書いたものです

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];

これは私が意味したことです:

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;

違いがわかりますか?

InvoicesMaxDateMAXを返すInvoice DateごとにFood item IDFood item ID同じMAXの同じ請求書が2つある場合Invoice Date、それらの中から1つの請求書を選択する必要があります。これは、によるグループ化によって行われますInvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate。最大のIDを持つ請求書を選択するため、ここでグループ化する必要はありませんInvoices.[Invoice ID]

このクエリをLatestInvoicesビューとして保存すると、正しく記述したとおりにさらに使用されます(最後のクエリではLatestInvoices.[Invoice ID]およびを使用しますLatestInvoices.ItemIDが、は使用しません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

なぜ、質問の最後のクエリがアイテムごとにいくつかの行を返すのか:

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];

あなたはここでグループ化されている[Food item ID][Price per unit]、ので、これらの二つのカラムのユニークな組み合わせがあるとして、あなたは多くの行として取得します。

次のクエリは、ごとに1行を返します[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];

余談ですが、実際にはのINNER JOIN代わりに明示を使用する必要があります,。その構文は20年前のものです。

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];

非常に詳細な回答をありがとう!あなたが共有した最初のクエリは機能し、実際に個々の食品の最新の請求日を取得しました。これは本当に役に立ちます。しかし、あなたが共有した次の2つのクエリを試したところ"Syntax error (missing operator) in query expression"、式になってしまいましたINNER JOIN Invoices AS I2 ON I2.ID = FPD2.[Invoice ID]...動作するかどうかを確認するために、さらに試します。
J.テイラー

@JesseTaylor、明らかに、ブラケットを明示的に置く必要が(あり)、クエリが複数の結合を使用する場合、ON句を少し移動する必要あります。チェックするアクセス権はありませんが、今日のドキュメントを読んで正しい構文を推測することができます。
Vladimir Baranov

@JesseTaylor、私は答えを更新し、構文を正しく推測できることを願っています。試してみて、動作するかどうか教えてください。
Vladimir Baranov

1
@JesseTaylor、どういたしまして。Accessを使用して久しぶりに、構文を正しく理解するのが困難です。あなたの見解についての1つのメモLatestInvoices:最終GROUPBY InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate、なしでのみである必要がありますInvoices.[Invoice ID]。でSELECT一部があるはずですMAX(Invoices.[Invoice ID]) AS [Invoice ID]。これがポイントです。最初に(内部クエリで)GROUP BY [Food item ID]最大請求日を見つけます。この日付の請求書は複数存在する可能性があるため、そのGROUP BYうちの最大のIDを持つ請求書を選択する2番目があります。
Vladimir Baranov

1
@JesseTaylor、残念ながら、あなたは私を誤解しました。私の答えを更新して、私が何を意味するのかを示しました。違いを確認するには、2つの請求書を同じサンプルの(サンプル)データにItemID同じ大きな日付で追加し、両方のクエリを試してください。
Vladimir Baranov

3

そのまま使用できるクエリ:

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
)

このクエリを実行すると、「このサブクエリでは最大で1つのレコードを返すことができます」というエラーが表示されます。データシートビューには、「#NAME?」を含むレコードが1つだけ表示されます。すべての分野で。
J.テイラー

3

次のクエリで解決できます。

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]

Accessがないため、SQL Serverでテストしました。これがうまくいくことを願っています。

編集/追加クエリ:食品テーブルの他の列を追加するために、クエリを変更しました。嫌いなやり方でやった。それが問題ないかどうかは、データと要件によって異なります。注文日を使用して、INVOICESテーブルに再び参加しました。これが私のワークアウトの時間を含む日付である場合は、そのことに注意してください。私はあなたのシナリオで別の方法を見ていません。たぶん、再帰クエリを使用したより良い解決策があります...?

それを試してみて、それが機能するかどうか私に知らせてください:

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]

ありがとうございました。これにより、各アイテムの最新の購入日が正しく表示されます。どのように私は私が(例えば、質問に言及したことをすべてのフィールドに引っ張って、けれども、これを使用することになりItemPrice per unitなど)?
J.テイラー

提案した新しいクエリは、「FROM句の構文エラー」以外のエラーメッセージをスローするだけです。
J.テイラー

Accessでは、JOIN操作が正確に "INNER JOIN"である必要がある場合があります。JOINだけでなくINNSER JOINを試してください。
Magier

2

以下がうまくいくと思います。

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

なぜあなたのクエリはあなたが望む結果を返さないのか:

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];

私が目にする最大の問題は、あなたがテーブルに参加するために本当に何もしていないということです。FROM句に両方をリストするだけで存在する暗黙の「結合」は、デカルト積を与えます。基本的に、クエリを実行しているフィールドについて、データベース内のすべての可能な組み合わせを返します。

たとえば、2つのテーブルにそれぞれ最新の日付を返す代わりに3つのレコードがある場合、クエリは次のようなものを返します:1,1 1,2 1,3 2,1 2,2 2,3 3,1 3,2 3 、3

結合を明示的に宣言することは非常に重要です。クエリでこれを行うには、2つの方法があります。

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

または

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

クエリが更新されても機能しない場合は、エイリアスを削除し、完全修飾列名を使用してみてください。



0

データモデルに関するMaxの提案に同意します。これらを実装すると、長期的にはSQLが読みやすくなります。

そうは言っても、DISTINCTは一意の行を表示します。したがって、最新のものだけを表示するには、表示される列を制限する必要があります。

次のようなものを試してください:

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]

(翻訳:ストア内の各アイテムについて、最新の請求日を表示します。)

これをビューとして保存し、テーブルと同じように別のクエリで使用できます。そのため、詳細が必要な場合は、購入価格の請求書で内部結合を行い、他のテーブルで結合することができます。

(理論的には、ネストされたクエリを実行することもできますが、単純なリクエストをしたため、保存されたクエリの方が簡単です。)

更新に基づいた更新:

MS Accessが手元にないため、JOINSではなくWHERE句を使用します。GUIを使用して、この情報に基づいてMS Accessのテーブル間を接続できるはずです。(さらにトラブルシューティングが必要な場合は、SQLFiddleを提供してください。)

ステップ1:これをVIEWとして保存します(たとえば、 "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];

手順2:2番目のクエリでビューを使用する

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)

...そしてあなたの質問に答える:[ユニットあたりの価格]列がSELECTおよびGROUP BYステートメントにあるため、更新の2番目のクエリは機能しません。これは基本的に、本当に必要なのは最新の値だけであるにもかかわらず、[Price per unit]のすべての可能な値を確認するように求めていることを意味します。


ありがとうございます。ただし、共有したクエリを実行しようとすると、「JOIN操作の構文エラー」というエラーが表示されます。
J.テイラー

Accessで自分でテーブルを作成する時間がありませんでした。あなたの質問にはいくつかあったので、あなたは結合について少し経験があると思いました。AccessでCreate-> Queryを実行して作成しようとしましたか?
chabzjo

共有した最初のクエリは、次の行があるために正しい結果が得られませんWHERE [Food purchase data].[Food item ID] = Invoices.ID...私はあなたが意図しWHERE [Food purchase data].[Invoice ID] = Invoices.[Invoice ID]たと思いますが、それでも最新のものだけでなく、食品ごとに複数の日付を返します。
J.テイラー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.