2012-07-03 21 views
5

Tengo dos tablas en la base de datos de ventas de MySQL:MySQL dentro de los grupos agregados sin sub-consultas - sugirió datos de prueba actualizada

órdenes tabla:

CREATE TABLE salestest.`orders` ( 
`ID` int(11) unsigned NOT NULL auto_increment, 
`OrderDate` datetime NOT NULL, 
`CustomerID` int(11) unsigned NOT NULL, 
PRIMARY KEY (`ID`), 
UNIQUE KEY `ID` (`ID`), 
KEY `OrderDate` (`OrderDate`), 
KEY `CustomerID` (`CustomerID`) 
) ENGINE=InnoDB; 

INSERT INTO salestest.orders VALUES 
(1, '2012-04-15', 1), 
(2, '2012-05-20', 1), 
(3, '2012-06-30', 1); 

OrderDetails tabla:

CREATE TABLE salestest.`OrderDetails` ( 
`ID` int(11) unsigned NOT NULL auto_increment, 
`OrderID` int(11) unsigned NOT NULL, 
`ProductID` int(11) unsigned NOT NULL, 
`Price` double NOT NULL default '0', 
PRIMARY KEY (`ID`), 
UNIQUE KEY `ID` (`ID`), 
KEY `OrderID` (`OrderID`), 
KEY `ProductID` (`ProductID`), 
CONSTRAINT `OrderID_fk` FOREIGN KEY (`OrderID`) REFERENCES `orders` (`ID`) 
) ENGINE=InnoDB; 

INSERT INTO salestest.OrderDetails VALUES 
(1, 1, 1, 2), 
(2, 1, 2, 15), 
(3, 1, 3, 22), 
(4, 2, 1, 3), 
(5, 2, 2, 17), 
(6, 2, 3, 23), 
(7, 2, 4, 40), 
(8, 3, 1, 4), 
(9, 3, 2, 20); 

Ahora tengo que seleccionar para cada cliente el último precio que compra cada producto.

La manera más fácil de hacerlo es mediante el uso de una subconsulta:

SELECT od2.CustomerID,od2.ProductID, od2.Price AS LastPrice, od2.OrderDate AS LastDate 
FROM (SELECT o1.ID, o1.CustomerID, o1.OrderDate, od1.ProductID, od1.Price 
    FROM orders AS o1 
    LEFT JOIN OrderDetails as od1 ON o1.ID=od1.OrderID 
    ORDER BY OrderDate DESC) AS od2 
GROUP BY CustomerID, ProductID 
ORDER BY CustomerID, ProductID; 

Resultado:

CustomerID ProductID LastPrice LastDate
1 1 4 2012-06-30 00:00:00
1 2 20 2012-06-30 00:00:00
1 3 23 2012-05-20 00:00:00
1 4 40 2012-05-20 00:00:00

Ahora la pregunta; cómo es posible obtener el mismo resultado si quiero evitar sub-consultas, tablas temporales o una vista, solo quiero usar uniones; esta consulta es una pequeña parte de una consulta mucho más grande, y tener subconsulta es altamente ineficiente.

me trataron esta consulta:

SELECT o1.CustomerID, od1.ProductID, od1.Price AS LastPrice, o1.OrderDate AS LastDate
partir de las órdenes izquierdo como o1 ÚNETE OrderDetails como OD1 EN o1.ID = od1.OrderID
GROUP BY CustomerID, ProductID
ORDER BY CustomerID, ProductID;

pero da un resultado diferente:

CustomerID ProductID LastPrice LastDate
1 1 2 2012-04-15 00:00:00
1 2 15 2012-04-15 00: 00:00
1 3 22 2012-04-15 00:00:00
1 4 40 2012-05-20 00:00:00

Como se ve, LastPrice & LastDate no son correctos.También probé la sugerencia de Allen, pero el resultado es:

CustomerID ProductID LastPrice LastDate
1 1 4 2012-06-30 00:00:00
1 2 20 2012-06-30 00:00: 00

primera consulta de los resultados de respuesta productos duplicados de Spencer:

CustomerID ProductID LastPrice LastDate
1 3 22 2012-04-15 00 : 00: 00
1 3 23 2012-05-20 00:00:00
1 4 40 2012-05-20 00:00:00
1 1 4 2012-06-30 00:00:00
1 2 20 2012-06-30 00:00:00

otras respuestas todas usan la sub consulta, que estoy tratando de evitar.
alguna sugerencia?

Respuesta

0

ACTUALIZACIÓN:

no he sido capaz de reproducir el conjunto de resultados utilizando sólo une (sin necesidad de utilizar una vista en línea o una consulta correlacionada).

No creo que sea posible devolver de manera confiable el conjunto de resultados especificado sin usar una vista en línea o una subconsulta correlacionada.


Esto devuelve el resultado especificado, pero sin inlineview y no hay subconsultas. (Pero eso no quiere decir que esta sea la consulta "más rápida" que devuelve el conjunto de resultados.

NO TRABAJO ... Por favor standby

SELECT o1.CustomerID, d1.ProductID, d1.Price, o1.Orderdate 
    FROM orders o1 
    JOIN OrderDetails d1 ON d1.OrderID = o1.ID 
    LEFT  
    JOIN orders o2 
    ON o1.CustomerID = o2.CustomerID 
     AND o1.ID <> o2.ID 
     AND (o1.OrderDate < o2.OrderDate 
      OR (o1.OrderDate = o2.OrderDate AND o1.ID < o2.ID) 
      ) 
    LEFT 
    JOIN OrderDetails d2 
    ON d2.OrderID = o2.ID 
     AND d2.ProductID = d1.ProductId 
     AND (o1.OrderDate < o2.OrderDate 
      OR (o1.OrderDate = o2.OrderDate AND o1.ID < o2.ID) 
      OR (o1.OrderDate = o2.OrderDate AND o1.ID = o2.ID AND d1.ID < d2.ID) 
      ) 
WHERE d2.ID IS NULL 

Esta consulta se une a las tablas a sí mismos y filtra la fila "superior" para cada grupo.

-

Conceptualmente, esta consulta es la misma que la siguiente consulta. La siguiente consulta utiliza una "vista en línea" (con alias a y b). El objetivo de la vista en línea es simplemente obtener CustomerID y OrderDate asociados con cada línea de OrderDetail.

SELECT a.CustomerID, a.ProductID, a.Price, a.Orderdate 
    FROM (SELECT o1.CustomerID, d1.ProductID, d1.Price, o1.OrderDate, d1.OrderID, d1.ID 
      FROM orders o1 
      JOIN OrderDetails d1 ON d1.OrderID = o1.ID 
     ) a 
    LEFT  
    JOIN (SELECT o2.CustomerID, d2.ProductID, d2.Price, o2.OrderDate, d2.OrderID, d2.ID 
      FROM orders o2 
      JOIN OrderDetails d2 ON d2.OrderID = o2.ID 
     ) b 
    ON a.CustomerID = b.CustomerID 
     AND a.ProductID = b.ProductId 
     AND a.OrderID <> b.OrderID 
     AND a.ID <> b.ID 
     AND (a.OrderDate < b.OrderDate 
      OR (a.OrderDate = b.OrderDate AND a.OrderID < b.OrderID) 
      OR (a.OrderDate = b.OrderDate AND a.OrderID = b.OrderID AND a.ID < b.ID)) 
    WHERE b.ID IS NULL 

Usaremos una expresión de tabla común (CTE) en lugar de las vistas en línea, si MySQL las admite.


Por último, aquí es un enfoque totalmente diferente, que utiliza variables de usuario "" MySQL para simular las funciones analíticas que faltan en MySQL.

SELECT q.CustomerID 
    , q.ProductID 
    , q.Price 
    , q.OrderDate 
    FROM (SELECT IF(p.CustomerID = @last_customerid,IF(p.ProductID = @last_productid,0,1),1) AS break 
      , @last_customerid := p.CustomerID AS CustomerID 
      , @last_productid := p.ProductID AS ProductID 
      , p.Price 
      , p.OrderDate 
     FROM (SELECT @last_customerid := NULL, @last_productid := NULL) i 
     JOIN (SELECT o.CustomerID, d.ProductID, o.OrderDate, d.Price 
        FROM orders o 
        JOIN OrderDetails d ON d.OrderID = o.ID 
       ORDER BY o.CustomerID, d.ProductID, o.OrderDate DESC 
      ) p 
     ) q 
    WHERE q.break = 1 

+0

código muy estructurado y muy limpio, ¡gracias! ¿Es posible reescribirlo para hacer lo mismo sin ninguna subconsulta? – user1499268

+0

la primera consulta muestra clientes/productos duplicados en el resultado, no solo la última compra. Puedes probar los nuevos datos que publiqué – user1499268

+0

@user: estoy trabajando con los nuevos datos que publicaste. La segunda consulta en mi respuesta (que tiene las vistas en línea) parece devolver el conjunto de resultados correcto. Con los nuevos datos, veo que la primera consulta en mi respuesta es devolver un conjunto de resultados incorrecto, estoy tratando de encontrar lo que está mal con él. Esa es la última que escribí, como un intento de reescribir la consulta usando las vistas en línea (básicamente para obtener el CustomerID y el OrderDate en el nivel OrderDetails). – spencer7593

0
select B.*,A.Price from 
(select CustomerID,ProductID,OrderDate,Price from Orders o join OrderDetails od on o.ID=od.OrderID) A 
join 
(select CustomerID,ProductID,max(OrderDate) as LastOrderDate 
from Orders o 
join OrderDetails od on o.ID=od.OrderID 
group by CustomerID,ProductID) B 
on A.CustomerID=B.CustomerID and A.ProductID=B.ProductID and A.OrderDate=B.LastOrderDate 
+0

¿Hay alguna forma de hacerlo sin sub-consulta? – user1499268

+0

Las dos consultas A y B se evalúan de forma independiente y la selección externa se realiza en la combinación de A y B, que es pequeña. No es como una consulta tipo bucle dentro de un bucle de la que debería preocuparse. – Dojo

1

Busque "greatest-n-per-group"

Es lo más grande que he aprendido en SQL, espero que les encanta también.

OK, aquí está mi puñalada en ella:

SELECT o.CustomerID, od.ProductID, od.Price AS LastPrice, o.OrderDate AS LastDate 
FROM OrderDetails od 
LEFT JOIN orders as o ON od.OrderID = o.ID 
LEFT JOIN orders as o2 ON o.CustomerID = o2.CustomerID AND o.id < o2.id 
WHERE o2.id IS NULL 
ORDER BY o.CustomerID, od.ProductID; 

¿Quieres saber, por el cliente + producto, lo que la última vez que el cliente compró cada producto y lo que pagó por ella.

Así que comencé con el producto, me uní a los pedidos (primera unión), luego volví a unir pedidos para poder limitar la consulta a un único pedido por cliente + producto (o2 coincide con todos los pedidos pero sin incluir el pedido más reciente). Luego usamos el hecho de que o2 no coincide con el orden más reciente para seleccionar solo esa fila.

Esto supone que no tendrá el mismo artículo dos veces en un orden con precios diferentes y que los pedidos más nuevos siempre tendrán un ID más alto.

Espero que esto lo acerque lo suficiente como para que sus datos/consultas reales puedan modificarse según sea necesario - ¡Buena suerte!

+0

+1 para la "n por grupo" – Aprillion

+0

Gracias Allen, el único problema en su solución es cuando el cliente compra productos 1,2,3,4 hoy, y luego compra productos 1,2 mañana. en este caso, su consulta solo arrojará información sobre productos 1,2, pero nada sobre 3,4. – user1499268

+0

Pruebe estos datos para probar: INSERT INTO salestest.orders VALORES (1, '2012-04-15', 1), (2, '2012-05-20', 1), (3, '2012 -06-30 ', 1); INSERT INTO salestest.orderDetails VALUES (1, 1, 1, 2), (2, 1, 2, 15), (3, 1, 3, 22), (4, 2, 1, 3) , (5, 2, 2, 17), (6, 2, 3, 23), (7, 2, 4, 40), (8, 3, 1, 4), (9, 3 , 2, 20); – user1499268

0

Usted puede encontrar esto más eficiente:

select opl.CustomerID, 
    opl.ProductID, 
    opl.LastDate, 
    od.Price 
from (
    select o.CustomerID, 
     od.ProductID, 
     max(o.OrderDate) as LastDate 
    from Orders o 
    inner join OrderDetails od on o.ID = od.OrderID 
    group by o.CustomerID, od.ProductID 
) opl 
inner join Orders o on opl.CustomerID = o.CustomerID 
    and opl.LastDate = o.OrderDate 
inner join OrderDetails od on o.ID = od.OrderID 
Cuestiones relacionadas