L'un des principaux avantages de posséder un langage riche est de pouvoir traiter certaines données à un niveau beaucoup plus bas que par des commandes classiques...
Les fonctions de manipulations de bits dans SQL Server en sont un bon exemple.
Pour rappel, on trouvera notamment :
- le AND logique : &
- le OR logique : |
Le gros avantage de ces fonctions bas niveau est un accroissement considérable des performances lors de traitements longs par exemple (insertion dans plusieurs tables en une seule procédure stockée).
Nous allons voir un exemple concret de manipulation de ces bits permettant d'exploser nos perfs (véridique sur un test à échelle réelle).
J'ai un formulaire de type "Dites-nous tout sur vous" dont je souhaite stocker en base les réponses des utilisateurs. Nous allons nous focaliser sur une question de ce questionnaire pour ne pas allonger ce post mais le principe restera le même pour l'ensemble du formulaire.
La question-exemple (à choix multiple) sera "comment occupez-vous vos loisirs ?". Les réponses seront les suivantes :
Sport, Culture-Découverte, Détente, Musique-Concerts, Auto-Moto, Cine-TV, Santé-Beauté-Forme, Bricolage, JeuxVideos, Voyages
Notre table d'utilisateur existe déjà :
T_Personne(PersonneId,Nom,Prenom,...)
Un cas classique voudrait que l'on conçoit nos tables de réponses de la façon suivante :
On peut éventuellement se passer de la table T_Reponse dans notre cas puisque ses valeurs seront uniquement {Oui, Non, NULL}.
On voit néanmoins que pour chaque réponse de l'utilisateur, on insère une ligne dans la table d'association T_Reponse_Question. Idem pour la mise à jour voire la suppression...
La première étape de l'amélioration de nos perfs consiste à dénormaliser notre schéma, pour obtenir la table de réponses aux questions suivante (on conserve uniquement la table T_Personne et celle-ci) :
Le champ PersonneId sera à la fois clé primaire et clé étrangère par rapport à la table T_Personne.
Côté code .NET, une enum "flags" dont la définition suit permettra de récupérer toutes les réponses :
[Flags]
public enum Loisirs
{
Sport = 1
,CultureDecouverte = 2
,Detente = 4
,MusiqueConcert = 8
,AutoMoto = 16
,CineTV = 32
,SanteBeauteForme = 64
,Bricolage = 128
,JeuxVideo = 256
,Voyages = 512
}
Dans la page, si des CheckBox permettent à l'utilisateur de répondre, chaque réponse est récupérée comme suit :
monUser.Loisirs = ((chkReponse0.Checked ? Loisirs.Sport : 0)
|(chkReponse1.Checked ? Loisirs.CultureDecouverte : 0)
| (chkReponse2.Checked ? Loisirs.Detente : 0)
| (chkReponse3.Checked ? Loisirs.MusiqueConcert : 0)
| (chkReponse4.Checked ? Loisirs.AutoMoto : 0)
| (chkReponse5.Checked ? Loisirs.CineTV : 0)
| (chkReponse6.Checked ? Loisirs.SanteBeauteForme : 0)
| (chkReponse7.Checked ? Loisirs.Bricolage : 0)
| (chkReponse8.Checked ? Loisirs.JeuxVideo : 0)
| (chkReponse9.Checked ? Loisirs.Voyages : 0));
Ainsi, côté SQL, pour une insertion ou une mise à jour, notre procédure stockée aura comme paramètre une variable @Loisirs de type INT, utilisée comme suit :
INSERT INTO [T_Question_Reponse_Denormalized]
([PersonneId]
,[Sport]
,[Culture-Decouverte]
,[Detente]
,[Musique-Concert]
,[Auto-Moto]
,[Cine-TV]
,[Sante-Beaute-Forme]
,[Bricolage]
,[JeuxVideo]
,[Voyages])
VALUES
(1
,@Loisirs & 1
,@Loisirs & 2
,@Loisirs & 4
,@Loisirs & 8
,@Loisirs & 16
,@Loisirs & 32
,@Loisirs & 64
,@Loisirs & 128
,@Loisirs & 256
,@Loisirs & 512)
Pour récupérer nos réponse, notre SELECT ressemblera à ceci :
SELECT [PersonneId]
,([Sport] * 1
|[Culture-Decouverte] * 2
|[Detente] * 4
|[Musique-Concert] * 8
|[Auto-Moto] * 16
|[Cine-TV] * 32
|[Sante-Beaute-Forme] * 64
|[Bricolage] * 128
|[JeuxVideo] * 256
|[Voyages] * 512) AS Loisirs
FROM [COACH].[dbo].[T_Question_Reponse_Denormalized]
WHERE PersonneId = 1
Enfin, côté code .NET, un simple :
(Loisirs)(int)reader["Loisirs"];
permettra de récupérer l'ensemble des réponses pour la question.
On remarquera donc comme principaux avantages :
- manipulation de bits donc accroissement des performances générales
- une seule ligne impactée pour l'insertion / la mise a jour / la suppression
L'inconvénient majeur à mon avis est l'extension du nombre de questions / réponses qui impose la création d'une nouvelle table par question et d'une nouvelle colonne par réponse....
Si les performances restent la contrainte numéro 1 (ce qui est le cas sur des applications Web), cette solution est à envisager...