web-dev-qa-db-ja.com

個々の食品の最新の購入レコードのみを表示するSQLクエリ

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

これが私が作業しているテーブルの図です: Tables in MS Access database

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演算子について私が誤解していることを誰かが私に説明できますか?つまり、なぜ_[Food items]_の各アイテムに対して1つのレコードのみを返さないのですか?

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

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

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

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

8
J. Taylor

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

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


編集して、質問に回答した最終的な解決策に基づいて私が何を意味するかを明確にします。

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

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

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

これは[〜#〜] i [〜#〜]の意味するところです:

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;

違いがわかりますか?

InvoicesMaxDateは、各Invoice Dateに対してMAX Food item IDを返します。同じFood item IDの2つの請求書が同じMAX Invoice Dateである場合、それらの中から1つの請求書を選択する必要があります。これは、InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDateによるグループ化によって行われます。ここでは、Invoices.[Invoice ID]によるnoのグループ化が必要です。これは、最大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]でグループ化しているため、これら2つの列の一意の組み合わせと同じ数の行を取得できます。

次のクエリは、[Food item ID]ごとに1行を返します。

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

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

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

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

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]
3
Magier

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

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]

OR

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

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

2
agpoweredmg

データモデルに関する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]のすべての可能な値を確認するように求めていることを意味します。

0
chabzjo