2012-07-03 7 views
5

Ho due tabelle nel database delle vendite MySQL:MySQL intra-gruppo aggrega con sotto-query - ha suggerito dati di test aggiornati

Ordini tavolo:

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 tavolo:

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

Ora devo selezionare per ogni cliente, l'ultimo prezzo che acquistano ogni prodotto.

Il modo più semplice per farlo è quello di utilizzare una sottoquery:

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; 

Risultato:

CustomerID ProductID LastPrice ultimoData
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

Ora la domanda; come è possibile ottenere lo stesso risultato se voglio evitare sub-query, tabelle temporanee o una vista, voglio solo usare i join; questa query è una piccola parte di una query molto più ampia e la sub-query è altamente inefficiente.

Ho provato questa query:

SELEZIONA o1.CustomerID, od1.ProductID, od1.Price AS LastPrice, o1.OrderDate AS ultimoData
DA Ordini sinistra come o1 ENTRA OrderDetails come OD1 ON o1.ID = od1.OrderID
GROUP BY CustomerID, ID prodotto
ORDINA PER ID cliente, ID prodotto;

ma dà un risultato diverso:

CustomerID ProductID LastPrice ultimoData
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

Come si vede, LastPrice & LastDate non sono corretti.Ho anche provato il suggerimento di Allen, ma il risultato è:

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

prima query dai risultati di risposta prodotti duplicati di Spencer:

CustomerID ProductID LastPrice ultimoData
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

altre risposte tutte utilizzano sub-query, che sto cercando di evitare.
qualche suggerimento?

risposta

0

UPDATE:

sono stato in grado di riprodurre il set di risultati utilizzando unisce solo (senza l'utilizzo di una vista in linea o una subquery correlata).

Non penso sia possibile restituire in modo affidabile il set di risultati specificato senza utilizzare una vista incorporata o una sottoquery correlata.


Questo restituisce il risultato specificato, ma senza inlineview e senza sottoquery. (Ma questo non vuol dire che questa sarà la query "più veloce" che restituisce il set di risultati.

NON LAVORO ... Per favore 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 

Questa interrogazione unisce le tabelle a se stessi e filtra la riga "più in alto" per ogni gruppo.

-

Concettualmente, la query è la stessa come la seguente query. La seguente query utilizza una "visualizzazione incorporata" (con alias come a e b). Lo scopo della vista in linea è in realtà solo per ottenere CustomerID e OrderDate associati a ciascuna riga 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 

Ci piacerebbe usare un'espressione di tabella comune (CTE) al posto dei punti di vista in linea, se MySQL li sosteneva.


Infine, qui è un approccio completamente diverso, che utilizza MySQL "variabili utente" per simulare le funzioni analitiche che mancano dalla 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

codice molto strutturato e molto pulito, grazie! È possibile riscriverlo per fare lo stesso senza sub-query? – user1499268

+0

la prima query mostra cliente/prodotto duplicato nel risultato, non solo l'ultimo acquisto. puoi testare i nuovi dati che ho postato – user1499268

+0

@user: sto lavorando con i nuovi dati che hai postato. La seconda query nella mia risposta (che ha le viste in linea) sembra restituire il set di risultati corretto. Con i nuovi dati, vedo che la prima query nella mia risposta sta restituendo un set di risultati errato, sto cercando di scoprire cosa c'è che non va. Questo è l'ultimo che ho scritto, come tentativo di riscrivere la query utilizzando le viste in linea (fondamentalmente per ottenere CustomerID e OrderDate al livello 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

Esiste un modo per farlo senza sub-query? – user1499268

+0

Le due query A e B vengono valutate in modo indipendente e la selezione esterna avviene sulla join di A & B che è piccola. Non è come una specie di query loop-inside-a-loop di cui dovresti preoccuparti. – Dojo

1

Cercare "greatest-n-per-group"

E 'la cosa più grande che io abbia mai imparato in SQL, spero lo si ama troppo.

OK, ecco il mio pugnalata a lui:

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; 

Volete sapere, da parte del cliente + del prodotto, ciò che l'ultima volta che il cliente ha comprato ogni prodotto e quello che hanno pagato per questo.

Così ho iniziato con il prodotto, ho unito gli ordini (primo join), quindi ho ricominciato gli ordini in modo da poter limitare la query a un singolo ordine per cliente + prodotto (o2 corrisponde a tutti gli ordini fino a, ma non incluso ordine più recente). Usiamo quindi il fatto che o2 non corrisponde all'ordine più recente per selezionare solo quella riga.

Questo presuppone che non si avrà lo stesso articolo due volte in un unico ordine con prezzi diversi e che gli ordini più recenti avranno sempre un ID più alto.

Speriamo che questo ti avvicini abbastanza che i tuoi dati reali/query possono modificare se necessario - Buona fortuna!

+0

+1 per "n per gruppo" – Aprillion

+0

Grazie Allen, l'unico problema nella tua soluzione è quando i clienti acquistano prodotti 1,2,3,4 oggi, quindi domani acquista i prodotti 1,2. in questo caso la tua query restituirà solo informazioni sui prodotti 1,2, ma nulla su 3,4. – user1499268

+0

Prova questi dati per il test: INSERISCI IN SALESTEST.ORDERS VALORI (1, '2012-04-15', 1), (2, '2012-05-20', 1), (3, '2012 -06-30 ', 1); INSERTO in salestest.orderDetails VALORI (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

Potete trovare questo più efficiente:

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 
Problemi correlati