Archives par étiquette : delete

MySQL – update et delete avec des jointures

Le but de ce billet est de réaliser des opérations de mise à jour ou de suppression à l’aide d’une jointure, externe ou interne.
Voici le schéma qui nous sert de base de travail :

  • une table client
  • une table produit
  • une table commande, avec deux contraintes de clé étrangère partant vers chacune des tables

Hoan_Bridge

Le SQL du schéma

create table client(
id tinyint unsigned primary key auto_increment,
nom varchar(30) not null,
prenom varchar(20) not null);

insert into client (nom, prenom) values 
('Ferrandez', 'Sébastien'), ('Jambon', 'Paul');

create table produit(
id tinyint unsigned primary key auto_increment,
designation varchar(30) not null,
prix float (6,2) unsigned not null);

insert into produit (designation, prix) values 
('Mousse à raser', 7.99), ('Stylo BIC', 4.99);

create table commande (
id tinyint unsigned primary key auto_increment, 
id_client tinyint unsigned , 
id_produit tinyint unsigned, 
quantite tinyint unsigned default 1,
date timestamp);

alter table commande add constraint foreign key (id_client) 
references client(id);

alter table commande add constraint foreign key (id_produit)
references produit(id);

insert into commande(id_client,id_produit, quantite )
values (1,1,2), (1,2,1), (2,2,1), (2,1,2);

Les jointures, externes et internes

Le principe de la jointure est trivial, on crée comme une sorte de « pont » entre au moins 2 tables selon une condition qu’on va très simplement appeler « condition de jointure ». Une condition de jointure n’est pas forcément une clause d’égalité !

Prenons l’exemple de deux tables sans rapport quelconque :

create table eleve (nom varchar(20), age tinyint unsigned);
insert into eleve values ('Ferrandez', 10), ('Lucas', 30);
create table produit (nom varchar(20), prix float(6,2) unsigned);
insert into produit values ('iPhone5', 499), ('Jambon', 10);

Qu’est-ce qui nous empêche de faire une jointure entre un élève et un produit basé sur l’égalité de valeur de l’âge de l’élève et du prix du produit ?

SELECT * 
FROM  eleve 
INNER JOIN produit ON ( eleve.age = produit.prix ) 

Nous obtenons

+-----------+------+--------+-------+
| nom       | age  | nom    | prix  |
+-----------+------+--------+-------+
| Ferrandez |   10 | Jambon | 10.00 |
+-----------+------+--------+-------+
1 row in set (0.00 sec)

Une jointure ne se fait pas uniquement sur la base d’une égalité de valeur, on peut utiliser d’autres opérateurs arithmétiques :

SELECT * 
FROM  eleve
INNER JOIN produit ON ( eleve.age <= produit.prix ) 

Celle-ci remontera les tuples suivants :

+-----------+------+---------+--------+
| nom       | age  | nom     | prix   |
+-----------+------+---------+--------+
| Ferrandez |   10 | iPhone5 | 499.00 |
| Lucas     |   30 | iPhone5 | 499.00 |
| Ferrandez |   10 | Jambon  |  10.00 |
+-----------+------+---------+--------+
3 rows in set (0.00 sec)   

Et pire, qu’est-ce qui nous empêche de faire une jointure sur des champs qui n’ont pas le même type de données (horreur !) :

SELECT * 
FROM  eleve
INNER JOIN produit ON ( eleve.age <= produit.nom ) 

Si on a le droit de faire ça, c’est que l’optimiseur MySQL effectue des conversions de type pour effectuer des comparaisons (différence, égalité etc.). Mais recentrons-nous sur le sujet du billet ! Une requête de jointure externe, contrairement aux internes, retourne à la fois les correspondances (c’est à dire, les enregistrements satisfaisant la clause de jointure) et les non-correspondances (celles qui ne la satisfont pas). Voyons ce que donne :

SELECT * 
FROM  eleve 
LEFT JOIN produit ON ( eleve.age = produit.prix ) 
+-----------+------+--------+-------+
| nom       | age  | nom    | prix  |
+-----------+------+--------+-------+
| Ferrandez |   10 | Jambon | 10.00 |
| Lucas     |   30 | NULL   |  NULL |
+-----------+------+--------+-------+
2 rows in set (0.00 sec)

Les non-correspondances sont signalées par la présence du marqueur NULL. Si vous choisissez de les remonter, rien de plus facile :

SELECT eleve.*
FROM  eleve 
LEFT JOIN produit ON ( eleve.age = produit.prix ) 
WHERE produit.prix IS NULL

Notez bien que le même test, mais sur produit.nom IS NULL aurait également fonctionné !
Bref, nous savons maintenant ce qu’est une jointure interne (que ce qui correspond) et une jointure externe (ce qui correspond + ce qui ne correspond pas).

La mise à jour

Nous allons mettre à jour les commandes du client Sébastien Ferrandez en disant qu’elles ont eu lieu le 24 Décembre 2013 à 23:59:59 :

UPDATE commande INNER JOIN client
ON (commande.id_client = client.id)
SET commande.date = '2013-12-24 23:59:59'
WHERE client. nom = 'ferrandez'
AND client.prenom = 'sebastien'

Notez que nous sommes en collation latin1_general_ci et que nous ne tenons donc compte ni des accents ni des majuscules…
Si d’aventure vous souhaitez modifier d’autres champs (y compris dans la table client, pas que commande !), séparez les dans SET avec des virgules !

Vous pouvez très bien utiliser la jointure externe pour mettre a jour des enregistrements ne se trouvant pas dans la table commande (nous aurions par exemple dans la table client un champ booléen A_DEJA_COMMANDE que nous mettrions à 0 si aucune commande n’était trouvée pour celui-ci).

La suppression

Insérons un nouveau tuple dans client :

insert into client (nom, prenom) values ('Diego', 'Miguel');

Et effectuons sa suppression immédiate en disant « J’efface les clients qui n’ont pas passé de commande »

DELETE client.* FROM client
LEFT OUTER JOIN commande 
ON ( client.id = commande.id_client )
WHERE commande.id_client IS NULL

N’oubliez pas que vous n’êtes absolument pas limités à UNE jointure et que vous pouvez très bien alterner jointures externes et internes dans la même requête ! Entraînez-vous sur des petits schémas tout simples du même genre que celui que nous avons vu dans ce billet et tout ça n’aura bientôt plus de secret pour vous !

MongoDB : les bases pour bien débuter (3/3)

Revenons sur la collection MongoDB qui nous sert d’exemple depuis le début et effectuons quelques opérations de mise à jour élémentaires. Voici donc pour rappel à quoi ressemble notre collection :

sebastien.ferrandez@sebastien:~$ mongo
MongoDB shell version: 2.0.6
connecting to: test
> show dbs;
admin   0.203125GB
gens    0.203125GB
local   (empty)
> use gens;
switched to db gens
> db.gens.find();
{ "_id" : ObjectId("517a850cb1d6ce34f91af2d1"), "nom" : "ferrandez" }
{ "_id" : ObjectId("517a851eb1d6ce34f91af2d2"), "nom" : "ferrandez", "prenom" : "léo" }
{ "_id" : ObjectId("517a856bb1d6ce34f91af2d4"), "nom" : "ferrandez", "prenom" : "maïa", "age" : 6 }

mongo-db-logo

Opérations de mise à jour

Avec inc

Supposons qu’il nous faille mettre à jour l’âge d’une personne : aujourd’hui c’est l’anniversaire de Maïa, elle a 7 ans ! Voici plusieurs façons de faire : tout d’abord nous incrémentons la clé age de nos documents pour lesquels age est supérieur à 5.

db.gens.update({ age:{$gt: 5}}, {$inc: {age: 1}});

Avec set

Nous ciblons uniquement le prénom

db.gens.update({ prenom: "maïa"}, {$set: {age: 7}});

Nous faisons un mélange des deux précédentes requêtes :

db.gens.update({ age:{$gt: 5}}, {$set: {age: 7}});

Opérations de suppression

Suppression par ObjectId

> db.gens.find();
{ "_id" : ObjectId("517a850cb1d6ce34f91af2d1"), "nom" : "ferrandez" }
{ "_id" : ObjectId("517a851eb1d6ce34f91af2d2"), "nom" : "ferrandez", "prenom" : "léo" }
{ "_id" : ObjectId("517e33cf4937f8d068f9e9aa"), "nom" : "ferrandez", "prenom" : "maïa", "age" : 7 }
> db.gens.remove( {"_id": ObjectId("517e33cf4937f8d068f9e9aa")});
> db.gens.find();
{ "_id" : ObjectId("517a850cb1d6ce34f91af2d1"), "nom" : "ferrandez" }
{ "_id" : ObjectId("517a851eb1d6ce34f91af2d2"), "nom" : "ferrandez", "prenom" : "léo" }

Suppression par champ quelconque

Supprimons tous les gens qui s’appellent « ferrandez » :

> db.gens.find();
{ "_id" : ObjectId("517a850cb1d6ce34f91af2d1"), "nom" : "ferrandez" }
{ "_id" : ObjectId("517a851eb1d6ce34f91af2d2"), "nom" : "ferrandez", "prenom" : "léo" }
> db.gens.remove({nom: 'ferrandez'});
> db.gens.find();

Notez que cette fois-ci je n’ai pas mis de guillemets autour de la clé (nom) et j’ai mis des apostrophes autour du nom pour que vous voyez bien qu’il n’est pas obligatoire de mettre tout ça entre guillemets systématiquement !

Suppression de la première occurrence seulement

En mettant justOne à 1 (le premier paramètre), seul le premier document satisfaisant aux critères sera supprimé :

> db.gens.find();
{ "_id" : ObjectId("517e36c14937f8d068f9e9ab"), "nom" : "ferrandez", "prenom" : "maïa", "age" : 7 }
{ "_id" : ObjectId("517e36e54937f8d068f9e9ac"), "nom" : "ferrandez", "prenom" : "sébastien" }
> db.gens.remove({nom: 'ferrandez'}, 1);
> db.gens.find();
{ "_id" : ObjectId("517e36e54937f8d068f9e9ac"), "nom" : "ferrandez", "prenom" : "sébastien" }

Suppression de l’intégralité des documents d’une collection

> db.gens.remove();

Voilà ! Pour ceux d’entre vous qui ont déjà des connaissances en langage SQL, vous avez les bases pour débuter avec MongoDB. Nous allons bientôt rentrer en détail dans le fonctionnement de MongoDB et en particulier nous attarder sur l’aspect dénormalisation !