| ArjunaAircraft Ident.: F-MBSD | Bonjour,
 Comme d'hab, j'ai rien à foutre (enfin si, mais justement, faut que je trouve un moyen d'optimiser ma base et du coup je fais plus ou moins de la R&D sur SQL Server), et j'ai donc décidé de regarder de plus près les "colonnes calculées".
 
 Qu'est-ce qu'une "colonne calculée" ?
 C'est une colonne, dans une table, qui est readonly (overridable par trigger instead of cependant) qui contien non pas une valeur, mais une formule.
 Il y a deux types de colonnes calculées :
 - Les "internes", qui font un calcul uniquement sur des champs de la même table, par exemple, dans une table "EcritureFinanciere", on aura des champs de données "credit", "debit" et une colonne calculée "balance" faisant la formule "credit - debit". Je n'ai pas testé ce type de champs, à mon avis, mon bench ne s'applique pas, d'autant plus qu'ils sont indexables, ce qui peut être très utile.
 - Les "externes", qui portent non pas sur les champs de la table, mais sur une fonction, qui peut aller chercher des données n'importe où (obligé de passer par une fonction, les sous-requêtes étant interdites dans une instruction "create table" ).
 
 A noter :
 Microsoft ne communique presque pas sur cette fonctionnalité de SQL Server 2000. Il faut faire la démarche de chercher cette info dans la doc pour en trouver la syntaxe. Mise à part dans les articles sur les index, il n'y a aucune référence vers cette fonctionnalité.
 
 Quand on regarde ça de loin, c'est assez allèchant : si j'affiche souvent "total facture" avec le numéro de facture, plutôt que de faire des jointures et un SUM() puis des group by dans tous les sens, j'appelle juste une colonne calculée dans ma table des commandes, et je retrouve le total mis à jour en fonction des écritures dans le détail.
 
 J'ai donc décidé de faire un petit bench pour voir ce que ça donne.
 
 Accrochez vos ceintures, me suis bien amusé pour le faire
   
 
 | Code : 
 set nocount on/* ------ Création de l'environnement de test ------ */PRINT 'Génération des objets de test'PRINT 'Table tsttbl'create table tsttbl (id numeric, val float)PRINT 'Table tsttbl2'create table tsttbl2 (id numeric, val float)PRINT 'Fonction tstFunc'gocreate function tstFunc(	@id numeric)returns numericasbegin	declare @tmpid numeric	select @tmpid = min(b.id) from tsttbl2 b, tsttbl a where a.id = @id and a.val > 0 and round(b.val, 4) = round(a.val, 4)	return isnull(@tmpid, 0)endgoPRINT 'Table tsttbl3'create table tsttbl3 (id numeric, val as dbo.tstFunc(id))create table #tmp (id numeric, val numeric)declare @i intPRINT 'Alimentation de la table tsttbl'set @i = 1while @i <= 10000begin	insert into tsttbl (id, val) values (@i, cos(sqrt(@i)))	set @i = @i + 1endPRINT 'Alimentation de la table tsttbl2'set @i = 1while @i <= 10000begin	insert into tsttbl2 (id, val) values (@i, sin(sqrt(@i)))	set @i = @i + 1endPRINT 'Alimentation de la table tsttbl3'set @i = 1while @i <= 10000begin	insert into tsttbl3 (id) values (@i)	set @i = @i + 1end/* ------ Bench ------*/PRINT 'Lancement du bench'declare @t1 as floatdeclare @t2 as floatdeclare @t3 as floatdeclare @t4 as floatPRINT 'Lancement de la première requête (colonnes calculées)'-- Temps avant la première requêteselect @t1 = datepart(ms, getdate()) + datepart(s, getdate()) * 1000 + datepart(minute, getdate()) * 60000 + datepart(hh, getdate()) * 3600000insert into #tmp (id, val) (select top 100 id, valfrom tsttbl3where val != 0)-- Temps après la première requêteselect @t2 = datepart(ms, getdate()) + datepart(s, getdate()) * 1000 + datepart(minute, getdate()) * 60000 + datepart(hh, getdate()) * 3600000delete #tmpPRINT 'Lancement de la seconde requête (requête classique)'-- Temps avant la seconde requêteselect @t3 = datepart(ms, getdate()) + datepart(s, getdate()) * 1000 + datepart(minute, getdate()) * 60000 + datepart(hh, getdate()) * 3600000insert into #tmp (id, val) (select top 100 c.id, min(tmp.id2) valfrom tsttbl c, (select a.id id1, b.id id2 from tsttbl2 b, tsttbl a where a.val > 0 and round(b.val, 4) = round(a.val, 4)) tmpwhere c.id = tmp.id1group by c.id)-- Temps après la seconde requêteselect @t4 = datepart(ms, getdate()) + datepart(s, getdate()) * 1000 + datepart(minute, getdate()) * 60000 + datepart(hh, getdate()) * 3600000/* ----- Nettoyage ------ */drop table #tmpPRINT 'Supression de tsttbl3'drop table tsttbl3PRINT 'Supression de la fonction tstFunc'drop function tstFuncPRINT 'Supression de tsttbl2'drop table tsttbl2PRINT 'Supression de tsttbl'drop table tsttblPRINT ''/* ----- Affichage des résultats ------ */PRINT 'Durée de la première requête :'PRINT cast(round((@t2 - @t1) / 1000, 2) as varchar) + ' secondes'PRINT 'Durée de la seconde requête :'PRINT cast(round((@t4 - @t3) / 1000, 2) as varchar) + ' secondes'set nocount off
 | 
 
 Le résultat :
 
 
 | Code : 
 Génération des objets de testTable tsttblTable tsttbl2Fonction tstFuncTable tsttbl3Alimentation de la table tsttblAlimentation de la table tsttbl2Alimentation de la table tsttbl3Lancement du benchLancement de la première requête (colonnes calculées)Lancement de la seconde requête (requête classique)Supression de tsttbl3Supression de la fonction tstFuncSupression de tsttbl2Supression de tsttblDurée de la première requête :61.77 secondesDurée de la seconde requête :28.25 secondes
 | 
 
 Interprétation :
 Contrairement à ce qu'on pouvait espérer, la requête numéro deux, avec jointures et sous-requêtes pourries est plus de deux fois plus rapide que la version avec colonne calculée.
 Au départ, j'avais même testé sur l'ensemble des lignes (environ 2800 réponses), et l'écart était encore plus flagrant (mais j'avais pas envie de faire tourner le bench pendant 2 heures
  ) 
 Ce que fait ce test :
 - Déjà, je n'utilise que des tables non indexées, sans PK ni autre fioritures, qui pourraient "aider" l'une ou l'autre des requêtes. Je voulais un environnement le plus objectif possible, donc sans la moindre optimisation.
 - De la même façon, pour des raisons évidentes, je ne voulais pas avoir des données dans la colonne "val" ordonnées de la même façon que la colonne "id". C'est pourquoi j'ai utiliser cos(sqrt(@i)) pour la première table et sin(sqrt(@i)) pour la seconde. J'obtiens donc des données non cycliques qui ne suivent pas de façon évidente l'évolution de @i.
 
 Ensuite, j'ai décidé de partir des fonctions COS et SIN car évoluant toutes deux dans un ensemble réduit, j'avais de grandes chances d'avoir des valeurs égales d'une table à l'autre, même en présence du SQRT().
 
 Le principe des requêtes est le suivant :
 -> Obtenir un certain nombre de valeurs de Y en fonction de X pour lequels cos(sqrt(X)) = sin(sqrt(Y))
 
 Pourquoi j'insère les lignes dans une table temporaire ?
 -> C'est juste pour faire joli, je n'ai pas trouvé de moyen dans SQL Server pour ne pas afficher le résultat d'une requête... J'ai tester avec l'ordre des deux requêtes inversé, ça ne change pas les résultats, donc ça n'influe pas sur les temps de traîtement.
 
 Voilà voilà
   
 Harko, t'as encore un truc à mettre en favoris
    |