En absence de réaction à mon MP et vu l'erreur grossière de méthode je ne peux ne pas réagir : L'exemple donné est faussé par un biais de protocole.
Je vous propose de suivre ce nouveau protocole en 3 temps :
1- Comparaison de l'espace occupé en fonction de l'atomisation des colonnes avec alimentation des données à l'identique
2- Descendre en profondeur sur le contenu réel des pages et voir le contenu des lignes quand les colonnes de la table sont des CHAR().
3- Faites votre conclusion
1- Comparaison de l'espace occupé en fonction de l'atomisation des colonnes avec alimentation des données à l'identique
Lors de la lecture faites bien attention à la colonne "data".
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 /*Création de la base de démo*/ CREATE DATABASE DB_TEST_IO GO /*Création de la table de référence*/ USE DB_TEST_IO; CREATE TABLE T_NUMSECU (NNI_NUMERO CHAR(13) CONSTRAINT U UNIQUE); GO /* remplissage de la table de référence*/ SET NOCOUNT ON; GO INSERT INTO T_NUMSECU SELECT CAST(CAST(1 + ABS(CHECKSUM(NEWID())) % 2 AS int) AS char(1)) -- sexe + FORMAT(100 * CAST(ABS(CHECKSUM(NEWID())) % 100 AS int) + CAST(1 + ABS(CHECKSUM(NEWID())) % 12 AS int), '0000') -- date de naissance + FORMAT(1000 * (CAST((ABS(CHECKSUM(NEWID())) % 97) AS int) + 1) + CAST(ABS(CHECKSUM(NEWID())) % 100 AS int) * 10, '00000') -- commune de naissance + FORMAT(CAST((ABS(CHECKSUM(NEWID())) % 1000) AS int), '000'); -- rang de naissance GO 100000 /*Création de la table normalisée équivalente (atomisation en 4 parties du n° de sécurité sociale) :*/ CREATE TABLE T_NUMSECU_ATOMIQUE_NNI (NNI_SEXE CHAR(1), NNI_DATE_NAISSANCE CHAR(4), NNI_LIEU_NAISSANCE CHAR(5), NNI_RANG_NAISSANCE CHAR(3), CONSTRAINT U_A UNIQUE (NNI_SEXE, NNI_DATE_NAISSANCE, NNI_LIEU_NAISSANCE, NNI_RANG_NAISSANCE)); GO /*Insertion des données A PARTIR DE LA TABLE DE REFERENCE */ INSERT INTO T_NUMSECU_ATOMIQUE_NNI SELECT LEFT(NNI_NUMERO, 1), SUBSTRING(NNI_NUMERO, 2, 4), SUBSTRING(NNI_NUMERO, 6, 5), RIGHT(NNI_NUMERO, 3) FROM T_NUMSECU; GO /*Création de la table non Atomisée */ CREATE TABLE T_NUMSECU_NON_ATOMIQUE_NNI (NNI_NUMERO CHAR(13) CONSTRAINT U_NA UNIQUE); GO /*Insertion des données A PARTIR DE LA TABLE DE REFERENCE */ INSERT INTO T_NUMSECU_NON_ATOMIQUE_NNI SELECT NNI_NUMERO FROM T_NUMSECU; GO /* vérification de l'espace occupé */ EXEC sp_spaceused 'T_NUMSECU' EXEC sp_spaceused 'T_NUMSECU_ATOMIQUE_NNI' EXEC sp_spaceused 'T_NUMSECU_NON_ATOMIQUE_NNI'
2- Descendre en profondeur sur le contenu réel des pages
Exécutez individuellement les ordres dbcc et analysez tranquillement les résultats.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 /* ******************************************************************* vérification de la structure de la ligne dans le système de stockage ******************************************************************* */ /* préparation : alimentation de variables pour repérer une même ligne dans chaque table */ declare @ss as varchar(13) select @ss= MIN(NNI_NUMERO) from T_NUMSECU T -- select @ss --> 1000102950898 declare @@nni_sexe char(1), @@nni_date_n char(4), @@nni_lieu char(5), @@nni_rang char(3) SELECT @@nni_sexe = LEFT(@ss, 1) , @@nni_date_n = SUBSTRING(@ss, 2, 4) , @@nni_lieu = SUBSTRING(@ss, 6, 5) , @@nni_rang = RIGHT(@ss, 3) /* création de requête dynamiques pour afficher l'intégralité du contenu de la page pour chaque table */ declare @level char(1) = '2' /* 0 = header 1 = header and hex dump for rows 2 = header and the page dump 3 = header and detail row information */ SELECT '/* [T_NUMSECU] : */ DBCC TRACEON(3604) DBCC PAGE (' + '[DB_TEST_IO], ' + '1, ' + SUBSTRING(sys.fn_PhysLocFormatter(T.%%physloc%%),4,CHARINDEX(':',sys.fn_PhysLocFormatter(T.%%physloc%%),4)-4) +', ' + @level +') DBCC TRACEOFF(3604)' as "Ordre à executer dans une nouvelle requête" FROM [DB_TEST_IO].[dbo].[T_NUMSECU] as T where t.[NNI_NUMERO] = @ss SELECT '/* [T_NUMSECU_ATOMIQUE_NNI] : */ DBCC TRACEON(3604) DBCC PAGE (' + '[DB_TEST_IO], ' + '1, ' + SUBSTRING(sys.fn_PhysLocFormatter(T.%%physloc%%),4,CHARINDEX(':',sys.fn_PhysLocFormatter(T.%%physloc%%),4)-4) +', ' + @level +') DBCC TRACEOFF(3604)' as "Ordre à executer dans une nouvelle requête" FROM [dbo].[T_NUMSECU_ATOMIQUE_NNI] AS T Where [NNI_SEXE] = @@nni_sexe AND [NNI_DATE_NAISSANCE] = @@nni_date_n AND [NNI_LIEU_NAISSANCE] = @@nni_lieu AND [NNI_RANG_NAISSANCE] = @@nni_rang SELECT '/* [T_NUMSECU_NON_ATOMIQUE_NNI] : */ DBCC TRACEON(3604) DBCC PAGE (' + '[DB_TEST_IO], ' + '1, ' + SUBSTRING(sys.fn_PhysLocFormatter(T.%%physloc%%),4,CHARINDEX(':',sys.fn_PhysLocFormatter(T.%%physloc%%),4)-4) +', ' + @level +') DBCC TRACEOFF(3604)' as "Ordre à executer dans une nouvelle requête" FROM [DB_TEST_IO].[dbo].[T_NUMSECU_NON_ATOMIQUE_NNI] AS T where t.[NNI_NUMERO] = @ss
3- Faites votre conclusion
Pour ma part je me méfie toujours de la façon dont SQL server alimente les table "non cluster".
Je me demande si cette digression sur l'atomicité ne devrait pas faire un post à part ?
Le savoir est une nourriture qui exige des efforts.
Je ne comprends pas trop ce que viennent faire index, vues matérialisées, compression dans un débat sur la normalisation.
Ce sont des implémentations physiques n'ayant qu'un seul objectif : l'amélioration des performances du SGBD.
Ça n'existe pas dans un MCD. Ces objets, fonctionnellement, n'apportent rien.
Email : http://scr.im/waldar
La dénormalisation c'est tellement bien que ce matin on s'est rendu compte en catastrophe qu'on avait oublié de recodifier des données "dénormalisées" dans une application.
Alors que la modification concerne à la base quelques dizaines de milliers de clients qui changent de code (mise à jour des fiches en quelques dizaines de secondes), on a oublié que le code "externe" était recopié sur toutes les commandes, les lignes de commandes, et les réclamations qui pointent sur les commandes.
L'outil étant tellement bien foutu qu'il ne sait importer/exporter qu'une table à la fois sans jointure (raison de la dénormalisation) on s'est rendu comptes qu'on envoyait à une application des réclamations avec des codes clients pourris car non recodifiés.
Bref, en urgence, on a lancé ce matin la recodification des 3 millions de lignes de commande... il reste 12 heures de traitement !
J'adore la dénormalisation. L'application de gestion qualité est plantée pendant tout ce temps, et la CRM est éteinte pour éviter de continuer à saisir de la merde.
Quelques centaines de personnes qui se tirent la nouille depuis ce matin en attendant que madame dénormalisation leur permette de travailler de nouveau.
La vie est belle.
On ne jouit bien que de ce qu’on partage.
Sinon, les intérêts de normaliser la réprésentation du numéro de sécu sont (en vrac) :
- Permettre de requêter simplement les personnes par leur age, sexe, département et commune de naissance : trouver quel département a le plus de femmes nées 1987 peut s'avérer compliqué et lent si on stock le code tel quel ! Et si on souhaite savoir quelles sont les villes commençant par S qui ont le moins d'hommes nés entre 1950 et 1980 c'est juste impossible sans normaliser correctement !
- Permettre de s'assurer que la saisie est consistante (numéro de commune existant dans le département par exemple)
- Effectuer la saisie partielle (lorsqu'on connait par exemple tous les éléments sauf le rang, cela permet tout de même de constituer une partie du numéro de sécu)
Et pour info, couper le numéro de sécu en X colonnes CHAR, j'en vois pas l'intérêt : département + commune peuvent être avantageusement remplacé par une relation vers la table commune qui elle-même référence la table département par exemple.
Quant au rang, il faudra m'expliquer pourquoi le stocker autrement que dans un type numérique !
=> Et à partir de là, le débat sur la place utilisée, est clos avant même d'être ouvert... On remplace 8 caractères par par deux SMALLINT... On a déjà réduit de 50% l'espace brut nécessaire au stockage de cette partie du code !
On ne jouit bien que de ce qu’on partage.
Frédéric Brouard - SQLpro - ARCHITECTE DE DONNÉES - expert SGBDR et langage SQL
Le site sur les SGBD relationnels et le langage SQL: http://sqlpro.developpez.com/
Blog SQL, SQL Server, SGBDR : http://blog.developpez.com/sqlpro
Expert Microsoft SQL Server - M.V.P. (Most valuable Professional) MS Corp.
Entreprise SQL SPOT : modélisation, conseils, audit, optimisation, formation...
* * * * * Expertise SQL Server : http://mssqlserver.fr/ * * * * *
Manifestement vous avez une compréhension de la normalisation qui ne correspond pas à la définition fournie par la théorie relationnelle et que je rappelle ici :
Soit r une relation munie des attributs A1, ..., An de types respectifs T1, ..., Tn. r est en première forme normale (1NF) si et seulement si, pour tous les tuples t contenus dans r, la valeur de l’attribut Ai dans t est du type Ti (i = 1, ..., n).
Je précise que normalisation et première forme normale signifient exactement la même chose :
Relation r is normalized if and only if it’s in 1NF.
Ces définitions sont reprises de l’ouvrage de C. J. Date, Database Design and Relational Theory.
Voyez aussi le paragraphe 2.7 de l’article Bases de données relationnelles et normalisation : de la première à la sixième forme normale.
Je rappelle encore qu’une relation est une valeur affectée à une relvar (variable relationnelle).
Quant à SQL, je cite à nouveau C. J. Date :
Une table est en première forme normale (1NF) — de façon équivalente, est normalisée — si et seulement si elle est la représentation fidèle d’une relvar.
La fidélité implique les 5 points suivants :
1. Les lignes de la table ne sont pas ordonnées (disons de haut en bas).
2. Les colonnes de la table ne ne sont pas ordonnées (disons de gauche à droite).
3. Il ne peut y avoir de lignes en double.
4. L’intersection d’une ligne et d’une colonne contient exactement une valeur du type de la colonne et rien d’autre.
5. Toutes les colonnes son régulières.
Pour mémoire, concernant le point 4 : pas d’attributs multivalués (groupes répétitifs, tableaux, listes...)
Concernant le point 5 : chaque colonne a un nom, et ce nom est unique parmi l’ensemble des noms des colonnes de la table. Par ailleurs, la table n’a pas de colonnes « cachées » (object IDs, timestamps spéciaux, ...)
C. J. Date rappelle encore pourquoi Codd a utilisé le mot « normalisation », quand il a inventé la théorie relationnelle (1969-1970) :
Codd : It seemed to me essential that some discipline be introduced into database design. I called it normalization because then President Nixon was talking a lot about normalizing relations with China. I figured that if he could normalize relations, so could I.
En écho au titre de la discussion (« Normalisation, pour quoi faire ? »), Codd fournit ici une réponse ô combien pertinente : introduire une certaine discipline dans la conception, la modélisation des bases de données.
(a) Faites simple, mais pas plus simple ! (A. Einstein)
(b) Certes, E=mc², mais si on discute un peu, on peut l’avoir pour beaucoup moins cher... (G. Lacroix, « Les Euphorismes de Grégoire »)
=> La relativité n'existerait donc que relativement aux relativistes (Jean Eisenstaedt, « Einstein et la relativité générale »)
__________________________________
Bases de données relationnelles et normalisation : de la première à la sixième forme normale
Modéliser les données avec MySQL Workbench
Je ne réponds pas aux questions techniques par MP. Les forums sont là pour ça.
Ah ?
En laissant le code de sécurité sociale sur 15 caractères sans le découper, vous faites comment pour retrouver cette information alors ?
Vous vous offusquez car je parle ici de rechercher selon un attribut qui n'existe pas dans l'analyse initiale ?
Justement, normaliser, c'est s'acheter de la tranquillité pour l'avenir, au prix parfois de quelques développements initiaux plus complexes (plus simple d'avoir une zone de 15 caractères avec un simple contrôle de clé que X zones de saisies avec contrôles de tables de référence).
Une fois les tables commune et département créées, aucun problème pour ajouter une colonne pour le nom de la commune et impacter sans autre modification tous les numéros de SS déjà saisis. Sans ces tables, la seule solution serait de rajouter la commune dans la même table que celle du numéro SS et vive la redondance, les orthographes erronées et les incohérences…
Si je me fourvoie, alors expliquez concrètement ce qui ne va pas dans ma démarche, car vos explications confirment simplement mes dires.
On ne jouit bien que de ce qu’on partage.
Vous parlez de numéro de sécurité social mais ayant déjà bossé sur le code Insee je peux vous assurer que ce n'est pas écrit dans du marbre. En effet, il y a des communes qui ont changé de code dans leur histoire et vont changer dans le futur. Lors de la conception il faut toujours prévoir le cas improbable. Autre exemple, définir le genre que par une donnée binaire H/F. Ouais mais maintenant nous aurons potentiellement le 3ème voir x ème genre. Le faite de normaliser jusqu'au bout des ongles permet d'avoir plus de chance d’échappatoire. Normaliser jusqu'au n'est qu'un but à atteindre mais il faut y garder un équilibre.
Mon avatar ? Ce n'est rien, c'est juste la tête que je fais lorsque je vois un code complètement frappa dingue !...
Le sexe peut prendre 6 valeurs : 1, 2, 4, 5, 7 et 8 (les 4 derniers correspondant aux étrangers qui ont des codes temporaires). Il y a donc forcément une entité de référence, qui est elle-même porteuse du genre (1, 4, 7 = homme, et 2, 5, 8 = femme).
Quant aux cas des codes INSEE des communes qui changent, qu'en est-il du code SS ?
Deux solutions très simples : soit le numéro SS est mis à jour en même temps, dans ce cas aucun code à retraiter, c'est immédiat, soit le numéro ne doit pas changer. Dans ce cas, le code INSEE de la commune devient une entité fille de la commune et on lui colle une période d'application. Corrélé à la date présente dans le code SS on peut alors déterminer le code INSEE en vigueur au moment de l'attribution du code.
Bref, dans un cas, simple ajout d'une table et modification d'une requête, et dans l'autre cas, rien à toucher.
La normalisation n'apporte donc aucune complexité insurmontable.
En revanche, on est toujours capable de faire des statistiques sur les périodes de naissance selon des informations liées aux communes par exemple, ce qu'on est incapable de faire si on ne normalise pas. Même mieux, pour les communes qui changent de code INSEE, on est capable de consolider les statistiques dans le temps, sans être impacté par ce change de code. Chose impossible sans normaliser (à moins que vous soyez amoureux des CASE WHEN de 2000 lignes)
On ne jouit bien que de ce qu’on partage.
Ah l'exagération, on est capable de le faire, ça demande un peu plus de code et de CPU, mais découper le numéro de SS en colonnes distinctes c'est simple.
D'ailleurs pour apporter ma pierre à l'édifice autour du numéro de sécu, voici ce que je ferais si j'avais à le faire.
Une table qui stocke le numéro découpé, complétée d'une colonne virtuelle qui converti et concatène le tout en NIR.
C'est cette dernière que je propagerai dans le modèle des données (pas en tant que clef primaire, juste là où c'est nécessaire) et certainement pas les colonnes atomiques.
Email : http://scr.im/waldar
Désolé, j'aurais du être plus précis lorsque je parlais du sexe, c'était sans rapport avec le numéro de sécurité social. Je parlais du sexe de manière général. En effet, très souvent je vois un champ type binaire et c'est précisément dans ce genre de cas que la dé-normalisation peut poser problème.
Je te rejoint (StringBuilder) sur le faite que la normalisation d'une base de donnée permet justement de contourner les problèmes comme le changement ou numéro insee.
Mon avatar ? Ce n'est rien, c'est juste la tête que je fais lorsque je vois un code complètement frappa dingue !...
Ben oui… Je rappelle que la théorie de la normalisation des relations est une branche des mathématiques appliquées, avec son axiomatique, ses théorèmes et tout ça. Elle s’est développée durant les années soixante-dix avec notamment Codd, Boyce, Delobel, Adiba, Armstrong, et Fagin en point d’orgue.
Reprenons la table T_NUMSECU_NNI proposée par SQLpro :
Du point de vue de la théorie relationnelle, cette table SQL est normalisée (sa structure est en effet l’image fidèle d’une relvar, cf. post #47), et comme je l’ai déjà écrit (cf. post #38), elle est en sixième forme normale (6NF).CREATE TABLE T_NUMSECU_NNI (NNI_NUMERO CHAR(13) CONSTRAINT U UNIQUE);
Merci de présenter votre théorie alternative. Sinon parlez plutôt de décomposition d’un attribut.
Vous pouvez commencer par créer une vue décomposant la colonne NNI_NUMERO au moyen de la fonction SUBSTRING, définie dans la norme SQL et proposée par les SGBD.
Exemple avec SQL Server :
Une ligne de la table :go create view T_NUMSECU_NNI_V (Sexe, Annee, Mois, Lieu, NoOrdre) as select substring(NNI_NUMERO, 1, 1) , substring(NNI_NUMERO, 2, 2) , substring(NNI_NUMERO, 4, 2) , substring(NNI_NUMERO, 6, 5) , substring(NNI_NUMERO, 11, 3) from T_NUMSECU_NNI go
Utilisation de la vue :insert into T_NUMSECU_NNI (NNI_NUMERO) values ('195012A005007')
Au résultat :select Sexe, Annee, Mois, Lieu, NoOrdre from T_NUMSECU_NNI_V
Ainsi, on sait retrouver l’information.Sexe Annee Mois Lieu NoOrdre ---- ----- ---- ---- ------- 1 95 01 2A005 007
(a) Faites simple, mais pas plus simple ! (A. Einstein)
(b) Certes, E=mc², mais si on discute un peu, on peut l’avoir pour beaucoup moins cher... (G. Lacroix, « Les Euphorismes de Grégoire »)
=> La relativité n'existerait donc que relativement aux relativistes (Jean Eisenstaedt, « Einstein et la relativité générale »)
__________________________________
Bases de données relationnelles et normalisation : de la première à la sixième forme normale
Modéliser les données avec MySQL Workbench
Je ne réponds pas aux questions techniques par MP. Les forums sont là pour ça.
Frédéric Brouard - SQLpro - ARCHITECTE DE DONNÉES - expert SGBDR et langage SQL
Le site sur les SGBD relationnels et le langage SQL: http://sqlpro.developpez.com/
Blog SQL, SQL Server, SGBDR : http://blog.developpez.com/sqlpro
Expert Microsoft SQL Server - M.V.P. (Most valuable Professional) MS Corp.
Entreprise SQL SPOT : modélisation, conseils, audit, optimisation, formation...
* * * * * Expertise SQL Server : http://mssqlserver.fr/ * * * * *
Oui, mais avec des performances moindres.... En atomisant les différentes parties de ce n° on ne viole pas les règles de normalisation et en plus on a de données performances quelque soit les circonstances....
Pour info le même Chris Date dans son livre sur les SGBDR indique que l'atomisation doit conduire à des valeurs SCALAIRES uniquement.... Ensuite il écrit "The Third Manifesto"....
A +
Frédéric Brouard - SQLpro - ARCHITECTE DE DONNÉES - expert SGBDR et langage SQL
Le site sur les SGBD relationnels et le langage SQL: http://sqlpro.developpez.com/
Blog SQL, SQL Server, SGBDR : http://blog.developpez.com/sqlpro
Expert Microsoft SQL Server - M.V.P. (Most valuable Professional) MS Corp.
Entreprise SQL SPOT : modélisation, conseils, audit, optimisation, formation...
* * * * * Expertise SQL Server : http://mssqlserver.fr/ * * * * *
J'avoue que cette remarque m'a fait cogiter.
Constatation : "normalisation" est un mot valise qui cache à la fois des méthodes (Merise, UML) et des objectifs (atomicité, ...) qui diffèrent en fonction du "niveau" attendu.
C'est ce qui fait que parler de dé-normalisation est toujours un peu, ambigu.
Un Modèle Conceptuel de Données (MCD) est une étape dans le travail de normalisation.
Je ne suis pas d'accord de faire apparaitre, sans broncher, des éléments "physiques" qui enfreignent allègrement les objectifs qui nous ont guidés lors de la normalisation, sous le seul prétexte qu'ils ne sont pas représentés, pendant, l'étude sur les données.
De plus, parmi ces objets "physiques" j'entrevois plusieurs "catégories" en fonction de l'incidence qu'elles ont sur l'écriture (et non l’exécution) du code :
Les objets transparents (index, compression, columnstore,...) : le fait de les annuler ne remet pas le code SQL existant en danger
Les objets semi-transparents (partitionnement, vue indexée, ...) : tout dépend s'ils ont été explicitement utilisés dans le SQL
En tous les cas merci pour avoir lancé le débat sur le fait que la normalisation doit s’arrêter, ou pas, au MCD.
Le savoir est une nourriture qui exige des efforts.
Je reviens sur ma question initiale.
En lisant les autres forums je suis tombé sur une réponse qui indiquerait que la normalisation doit se faire à un niveau "pragmatique" :
Si je suis, dans le fond assez d'accord avec la nécessité de marquer une limite à l'étude pour l'étude, comment délimiter le périmètre ?
La solution proposée présente le défaut de devoir (1) créer une table, (2) de l'alimenter pour pouvoir (3) dénombrer le contenu afin de pouvoir créer la table
Quelqu'un a mieux ?
Le savoir est une nourriture qui exige des efforts.
Plutôt que d'aller chercher à droites et à gauche des brides d'informations, pourquoi ne pas étudier l'article référence d'fsmrel Bases de données relationnelles et normalisation :
de la première à la sixième forme normale et revenir sur les fondamentaux.
Qu'est-ce que le Modèle Relationnel ?
Qu'est-ce qu'une relation/relvar ?
Qu'est-ce que la normalisation ?
Première forme normale ?
Le concept d'indépendance physique.
La notion de dépendance fonctionnelle.
La décomposition sans perte de dépendance.
Tout y est..
Normalisation n'est pas un "mot-valise".
Le MCD n'est pas "une étape dans le travail de normalisation"
Normalisation vs Modélisation;
La normalisation joue un rôle crucial quant à la qualité de l'architecture de la base de données, laquelle doit être structurellement valide et apte à évoluer, premièrement par le recours à une démarche au niveau conceptuel synthétique, descendante (donc en amont), à l'aide par exemple de la méthode Merise (démarche valant également pour les diagrammes de classes), deuxièmement par une vérification rigoureuse, mettant en jeu une démarche analytique, ascendante, pour laquelle on s'appuie justement sur la théorie de la normalisation : l'architecture de la base de données relève ainsi d'une approche mixte où l'on pratique l'art du yoyo, en alternant intelligemment les deux démarches.
Merci je n'avais pas relevé ce passage dans cet excellent article.
"L'art du yoyo" c'est pas mal aussi comme définition de la limite de l'étude.
Je n'en doute pas.
Mais vu le nombre de posts, je ne suis pas le seul pour qui une relecture s'imposait.
Merci de défendre ces affirmations.
Le savoir est une nourriture qui exige des efforts.
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager