2010-09-01 9 views
45

Ho un cliente della tabella che memorizza un customer_id, un email e un riferimento. C'è una tabella aggiuntiva customer_data che memorizza un record storico delle modifiche apportate al cliente, cioè quando viene apportata una modifica viene inserita una nuova riga.MySQL UNISCITI solo alla riga più recente?

Per visualizzare le informazioni sul cliente in una tabella, è necessario unire le due tabelle, tuttavia è necessario unire solo la riga più recente di customer_data alla tabella del cliente.

Diventa un po 'più complicato in quanto la query è impaginata, quindi ha un limite e un offset.

Come posso fare questo con MySQL? Penso di voler mettere un DISTINCT da qualche parte ...

La query al minuto è come questo-

SELECT *, CONCAT(title,' ',forename,' ',surname) AS name 
FROM customer c 
INNER JOIN customer_data d on c.customer_id=d.customer_id 
WHERE name LIKE '%Smith%' LIMIT 10, 20 

Additionaly, ho ragione nel pensare che posso usare CONCAT con LIKE in per di qua?

(Mi rendo conto che INNER JOIN potrebbe essere il tipo sbagliato di JOIN da usare. Io in realtà ho idea di quale sia la differenza tra le diverse join. Ho intenzione di guardare in quella ora!)

+0

che modo la tabella di storia del cliente assomiglia? Come viene determinata la riga più recente? C'è un campo temporale? –

+0

Più recente è semplicemente l'ultima riga inserita, quindi la sua chiave primaria è il numero più alto. – bcmcfc

+0

Perché non un trigger? dai un'occhiata a questa risposta: http://stackoverflow.com/questions/26661314/best-and-optimal-way-to-join-max-value-from-other-table/26664982#26664982 –

risposta

76

Si consiglia di provare il seguente:

SELECT CONCAT(title, ' ', forename, ' ', surname) AS name 
FROM  customer c 
JOIN  (
       SELECT MAX(id) max_id, customer_id 
       FROM  customer_data 
       GROUP BY customer_id 
     ) c_max ON (c_max.customer_id = c.customer_id) 
JOIN  customer_data cd ON (cd.id = c_max.max_id) 
WHERE  CONCAT(title, ' ', forename, ' ', surname) LIKE '%Smith%' 
LIMIT  10, 20; 

Nota che un JOIN è solo sinonimo di INNER JOIN. caso

prova:

CREATE TABLE customer (customer_id int); 
CREATE TABLE customer_data (
    id int, 
    customer_id int, 
    title varchar(10), 
    forename varchar(10), 
    surname varchar(10) 
); 

INSERT INTO customer VALUES (1); 
INSERT INTO customer VALUES (2); 
INSERT INTO customer VALUES (3); 

INSERT INTO customer_data VALUES (1, 1, 'Mr', 'Bobby', 'Smith'); 
INSERT INTO customer_data VALUES (2, 1, 'Mr', 'Bob', 'Smith'); 
INSERT INTO customer_data VALUES (3, 2, 'Mr', 'Jane', 'Green'); 
INSERT INTO customer_data VALUES (4, 2, 'Miss', 'Jane', 'Green'); 
INSERT INTO customer_data VALUES (5, 3, 'Dr', 'Jack', 'Black'); 

Risultato (query senza la LIMIT e WHERE):

SELECT CONCAT(title, ' ', forename, ' ', surname) AS name 
FROM  customer c 
JOIN  (
       SELECT MAX(id) max_id, customer_id 
       FROM  customer_data 
       GROUP BY customer_id 
     ) c_max ON (c_max.customer_id = c.customer_id) 
JOIN  customer_data cd ON (cd.id = c_max.max_id); 

+-----------------+ 
| name   | 
+-----------------+ 
| Mr Bob Smith | 
| Miss Jane Green | 
| Dr Jack Black | 
+-----------------+ 
3 rows in set (0.00 sec) 
+1

Grazie per il livello di dettaglio in cui sei entrato. Spero che aiuti gli altri e solo me! – bcmcfc

+6

A lungo termine questo approccio potrebbe creare problemi di prestazioni in quanto sarebbe necessario creare una tabella temporanea. Quindi un'altra soluzione (se possibile) consiste nell'aggiungere un nuovo campo booleano (is_last) in customer_data che dovresti aggiornare ogni volta che viene aggiunta una nuova voce. L'ultima voce avrà is_last = 1, tutte le altre per questo cliente - is_last = 0. – cephuo

+0

Le persone dovrebbero (per favore) leggere anche la seguente risposta (da Danny Coulombe), perché questa risposta (mi dispiace Daniel) è terribilmente lenta con query più lunghe/più dati. Ha reso la mia pagina "aspetta" per 12 secondi da caricare; Quindi, per favore controlla anche https://stackoverflow.com/a/35965649/2776747. Non l'ho notato fino a dopo un sacco di altri cambiamenti, quindi mi ci è voluto molto tempo per scoprirlo. – Art

0
SELECT CONCAT(title,' ',forename,' ',surname) AS name * FROM customer c 
INNER JOIN customer_data d on c.id=d.customer_id WHERE name LIKE '%Smith%' 

Penso che avete bisogno di cambiare c.customer_id a c.id

altra struttura della tabella di aggiornamento

+0

Ho downvoted perché ho interpretato male la tua risposta e inizialmente ho pensato che fosse sbagliato. La fretta è un cattivo consigliere :-) – Wirone

0

E 'una buona idea che la registrazione dei dati effettivi in ​​"customer_data" tavolo. Con questi dati puoi selezionare tutti i dati dalla tabella "customer_data" come desideri.

10

Presumendo la colonna autoincrement in customer_data si chiama Id, si può fare:

SELECT CONCAT(title,' ',forename,' ',surname) AS name * 
FROM customer c 
    INNER JOIN customer_data d 
     ON c.customer_id=d.customer_id 
WHERE name LIKE '%Smith%' 
    AND d.ID = (
       Select Max(D2.Id) 
       From customer_data As D2 
       Where D2.customer_id = D.customer_id 
       ) 
LIMIT 10, 20 
7

Per chi deve lavorare con un versione precedente di MySQL (pre-5.0 ish) non è possibile eseguire interrogazioni secondarie per questo tipo di query. Ecco la soluzione che ero in grado di fare e sembrava funzionare alla grande.

SELECT MAX(d.id), d2.*, CONCAT(title,' ',forename,' ',surname) AS name 
FROM customer AS c 
LEFT JOIN customer_data as d ON c.customer_id=d.customer_id 
LEFT JOIN customer_data as d2 ON d.id=d2.id 
WHERE CONCAT(title, ' ', forename, ' ', surname) LIKE '%Smith%' 
GROUP BY c.customer_id LIMIT 10, 20; 

Essenzialmente questo è trovare l'id massimo della tabella di dati che unisce al cliente poi unirsi alla tabella di dati per l'id max trovato. La ragione di ciò è dovuta al fatto che selezionare il massimo di un gruppo non garantisce che il resto dei dati corrisponda all'ID, a meno che non si unisca nuovamente a se stesso.

Non ho provato questo su nuove versioni di MySQL ma funziona su 4.0.30.

+0

Questo è squisito nella sua semplicità. Perché è la prima volta che vedo questo approccio? Si noti che 'EXPLAIN' indica che questo utilizza una tabella temporanea e un filesort. L'aggiunta di 'ORDER BY NULL' alla fine elimina il fileort. – Timo

+0

Con mio rammarico, la mia soluzione, non così bella, è 3,5 volte più veloce per i miei dati. Ho usato un sottoquery per selezionare la tabella principale più gli ID più recenti delle tabelle unite, quindi una query esterna che seleziona il sottoquery e legge i dati effettivi dalle tabelle unite. Sto unendo 5 tabelle sulla tabella principale e collaudo con una condizione where che seleziona 1000 record. Gli indici sono ottimali. – Timo

+0

Stavo usando la tua soluzione con 'SELECT *, MAX (firstData.id), MAX (secondData.id) [...]'. Logicamente, passando a 'SELECT main. *, FirstData2. *, SecondData2. *, MAX (firstData.id), MAX (secondData.id), [...]' Sono riuscito a renderlo significativamente più veloce. Ciò consente ai primi join di leggere solo dall'indice, anziché dover leggere tutti i dati dall'indice primario. Ora la soluzione carina richiede solo 1,9 volte il tempo della soluzione basata su subquery. – Timo

31

Se si lavora con query pesanti, è meglio spostare la richiesta per l'ultima riga nella clausola where. È molto più veloce e sembra più pulito.

SELECT c.*, 
FROM client AS c 
LEFT JOIN client_calling_history AS cch ON cch.client_id = c.client_id 
WHERE 
    cch.cchid = (
     SELECT MAX(cchid) 
     FROM client_calling_history 
     WHERE client_id = c.client_id AND cal_event_id = c.cal_event_id 
    ) 
+3

Wow Sono quasi incredulo di quanto sia una differenza di prestazioni. Non sono sicuro del perché sia ​​stato così drastico, ma finora è stato così tanto più veloce che mi è sembrato di incasinare da qualche altra parte ... –

+1

Vorrei davvero poterlo fare più di una volta così che venga visto di più. L'ho testato un po 'e in qualche modo rende le mie query praticamente istantanee (WorkBench dice letteralmente 0.000 secondi, anche con 'sql_no_cache set'), mentre la ricerca nel join richiedeva più secondi per essere completata. Ancora sconcertato, ma voglio dire che non puoi litigare con risultati del genere. –

+0

Non so neanche perché sia ​​più veloce, ma ho sentito che MySQL parte sempre dal basso. Quindi probabilmente la query "SELEZIONA MAX" continua semplicemente dov'era e esegue solo poche righe a causa dell'indice ID invece di essere ripetuta ogni volta quando viene inserita in un JOIN. –

0

È anche possibile fare questo

SELECT CONCAT(title, ' ', forename, ' ', surname) AS name 
FROM  customer c 
LEFT JOIN (
       SELECT * FROM customer_data ORDER BY id DESC 
     ) customer_data ON (customer_data.customer_id = c.customer_id) 
GROUP BY c.customer_id   
WHERE  CONCAT(title, ' ', forename, ' ', surname) LIKE '%Smith%' 
LIMIT  10, 20; 
Problemi correlati