SQL查询仅显示单个食品的最新购买记录


8

我正在使用MS Access 2013中的食品购买/发票系统,正在尝试创建一个SQL查询,该查询将返回每个食品的最新购买价格。

这是我正在使用的表的图表: MS Access数据库中的表

我对SQL的理解是非常基础的,因此我尝试了以下(不正确的)查询,希望它对每个项目仅返回一条记录(由于DISTINCT运算符),并且仅返回最近的购买记录(因为我做了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]

更重要的是- 给定上面显示的表格结构,对于我来说,仅提取每个食品的最新食品购买数据最简单方法是什么?我并没有真正关心效率,而是简单性(我正在使用的数据库相当小-甚至数以万计的记录范围还需要很多年)。我更关心查询对于那些不了解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返回在该行的所有列(而不是单个列)中不同的行。
马克斯·弗农

2
请注意,请避免在表名和列名中使用空格。那么,您将不需要用[和包围一切]
Max Vernon

1
并且(最好)在所有ID列中都包含表的名称,因此IDInvoices表中变为InvoiceID
Max Vernon 2015年

哦,这很有意义-我认为那DISTINCT是单列的。是否有一个类似的运算符将仅基于单个列中的唯一性进行选择?另外,感谢有关命名约定的提示-是的,不得不在[ ... ]任何地方使用都是非常烦人的事……而且我可以看到在ID列中包含表名将如何提高可读性。
J. Taylor

Answers:


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

基于嵌套连接MS Access语法,并使用docs中的以下示例:

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

在实践中,我将使用单个联接为第一个查询创建一个视图。然后,我将创建第二个视图,该视图将第一个视图与表连接在一起,然后将第三个视图依此类推,以免嵌套连接或将其最小化。整体查询将更易于阅读。


根据您提出的最终解决方案进行编辑,以弄清我的意思。

最后一次传达我的信息。

这是根据我上面的建议写的:

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;

你看得到差别吗?

InvoicesMaxDate返回MAX Invoice Date每个Food item ID。如果Food item ID具有相同MAX的两个发票相同,Invoice Date则应在其中选择一个发票。这是通过将分组来完成的InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate。应该有没有通过分组Invoices.[Invoice ID]这里,是因为我们想挑选具有最大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],因此您将获得的行数与这两列的唯一组合一样多。

以下查询每个返回一行[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];

感谢您的详细回答!您共享的第一个查询有效,并且确实提取了每个食品的最新发票日期,这确实很有帮助。但是,当我尝试与您共享的下两个查询时,我得到"Syntax error (missing operator) in query expression"了表达式INNER JOIN Invoices AS I2 ON I2.ID = FPD2.[Invoice ID]……我将继续研究它,看看是否可以使其正常工作。
J. Taylor

@JesseTaylor,显然,有必要明确地放在方括号中,(并且)当查询使用多个联接时,必须ON稍微移动该子句。我没有Access可以检查,但是我可以通过今天晚些时候阅读文档来尝试猜测正确的语法。
弗拉基米尔·巴拉诺夫

@JesseTaylor,我更新了答案,希望我猜对了语法。请尝试一下,让我知道它是否有效。
弗拉基米尔·巴拉诺夫

1
@JesseTaylor,欢迎您。自从我使用Access以来已经有一段时间了,很难正确地使用它的语法。关于您的观点的一点说明LatestInvoices:决赛GROUP应该是BY InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate唯一的,没有Invoices.[Invoice ID]。在这一SELECT部分应该有MAX(Invoices.[Invoice ID]) AS [Invoice ID]。这就是重点。首先(在内部查询中),我们GROUP BY [Food item ID]找到最大发票日期。该日期可能有几张发票,因此还有第二个GROUP BY可以选择其中最大ID的发票。
弗拉基米尔·巴拉诺夫

1
@JesseTaylor,不幸的是,您误解了我。我更新了答案以向您展示我的意思。若要查看差异,请在您的(样本)数据中添加两个ItemID具有相同大日期的发票,并尝试两个查询。
弗拉基米尔·巴拉诺夫

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
)

当我运行此查询时,我只会收到一个错误:“此子查询最多可以返回一条记录。” 数据表视图仅显示一条带有“ #NAME?”的记录。在每个领域。
J. Taylor

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]

因为我没有访问权限,所以我在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. Taylor

您建议的新查询仅引发一条错误消息,内容为“ FROM子句中的语法错误”。
J. Taylor

也许Access要求JOIN操作必须完全是“ INNER JOIN”。尝试INNSER JOIN,而不只是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子句中列出两个隐式的“ join”就可以给您带来笛卡尔积。基本上,它将为您查询的字段返回数据库中的所有可能组合。

例如,如果两个表各有3条记录而不是返回最近的日期,则查询将返回以下内容:1,1 1,2 1,3 2,1 2,2 2,3 3,1 3,2 3 ,3

显式声明联接非常重要。您可以在查询中执行的两种方法是:

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]

(翻译:对于商店中的每个项目,请显示其最近的发票日期。)

您可以将其保存为视图,并像在表中一样在另一个查询中使用它。因此,您可以在发票上进行购买价格的内部联接,如果需要这些详细信息,则可以在其他表上联接。

(理论上,您也可以执行嵌套查询,但是由于您请求的是简单查询,因此保存的查询更加简单。)

根据您的更新进行更新:

我将使用WHERE子句代替JOINS,因为我没有MS Access的方便。您应该能够使用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:在第二个查询中使用视图

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语句中。这实际上意味着您要查看[单位价格]的所有可能值,即使您真正想要的只是一个值:最新值。


谢谢,但是当我尝试运行共享的查询时,我只会收到一个错误:“ JOIN操作中的语法错误”。
J. Taylor

抱歉,我没有时间自己在Access中创建表。我假设您对联接有一些经验,因为您的问题中有一些。您是否尝试通过在Access中执行“创建”->“查询”来创建它?
chabzjo 2015年

由于该行,您共享的第一个查询不会给出正确的结果WHERE [Food purchase data].[Food item ID] = Invoices.ID...我想是您的意思,WHERE [Food purchase data].[Invoice ID] = Invoices.[Invoice ID]但是每个食物项仍返回多个日期,而不仅仅是最近的日期。
J. Taylor
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.