Consultas SQL para mostrar solo los registros de compras más recientes para alimentos individuales

8

Estoy trabajando con un sistema de compra / facturación de alimentos en MS Access 2013 y estoy tratando de crear una consulta SQL que devolverá el precio de compra más reciente para cada artículo alimenticio individual.

Aquí hay un diagrama de las tablas con las que estoy trabajando: Tablas en la base de datos de MS Access

Mi comprensión de SQL es muy básica, e intenté la siguiente consulta (incorrecta), con la esperanza de que solo devolvería un registro por artículo (debido al DISTINCToperador) y que solo devolvería la compra más reciente (ya que lo hice 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;

Sin embargo, la consulta anterior simplemente devuelve todas las compras de alimentos (es decir, múltiples registros para cada registro [Food items]), con los resultados ordenados por fecha. ¿Alguien puede explicarme lo que estoy entendiendo mal sobre el DISTINCToperador? Es decir, ¿por qué no devuelve solo un registro para cada elemento [Food items]?

Y más importante: ¿cuál es la forma más sencilla para mí de obtener los datos de compra de alimentos más recientes para cada artículo de alimentos individual, dada la estructura de la tabla que se muestra arriba ? Realmente no me importa tanto la eficiencia como la simplicidad (la base de datos con la que estoy trabajando es bastante pequeña, pasarán años antes de que esté incluso en el rango de decenas de miles de registros). Me importa más que la consulta sea comprensible para alguien con poco conocimiento de SQL.

ACTUALIZACIÓN: así que lo intenté, las dos respuestas sugeridas a continuación, y ninguna de ellas funciona (simplemente arrojan errores de sintaxis).

Basado en las sugerencias a continuación, y leyendo más en línea, escribí la siguiente nueva consulta, usando la función de agregado max()y una GROUP BYcláusula:

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

Pero sigo teniendo el mismo problema: es decir, sigo viendo más de un resultado para cada alimento. ¿Alguien puede explicar por qué esta consulta no solo devuelve la compra más reciente de cada alimento?

ACTUALIZACIÓN 2 (¡RESUELTO!) :

Ninguna de las respuestas a continuación funcionó, pero en base a algunas modificaciones importantes de la respuesta de Vladimir a continuación , pude crear las siguientes consultas, que parecen estar dando los resultados correctos.

Primero, creé esta vista y la llamé "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

Luego escribí otra consulta para obtener los campos que necesitaba:

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;

¡Gracias a todos los que se tomaron el tiempo para ayudarme con esto!

J. Taylor
fuente
2
DISTINCTdevuelve filas que son distintas en todas las columnas de la fila, no en columnas individuales.
Max Vernon
2
Solo un consejo, evite usar espacios en los nombres de las tablas y columnas. Entonces no necesitarás rodear todo con [y]
Max Vernon
1
Y es (posiblemente) mejor incluir el nombre de la tabla en todas las IDcolumnas, por lo que IDen la Invoicestabla se convierte InvoiceID.
Max Vernon
Oh, eso tiene sentido, pensé que DISTINCTera por columnas individuales. ¿Existe un operador análogo que seleccione solo en función de la unicidad en una sola columna? Además, gracias por los consejos sobre convenciones de nomenclatura: sí, es muy molesto tener que usarlo en [ ... ]todas partes ... Y puedo ver cómo incluir el nombre de la tabla en la columna ID aumentaría la legibilidad.
J. Taylor

Respuestas:

7

MS Access es bastante limitado.

Supongo que es posible tener más de una factura para la misma fecha. En este caso, elegiré una factura con la ID más alta.

Al principio, encontraremos la Fecha máxima de factura para cada artículo alimenticio.

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]

Como es posible que haya varias facturas para la fecha máxima encontrada, elegiremos una factura con la ID máxima por artículo

Basado en la sintaxis de MS Access de uniones anidadas y usando este ejemplo de los documentos:

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
;

Tratemos de armarlo:

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

Ahora tenemos el ItemID y el ID de la última factura para ese artículo. Unir esto a las tablas originales para obtener otros detalles (columnas).

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 la práctica, crearía una vista para la primera consulta con una sola combinación. Luego crearía una segunda vista que une la primera vista con las tablas, luego la tercera vista y así sucesivamente, para evitar las uniones anidadas o minimizarlas. La consulta general sería más fácil de leer.


Edite para aclarar lo que quiero decir en función de su solución final que haya puesto en la pregunta.

Un último intento de transmitir mi mensaje.

Esto es lo que escribiste en base a mis sugerencias anteriores:

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

Esto es lo que quise decir:

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;

¿Ves la diferencia?

Los InvoicesMaxDateretornos MAX Invoice Datepara cada uno Food item ID. Si hay dos facturas para el mismo Food item IDcon el mismo MAX Invoice Date, deberíamos elegir una factura entre ellas. Esto se hace agrupando por InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDate. No debe haber ninguna agrupación por Invoices.[Invoice ID]aquí, porque queremos recoger la factura con el ID máxima.

Una vez que tiene esta consulta guardada como una LatestInvoicesvista, se usa más a medida que escribió correctamente (tenga en cuenta que la consulta final usa LatestInvoices.[Invoice ID]y LatestInvoices.ItemID, pero no usa 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

En cuanto a por qué su última consulta en la pregunta devuelve varias filas por elemento:

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

Está agrupando aquí por [Food item ID]y [Price per unit], por lo que obtendrá tantas filas como combinaciones únicas de estas dos columnas.

La siguiente consulta devolvería una fila por [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];

Una nota al margen, realmente debería usar explícito en INNER JOINlugar de ,. Esa sintaxis tiene 20 años.

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];
Vladimir Baranov
fuente
¡Gracias por tu respuesta muy detallada! La primera consulta que compartió funcionó y, de hecho, obtuvo la fecha de factura más reciente para cada artículo alimenticio individual, lo cual es realmente útil. Sin embargo, cuando probé las siguientes 2 consultas que compartiste, obtuve "Syntax error (missing operator) in query expression"la expresión INNER JOIN Invoices AS I2 ON I2.ID = FPD2.[Invoice ID]... Jugaré un poco más para ver si puedo hacer que funcione.
J. Taylor
@JesseTaylor, aparentemente, es necesario poner corchetes explícitamente (y )cuando la consulta usa varias combinaciones y mover la ONcláusula un poco. No tengo acceso para verificar, pero puedo intentar adivinar la sintaxis correcta leyendo los documentos más tarde hoy.
Vladimir Baranov
@JesseTaylor, actualicé la respuesta y espero adivinar la sintaxis correctamente. Por favor, pruébalo y avísame si funciona.
Vladimir Baranov
1
@JesseTaylor, de nada. Ha pasado un tiempo desde que usé Access y es difícil obtener la sintaxis correcta. Una nota sobre su punto de vista LatestInvoices: la final GROUPdebe ser BY InvoicesMaxDate.ItemID, InvoicesMaxDate.MaxDatesolo, sin Invoices.[Invoice ID]. En la SELECTparte debería haber MAX(Invoices.[Invoice ID]) AS [Invoice ID]. Este es el punto. Al principio (en la consulta interna), GROUP BY [Food item ID]encontramos la fecha máxima de facturación. Puede haber varias facturas con esta fecha, por lo que hay un segundo GROUP BYpara elegir la factura con la ID máxima entre ellas.
Vladimir Baranov
1
@JesseTaylor, Desafortunadamente, me malinterpretaste. Actualicé mi respuesta para mostrarte lo que quise decir. Para ver la diferencia, agregue dos facturas a sus datos (de muestra) para la misma ItemIDcon la misma fecha grande e intente ambas consultas.
Vladimir Baranov
3

Una consulta que simplemente sale de la caja:

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
)
aksenoff
fuente
Cuando ejecuto esta consulta, solo aparece un error: "Como máximo, esta subconsulta puede devolver un registro". La vista de la hoja de datos solo muestra un registro con "#NAME?" en todos los campos
J. Taylor
3

Podría resolverlo con la siguiente consulta:

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]

Como no tengo acceso, probé esto en SQL Server. Espero que esto funcione para tí.

Editar / Consulta adicional : para agregar las otras columnas de la tabla de artículos alimenticios, cambié la consulta. Lo hice de una manera que realmente no me gusta. Si está bien para usted, depende de sus datos y requisitos. Me uní a la tabla FACTURAS nuevamente usando la Fecha del pedido. En caso de que esta sea una fecha que incluya el tiempo de mi trabajo, tenga en cuenta eso. No veo otra forma en tu escenario. ¿Quizás haya una mejor solución usando la consulta recursiva ...?

Pruébalo y avísame si funciona:

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]
Magier
fuente
Gracias. Esto me da correctamente la fecha de compra más reciente para cada artículo. ¿Cómo voy a usar esto, sin embargo, para tirar en todos los campos que he mencionado en la pregunta (por ejemplo Item, Price per unit, etc)?
J. Taylor
La nueva consulta que sugirió solo arroja un mensaje de error que dice nada más que "Error de sintaxis en la cláusula FROM".
J. Taylor
Quizás Access requiera que la operación JOIN sea exactamente "INNER JOIN". Prueba INNSER JOIN en lugar de solo JOIN.
Magier
2

Creo que lo siguiente debería funcionar.

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

En cuanto a por qué su consulta no devuelve los resultados que desea:

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

El mayor problema que veo es que realmente no estás haciendo nada para unirte a tus mesas. La "unión" implícita que está presente simplemente enumerando ambas en su cláusula FROM le está dando un producto cartesiano. Básicamente, devolverá todas las combinaciones posibles en su base de datos para los campos que está consultando.

Por ejemplo, si las dos tablas tenían 3 registros cada una en lugar de devolver la fecha más reciente, su consulta devolvería algo como: 1,1 1,2 1,3 2,1 2,2 2,3 3,1 3,2 3 3

Es muy importante que declares explícitamente tus uniones. Las dos formas en que podría hacerlo en su consulta serían:

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

O

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

Consultas actualizadas, si todavía no funcionan, intente eliminar los alias y utilice los nombres de columna completos.

mgpoder
fuente
Continuemos esta discusión en el chat .
agpoweredmg
0

Estoy de acuerdo con las sugerencias de Max sobre su modelo de datos. Implementarlos hará que su SQL sea más legible a largo plazo.

Dicho esto, DISTINCT mostrará filas únicas. Por lo tanto, para mostrar solo los más recientes, debe limitar las columnas que se muestran.

Intenta algo como:

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]

(Traducción: para cada artículo en la tienda, muestre su fecha de factura más reciente).

Puede guardar esto como una vista y usarlo en otra consulta como lo haría con una tabla. Por lo tanto, puede hacer una unión interna en la factura por el precio de compra y unirse en las otras tablas si necesita esos detalles.

(Teóricamente, también podría hacer una consulta anidada, pero dado que solicitó simple, una consulta guardada es más simple).

ACTUALIZACIÓN basada en su actualización:

Voy a usar cláusulas WHERE en lugar de JOINS porque no tengo MS Access a mano. Debería poder usar la GUI para hacer las conexiones entre las tablas en MS Access en función de esta información. (Proporcione un SQLFiddle si realmente necesita ayuda con la resolución de problemas).

Paso 1: guarde esto como una VISTA (por ejemplo, "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];

Paso 2: use la vista en una segunda consulta

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)

... y para responder a su pregunta: la segunda consulta en la actualización no funciona porque la columna [Precio por unidad] está en sus declaraciones SELECT y GROUP BY. Esto significa esencialmente que está pidiendo ver TODOS los valores posibles de [Precio por unidad] aunque lo que realmente desea es solo uno: el valor más reciente.

chabzjo
fuente
Gracias, pero cuando trato de ejecutar la consulta que compartió, aparece un error: "Error de sintaxis en la operación JOIN".
J. Taylor
Lo siento, no tuve tiempo para crear las tablas en Access. Supuse que tenías un poco de experiencia con las uniones ya que había algunas en tu pregunta. ¿Intentaste crearlo haciendo Crear -> Consulta en Access?
chabzjo
La primera consulta que compartió no da resultados correctos debido a la línea WHERE [Food purchase data].[Food item ID] = Invoices.ID... Supongo que quiso decir, WHERE [Food purchase data].[Invoice ID] = Invoices.[Invoice ID]pero aún así devuelve varias fechas por artículo de comida en lugar de solo la más reciente.
J. Taylor