155 votes

MySQL JOIN la ligne la plus récente seulement ?

J'ai une table customer qui stocke l'identifiant du client, l'email et la référence. Il y a une table supplémentaire customer_data qui stocke un historique des modifications apportées au client, c'est-à-dire que lorsqu'une modification est apportée, une nouvelle ligne est insérée.

Afin d'afficher les informations sur les clients dans un tableau, les deux tableaux doivent être reliés, mais seule la ligne la plus récente de customer_data doit être reliée au tableau customer.

La situation est un peu plus compliquée, car la requête est paginée et comporte donc une limite et un décalage.

Comment puis-je faire cela avec MySQL ? Je pense que je veux mettre un DISTINCT quelque part...

L'interrogation actuelle est la suivante

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

En outre, ai-je raison de penser que je peux utiliser CONCAT avec LIKE de cette manière ?

(Je suis conscient que INNER JOIN n'est peut-être pas le bon type de JOIN à utiliser. En fait, je n'ai aucune idée de la différence entre les différents JOIN. Je vais me pencher sur la question maintenant).

205voto

Danny Coulombe Points 506

Si vous travaillez avec des requêtes lourdes, vous feriez mieux de déplacer la demande de la dernière ligne dans la clause where. C'est beaucoup plus rapide et plus propre.

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
   )

193voto

Daniel Vassallo Points 142049

Vous pouvez essayer ce qui suit :

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;

Il convient de noter qu'un JOIN n'est qu'un synonyme de INNER JOIN .

Cas de test :

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

Résultat (requête sans le LIMIT y 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)

12voto

Thomas Points 42973

En supposant que la colonne d'autoincrémentation de customer_data est nommé Id vous pouvez faire :

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

9voto

payne8 Points 668

Pour tous ceux qui travaillent avec une ancienne version de MySQL (avant la version 5.0), il n'est pas possible de faire des sous-requêtes pour ce type de requête. Voici la solution que j'ai pu mettre en place et qui semble fonctionner à merveille.

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;

Il s'agit essentiellement de trouver l'identifiant maximum de votre table de données, de l'associer au client, puis d'associer la table de données à l'identifiant maximum trouvé. La raison en est que la sélection de l'identifiant maximal d'un groupe ne garantit pas que le reste des données corresponde à l'identifiant, à moins que vous ne le joigniez à lui-même.

Je n'ai pas testé cette méthode sur des versions plus récentes de MySQL, mais elle fonctionne sur la version 4.0.30.

7voto

Benjamin Points 7314

Je sais que cette question est ancienne, mais elle a fait l'objet de beaucoup d'attention au fil des ans et je pense qu'il manque un concept qui pourrait aider quelqu'un dans un cas similaire. Je l'ajoute ici par souci d'exhaustivité.

Si vous ne pouvez pas modifier le schéma original de votre base de données, beaucoup de bonnes réponses ont été fournies et résolvent parfaitement le problème.

Si usted peut Cependant, si vous modifiez votre schéma, je vous conseille d'ajouter un champ dans votre customer qui contient le id de la dernière customer_data pour ce client :

CREATE TABLE customer (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  current_data_id INT UNSIGNED NULL DEFAULT NULL
);

CREATE TABLE customer_data (
   id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
   customer_id INT UNSIGNED NOT NULL, 
   title VARCHAR(10) NOT NULL,
   forename VARCHAR(10) NOT NULL,
   surname VARCHAR(10) NOT NULL
);

Interroger les clients

L'interrogation est aussi simple et rapide que possible :

SELECT c.*, d.title, d.forename, d.surname
FROM customer c
INNER JOIN customer_data d on d.id = c.current_data_id
WHERE ...;

L'inconvénient est la complexité supplémentaire lors de la création ou de la mise à jour d'un client.

Mise à jour d'un client

Lorsque vous souhaitez mettre à jour un client, vous insérez un nouvel enregistrement dans le fichier customer_data et mettre à jour le tableau customer enregistrer.

INSERT INTO customer_data (customer_id, title, forename, surname) VALUES(2, 'Mr', 'John', 'Smith');
UPDATE customer SET current_data_id = LAST_INSERT_ID() WHERE id = 2;

Création d'un client

Pour créer un client, il suffit d'insérer l'élément customer puis en exécutant les mêmes déclarations :

INSERT INTO customer () VALUES ();

SET @customer_id = LAST_INSERT_ID();
INSERT INTO customer_data (customer_id, title, forename, surname) VALUES(@customer_id, 'Mr', 'John', 'Smith');
UPDATE customer SET current_data_id = LAST_INSERT_ID() WHERE id = @customer_id;

Conclusion

La complexité supplémentaire liée à la création/mise à jour d'un client peut être redoutable, mais elle peut facilement être automatisée à l'aide de déclencheurs.

Enfin, si vous utilisez un ORM, cela peut être très facile à gérer. L'ORM peut se charger d'insérer les valeurs, de mettre à jour les identifiants et de joindre les deux tables automatiquement pour vous.

Voici comment votre mutable Customer ressemblerait à ce qui suit :

class Customer
{
    private int id;
    private CustomerData currentData;

    public Customer(String title, String forename, String surname)
    {
        this.update(title, forename, surname);
    }

    public void update(String title, String forename, String surname)
    {
        this.currentData = new CustomerData(this, title, forename, surname);
    }

    public String getTitle()
    {
        return this.currentData.getTitle();
    }

    public String getForename()
    {
        return this.currentData.getForename();
    }

    public String getSurname()
    {
        return this.currentData.getSurname();
    }
}

Et votre immuable CustomerData qui ne contient que des getters :

class CustomerData
{
    private int id;
    private Customer customer;
    private String title;
    private String forename;
    private String surname;

    public CustomerData(Customer customer, String title, String forename, String surname)
    {
        this.customer = customer;
        this.title    = title;
        this.forename = forename;
        this.surname  = surname;
    }

    public String getTitle()
    {
        return this.title;
    }

    public String getForename()
    {
        return this.forename;
    }

    public String getSurname()
    {
        return this.surname;
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X