IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

SQL Procédural MySQL Discussion :

Fonction et vitesse d'exécution ?


Sujet :

SQL Procédural MySQL

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Points : 59
    Points
    59
    Par défaut Fonction et vitesse d'exécution ?
    Bonjour à vous,

    Je m'interroge sur une chose concernant la vitesse d'exécution dans le cas où on utilise des fonctions par rapport à si on ne les utilisait pas. Par exemple, imaginez que je fasse une fonction dont le but serait de retourner une valeur nommée "get_info_table2" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    BEGIN
    DECLARE result VARCHAR(64);
    set result = "";
         SELECT table2.type
         INTO result
         FROM table2 
         WHERE table2.id=_id
         ORDER BY table2.timestamp DESC LIMIT 1;
    RETURN result;
    END
    Maintenant, j'aimerais savoir si faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    SELECT table.id, table.nom, get_info_table2(table.id) as info
    FROM table
    WHERE *
    serait plus rapide, plus lent, ou équivalent à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    SELECT table.id, table.nom, 
    (SELECT table2.type
         INTO result
         FROM table2 
         WHERE table2.id=table.id
         ORDER BY table2.timestamp DESC LIMIT 1) as info
    FROM table
    WHERE *
    Bon je sais qu'en l'occurence, l'exemple est un peu con et qu'il serait sans doute plus élégant de faire une jointure mais c'est la vitesse d'exécution d'une fonction que j'aimerais discuter ici... En d'autres termes, ma question est : "créer une fonction et utiliser son nom dans une requête est-il plus rapide que de mettre le code de la fonction directement dans la requête qui l'utilise ??"

    L'avantage que je vois à créer une fonction est que la "maintenance" est plus facile : si ce bloc est utilisé dans plusieurs requêtes et qu'il doit être modifié par la suite, il me suffira de le modifier une seule fois (dans la déclaration de fonction) pour que le changement se répercute partout bien sûr... Mais si c'est calamiteux en terme de performances, ce n'est peut-être pas idéal non plus...

    Merci pour vos retours d'expérience(s) et vos avis ! :-)

    RL

  2. #2
    Rédacteur

    Avatar de SQLpro
    Homme Profil pro
    Expert bases de données / SQL / MS SQL Server / Postgresql
    Inscrit en
    Mai 2002
    Messages
    21 787
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Expert bases de données / SQL / MS SQL Server / Postgresql
    Secteur : Conseil

    Informations forums :
    Inscription : Mai 2002
    Messages : 21 787
    Points : 52 794
    Points
    52 794
    Billets dans le blog
    5
    Par défaut
    Les bases de données relationnelles procède par manipulation d'ensemble de données. Les fonction, procédures et autres routines procèdent par itération.
    Les manipulation ensemblistes relèguent la notion d'ordre ce qui permet :
    • d'utiliser des index (seek - recherche) plutôt que d'effectuer des lectures ligne à ligne (scan - balayage)
    • de multithreader une même opération (groupage, tri, balayage...) alors que l'itération qui suppose un ordre oblige à un traitement monothread.
    • de simplifier le code de la requête (algèbre relationnelle : factorisation par exemple) ce qui permet de ne faire que quelques opérations lorsque le résultat est déterministe à la place de toutes les opérations
    • d'optimiser la requête en choisissant l'ordre d'exécution des opérations et non l'ordre d'écriture

    En mode itératif (et les fonctions le sont) aucune de ces manipulation n'est supportable.
    Ce sont quelques uns des multiples avantages des bon SGBD Relationnels. Mais quand je parle des bons, je ne cite pas MySQL !
    A me lire : https://blog.developpez.com/sqlpro/p...oudre_aux_yeux

    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/ * * * * *

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Points : 59
    Points
    59
    Par défaut
    Hmmmm... grand merci pour votre réponse mais sans doute que mes connaissances ne me permettent pas de la comprendre... je ne vois pas comment déduire la reponse à ma question de votre intervention... pourriez-vous m'aiguiller un peu plus svp? Mille mercis encore...

  4. #4
    Rédacteur

    Avatar de SQLpro
    Homme Profil pro
    Expert bases de données / SQL / MS SQL Server / Postgresql
    Inscrit en
    Mai 2002
    Messages
    21 787
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Expert bases de données / SQL / MS SQL Server / Postgresql
    Secteur : Conseil

    Informations forums :
    Inscription : Mai 2002
    Messages : 21 787
    Points : 52 794
    Points
    52 794
    Billets dans le blog
    5
    Par défaut
    En gros si j'ai une table composée de 100 000 000 de lignes dont 99 999 999 ont comme valeur de la colonne C = 321 et une seule avec la valeur 1, et que je veut faire la requête SELECT suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    SELECT *, LOG(C)
    FROM   MaTable
    Si le serveur, s'il dispose d'un index, il ne fera que 2 opérations :
    • LOG(1) dont le résultat est reporté sur l'unique ligne avec la valeur 1
    • LOG(321) dont le résultat est reporté sur toutes les autres lignes.



    Si vous utilisez une fonction utilisateur pour ce faire, alors il sera obligé d'exécuter 100 000 000 calculs !

    Autrement dit, la vitesse d'exécution pour ce cas de figure est simple... Si le calcul dure 1ms alors dans le premier cas la réponse intervient en 2 ms et dans le second au bout de 27h 45m 40s... !

    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/ * * * * *

  5. #5
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Points : 59
    Points
    59
    Par défaut
    Merci pour votre réponse ; je comprends mieux mais pas encore bien... Tout à fait naïvement, je me disais à vous lire que l'index sur la colonne C permettait de n'évaluer la fonction LOG que sur les valeurs distinctes et de reporter le résultat là où il faut (donc de n'exécuter le calcul de LOG(1) puis LOG(321) qu'une seule fois dans votre exemple). Du coup, que ce soit la fonction LOG ou une autre fonction utilisateur, je me disais que cela n'avait pas grande importance, grâce à l'index :-) Mais vous semblez dire que si on utilise une fonction utilisateur même en ayant un index, le calcul sera stupidement fait à chaque fois.
    Du coup je ne comprends plus bien votre réponse : qu'est-ce que l'index et le fait que ce soit une fonction utilisateur (et pas une fonction MySQL) ont à voir l'un avec l'autre ??

  6. #6
    Rédacteur

    Avatar de SQLpro
    Homme Profil pro
    Expert bases de données / SQL / MS SQL Server / Postgresql
    Inscrit en
    Mai 2002
    Messages
    21 787
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Expert bases de données / SQL / MS SQL Server / Postgresql
    Secteur : Conseil

    Informations forums :
    Inscription : Mai 2002
    Messages : 21 787
    Points : 52 794
    Points
    52 794
    Billets dans le blog
    5
    Par défaut
    Un SGBDR établis un plan de requête, en lisant le texte de la requête (transformé en arbre algébrique relationnel) et décide des opérations à effectuer par rapport aux évaluations de cardinalité (nombre de ligne à manipuler) effectuées sur des distributions statistiques. Tout ceci sans exécuter quoi que ce soit, puisque le but est de trouver comment exécuter le mieux possible les traitements inhérents à la requête.
    Pour avoir des statistiques il faut des données. Une table, un index contient des données et peut donc faire l'objet de statistiques permettant d'établir un plan de requête efficace car optimal. Une fonction ne contient rien... Elle ne peut donc pas être estimée d'aucune manière. Ainsi le plan établi sur une fonction est systématiquement de l'exécuter pour toutes les valeurs...

    Si vous voulez un exemple précis en voici un :

    -- soit la table :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CREATE TABLE MaTable (C INT)
    -- insertion primale (10 lignes) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    --> exécutez 3 fois la requête suivante
    -- (ATTENTION c'est long surtout si vous avez : un SGBDR lent, un PC lent... !)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    INSERT INTO MaTable
    SELECT t1.C 
    FROM   MaTable AS t1
           CROSS JOIN MaTable AS t2;
    --> pour avoir 149 096 310 lignes dans la table :
    -- 10 x 10 + 10 = 110
    -- 110 x 110 + 110 = 12210
    -- 12210 x 12210 + 12210 = 149096310

    -- on ajoute une seule ligne avec la valeur 1
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    INSERT INTO MaTable VALUES (1);
    -- indexer 149 096 311 lignes, c'est long aussi !!!
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CREATE INDEX X ON MaTable (C);
    -- on créé une deuxième table pour stocker des valeurs de logarithmes précalculés :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CREATE TABLE T_LOG (N INT, LOGARITHME DECIMAL(16, 2));
    -- insérons deux lignes de logarithmes précalculés :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    INSERT INTO T_LOG VALUES (321, LOG(321)), (1, LOG(1));
    -- et indexons la table :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CREATE INDEX Y ON T_LOG (N);
    -- requête itérative du fait de la fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    SELECT * 
    FROM   MaTable 
    WHERE  LOG(C) = 0;
    -- requête ensembliste du fait de la jointure
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT * 
    FROM   MaTable AS T 
           JOIN T_LOG AS L 
    	        ON T.C = L.N 
    WHERE LOGARITHME = 0;
    À priori le calcul des plans de requête montre ceci (je suis sous SQL Server) :
    Nom : Comparaison_plan_de_requete.png
Affichages : 895
Taille : 30,5 Ko

    Il semble que le première requête devrait être plus rapide...

    Testez les maintenant en exécution....

    Sur mon PC :
    Requête 1 : Temps UC = 24452*ms, temps écoulé = 12284*ms.
    Requête 2 : Temps UC = 0*ms, temps écoulé = 0*ms.

    CQFD...

    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/ * * * * *

  7. #7
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Points : 59
    Points
    59
    Par défaut
    Merci pour cet exemple mais il me semble que ce n'est pas tout à fait en lien avec ma question de départ. Dans votre exemple, vous comparez une requète qui calcule tous les logarithmes avec une requète qui utilise une table avec les logarithmes précalculés... Donc dans la 2e requète, il n'y a plus aucun calcul de log et cela me paraît bien normal que cela aille *beaucoup* plus vite.

    Dans ma question initiale, il est question de comparer la rapidité d'exécution de la requète suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    SELECT table.id, table.nom, 
    (SELECT table2.type
         FROM table2 
         WHERE table2.id=table.id
         ORDER BY table2.timestamp DESC LIMIT 1) as info
    FROM table
    WHERE *
    avec la rapidité de :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    SELECT table.id, table.nom, get_info_table2(table.id) as info
    FROM table
    WHERE *
    où la fonction get_info_table2 a juste le rôle de faire la sous-requète SELECT en gras. Dans les deux cas, il me semble que cette sous-requète doit être calculée autant de fois qu'il y a de lignes dans la table "table" : pas de précalcul, mais juste le fait de mettre une sous-requète en fonction ou l'intégrer dans la requète principale. Il me semble que c'est un peu différent de la situation que vous décrivez, non ??

  8. #8
    Rédacteur

    Avatar de SQLpro
    Homme Profil pro
    Expert bases de données / SQL / MS SQL Server / Postgresql
    Inscrit en
    Mai 2002
    Messages
    21 787
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Expert bases de données / SQL / MS SQL Server / Postgresql
    Secteur : Conseil

    Informations forums :
    Inscription : Mai 2002
    Messages : 21 787
    Points : 52 794
    Points
    52 794
    Billets dans le blog
    5
    Par défaut
    Absolument pas.... Il n'y a aucune différence avec mon exemple.
    Votre fonction sera analysée comme scalaire donc itérative par l'optimiseur alors que la solution ensembliste pourra être simplifiée et récrite pour utiliser les index.

    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/ * * * * *

  9. #9
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Points : 59
    Points
    59
    Par défaut
    Ah bon... C'est complètement con, non ? Enfin, c'est sans doute moi qui suis un peu trop naïf.
    J'avais imaginé mettre la sous-reqûete en fonction pour deux raisons : faciliter la lecture des requètes MySQL et aussi pour centraliser certaines sous-requêtes souvent utilisées, histoire de me faciliter la vie quand une modification sera nécessaire sur cette sous-requête : pas besoin de faire la modif à quinze endroits... Du coup, ma question devient : est-ce qu'il y a une autre technique pour faire ce que je veux élégamment et sans trop perdre en performances ??

  10. #10
    Rédacteur

    Avatar de SQLpro
    Homme Profil pro
    Expert bases de données / SQL / MS SQL Server / Postgresql
    Inscrit en
    Mai 2002
    Messages
    21 787
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Expert bases de données / SQL / MS SQL Server / Postgresql
    Secteur : Conseil

    Informations forums :
    Inscription : Mai 2002
    Messages : 21 787
    Points : 52 794
    Points
    52 794
    Billets dans le blog
    5
    Par défaut
    Citation Envoyé par trucmuche2005 Voir le message
    Ah bon... C'est complètement con, non ? Enfin, c'est sans doute moi qui suis un peu trop naïf.
    C'est le raisonnement qu'ont pratiquement tous les développeurs qui n'ont pas été formé aux techniques de traitement ensembliste des SGBDR... Comme on leur a appris à factoriser en faisant des fonctions pour organiser leurs traitements, ils appliquent ces principes, même là ou c'est l'inverse qu'il faut faire, créant des dégâts monstrueux...

    J'avais imaginé mettre la sous-reqûete en fonction pour deux raisons : faciliter la lecture des requètes MySQL et aussi pour centraliser certaines sous-requêtes souvent utilisées, histoire de me faciliter la vie quand une modification sera nécessaire sur cette sous-requête : pas besoin de faire la modif à quinze endroits...
    Votre raisonnement est juste, mais il doit être encore une fois adapté aux SGBDR.
    Pour factoriser des requêtes, deux solutions :
    • utiliser des vues (tables construites dynamiquement par une requête)
    • utiliser des CTE (Common table Expression)

    Malheureusement MySQL est un des pires SGBD (pseudo relationnel) en la matière et ne sait pas optimiser correctement les vues et vient tout juste de commencer à intégrer les CTE qui datent de la norme SQL de 1999 (soit 18 ans de retard....)

    Du coup, ma question devient : est-ce qu'il y a une autre technique pour faire ce que je veux élégamment et sans trop perdre en performances ??
    Oui, changer de SGBD pseudo R pour un vrai bon SGBD Relationnel comme SQL Server, Oracle ou IBM DB2...

    Il n'y a pas de miracle. Quand c'est gratuit on en a pour son argent...

    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/ * * * * *

  11. #11
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Points : 59
    Points
    59
    Par défaut
    Merci pour cette discussion fort intéressante ! :-) Je suis très heureux d'avoir posé la question (et j'espère de ce fait avoir évité quelques "dégâts monstrueux" :-D)... Merci pour votre temps passé à me répondre ! :-) soyez sûr que je n'en resterai pas là et que j'en ferai quelque chose :-)

  12. #12
    Modérateur

    Profil pro
    dba
    Inscrit en
    Janvier 2010
    Messages
    5 643
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : dba

    Informations forums :
    Inscription : Janvier 2010
    Messages : 5 643
    Points : 13 092
    Points
    13 092
    Par défaut
    Bonjour,

    Citation Envoyé par SQLpro Voir le message
    Absolument pas.... Il n'y a aucune différence avec mon exemple.
    Bah... un peu quand même. On s'éloigne de la question initiale qui était de savoir s'il y avait une différence de perf entre l'utilisation d'un fonction et le fait de mettre directement le contenu de la fonction dans la requete.

    Le problème de perf que tu exposes est plus dû a un problème de sargabilité.

    D'ailleurs, pas besoin de table supplémentaire avec la liste des logarithmes, il suffit de modifier la requête pour la rendre sargable :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    SELECT * 
    FROM   MaTable 
    WHERE  LOG(C) = 0;
    ==>
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    SELECT * 
    FROM   MaTable 
    WHERE  C = EXP(0);

  13. #13
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Points : 59
    Points
    59
    Par défaut
    Je reste à l'écoute... car effectivement, aieeeuuuuu a bien compris ma question. Et il est bien entendu qu'ici, je dois rester avec MySQL, c'est une contrainte de base et c'est pour cela que j'ai posté dans ce forum...

  14. #14
    Expert éminent sénior Avatar de Artemus24
    Homme Profil pro
    Agent secret au service du président Ulysses S. Grant !
    Inscrit en
    Février 2011
    Messages
    6 388
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Agent secret au service du président Ulysses S. Grant !
    Secteur : Finance

    Informations forums :
    Inscription : Février 2011
    Messages : 6 388
    Points : 19 107
    Points
    19 107
    Par défaut
    Salut trucmuche2005.

    Je n'aime pas raisonner dans le vide sur des questions trop vague car il suffit d'un rien pour changer la réponse à une question donnée.

    Citation Envoyé par trucmuche2005
    Je m'interroge sur une chose concernant la vitesse d'exécution dans le cas où on utilise des fonctions par rapport à si on ne les utilisait pas.
    Avant de vous poser des questions métaphysiques, il serait bon d'analyser votre problème et ensuite de trouver la solution qui se prête le mieux à ce que vous cherchez à faire.

    Citation Envoyé par trucmuche2005
    créer une fonction et utiliser son nom dans une requête est-il plus rapide que de mettre le code de la fonction directement dans la requête qui l'utilise ??
    Cela dépend de ce que vous faites !

    Une fonction n'a pas de mémoire et donc si vous l'appelez 1 million de fois, elle va refaire le même calcul 1 million de fois.
    Cela peut être la réponse à votre attente, si vous devez faire un calcul qui dépend de cette ligne.
    Mais comme le dit SQLPRO, vous raisonnez séquentiellement (en mode itératif) et non ensemble de données. La différente se fait sentir en terme de performance !

    Analysons votre requête :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT    type
        INTO  result
        FROM  table2
       WHERE  id = _id
    ORDER BY  timestamp DESC LIMIT 1
    Votre requête n'est pas très optimisée. Pourquoi ? Je n'ai pas trop ce "limit 1" !
    D'après ce que j'ai pu comprendre, vous recherchez pour un "id" (je suppose passage en paramètre de votre fonction), le type, ayant le plus grand "timestamp".

    Si vous avez plusieurs lignes ayant le même identifiant, vous devez sélectionner la ligne ayant le plus grand "timestamp".

    Je soupçonne déjà un problème de modélisation. Vous recherchez un "type" associé à un "identifiant" donné.
    Si vous avez toujours le même "type" pour un "identifiant" donné, pourquoi le mettre dans cette table ?
    Vous allez créer des répétitions inutiles.

    Dans le cas où il y a plusieurs "type" pour un "identifiant" donné, pourquoi celui ayant le "timestamp" le plus grand serait le bon ?
    J'espère aussi que vous n'avez pas plusieurs "timestamp" identique pour un "identifiant" donné.

    Vous cherchez à récupérer le "type", mais rien n'indique qu'il est nécessairement unique !
    Le cas classique.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT    t1.type
        INTO  result
        FROM  table2 as t1
       WHERE  t1.timestamp = ( SELECT MAX(t2.timestamp) from result as t2 where t2.id = _id )
    GROUP BY  t1.id, t1.type
    Cette façon de faire est moins performante que la solution ci-après :

    La bonne façon de procéder.
    Il faut faire une jointure sur la même table.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    SELECT           t1.type
               INTO  result
               FROM  table2 as t1
    LEFT OUTER JOIN  table2 as t2
                 ON  t2.id = t1.id
                AND  t2.timestamp > t1.timestamp
              WHERE  t2.id is null;
    En terme de performance, cette solution est meilleure que la précédente.

    Poursuivons l'analyse.
    Vous avez deux tables, dont l'une est la mère (table1 )de l'autre (table2).
    Là encore, il vaut mieux faire une jointure entre vos deux tables. Ce qui donne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    SELECT           t1.id,
                     t1.nom,
                     t2.type
               FROM  table1 as t1
         INNER JOIN  table2 as t2
                 ON  t2.id        = t1.id
     
    LEFT OUTER JOIN  table2 as t3
                 ON  t3.id        = t1.id
                AND  t3.timestamp > t2.timestamp
              WHERE  t3.id is null;
    En résumé, je donne entièrement raison à SQLPRO.
    Faire une fonction pour traiter séquentiellement ce que l'on peut faire de manière plus performante par l'approche ensemble de données est une grosse connerie !!!

    Citation Envoyé par trucmuche2005
    L'avantage que je vois à créer une fonction est que la "maintenance" est plus facile
    Quelle maintenance ? Sur des petites applications de ce genre, il n'y a pas de maintenance.
    Et si on doit intervenir, c'est plutôt pour faire une refonte de l'application.

    Vous croyez que la maintenance doit être privilégiée au détriment de la performance ? Encore une autre connerie.
    Si vous utilisez dans vos traitements cinquante fois la même requête (???), dans ce cas là, mettez votre requête dans un script dédié à cet effet et incluez ce script en substitution de votre requête.
    Ainsi, votre requête sera isolé à un seul endroit, ce qui facilite la maintenance si elle a lieu, même si elle est appelée cinquante fois.
    Hormis la connaissance des bases de données, j'ai aussi l'impression que vous ne connaissez pas grand chose aux techniques de développements.

    @+
    Si vous êtes de mon aide, vous pouvez cliquer sur .
    Mon site : http://www.jcz.fr

  15. #15
    Modérateur

    Profil pro
    dba
    Inscrit en
    Janvier 2010
    Messages
    5 643
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : dba

    Informations forums :
    Inscription : Janvier 2010
    Messages : 5 643
    Points : 13 092
    Points
    13 092
    Par défaut
    Citation Envoyé par Artemus24 Voir le message
    Le cas classique.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT    t1.type
        INTO  result
        FROM  table2 as t1
       WHERE  t1.timestamp = ( SELECT MAX(t2.timestamp) from result as t2 where t2.id = _id )
    GROUP BY  t1.id, t1.type
    Cette façon de faire est moins performante que la solution ci-après :

    La bonne façon de procéder.
    Il faut faire une jointure sur la même table.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    SELECT           t1.type
               INTO  result
               FROM  table2 as t1
    LEFT OUTER JOIN  table2 as t2
                 ON  t2.id = t1.id
                AND  t2.timestamp > t1.timestamp
              WHERE  t2.id is null;
    En terme de performance, cette solution est meilleure que la précédente.
    C'est sacrément péremptoire, et ce sera pourtant faux dans bien des cas si les bon index sont posés. En fonction de la répartition des données, la solution 1 pourra être bien meilleure !

  16. #16
    Expert éminent sénior Avatar de Artemus24
    Homme Profil pro
    Agent secret au service du président Ulysses S. Grant !
    Inscrit en
    Février 2011
    Messages
    6 388
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Agent secret au service du président Ulysses S. Grant !
    Secteur : Finance

    Informations forums :
    Inscription : Février 2011
    Messages : 6 388
    Points : 19 107
    Points
    19 107
    Par défaut
    Salut aieeeuuuuu.

    Faites le test et vous vous rendrez compte par vous même que la seconde solution est plus rapide que la première.

    C'est même escartefigue qui m'a donné cette solution, il y a déjà fort longtemps.

    @+
    Si vous êtes de mon aide, vous pouvez cliquer sur .
    Mon site : http://www.jcz.fr

  17. #17
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Points : 59
    Points
    59
    Par défaut
    Bonjour à vous,

    Et merci encore pour vos interventions.

    @ Artemus24 : même si votre compétence en la matière est indéniable par rapport à la mienne, je n'apprécie pas beaucoup votre ton relativement dénigrant et désagréable. Votre dernier message transpire la suffisance et je vous saurais gré de tolérer que d'autres sont là pour apprendre.
    Vous écrivez : "Vous croyez que la maintenance doit être privilégiée au détriment de la performance ? Encore une autre connerie." -- je n'ai jamais écrit ni pensé cela, justement... je pose la question justement pour allier les deux, vous n'avez pas compris cela ? Vous m'avez alors mal lu...
    Aussi, vous écrivez "Hormis la connaissance des bases de données, j'ai aussi l'impression que vous ne connaissez pas grand chose aux techniques de développements." -- De fait mais je n'ai jamais prétendu le contraire. J'apprends, petit à petit, et échanger avec des pros et des gens plus compétent fait partie de l'apprentissage.

    Constructivement maintenant, si vous le voulez bien.

    Vous dites : "Si vous utilisez dans vos traitements cinquante fois la même requête (???), dans ce cas là, mettez votre requête dans un script dédié à cet effet et incluez ce script en substitution de votre requête."
    C'est EXACTEMENT ma question initiale mais je cherche à rester dans MySQL et ma question est donc : "comment créer ce script dédié dont vous parlez en MySQL (cad sans sortir de MySQL) ??". À vous lire, je comprends que ce n'est pas en terme de fonction comme je le tentais (cfr mon premier post).

  18. #18
    Modérateur

    Profil pro
    dba
    Inscrit en
    Janvier 2010
    Messages
    5 643
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : dba

    Informations forums :
    Inscription : Janvier 2010
    Messages : 5 643
    Points : 13 092
    Points
    13 092
    Par défaut
    Citation Envoyé par Artemus24 Voir le message
    Faites le test ...
    OK
    Par contre je n'ai pas de MySQL sous la main et pas le temps (surtout pas l'envie en fait) de l'installer. Le test est donc fait sur SQL Server, je poste quand même le code, je vous laisserai l'adapter a MySQL.
    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
     
    CREATE TABLE Table2 (
    	id INT NOT NULL --PRIMARY KEY IDENTITY
    	,type  int
    	,timestamp DATETIME2(3)
    );
     
    INSERT INTO Table2 
    	SELECT TOP(50000) 1,1,'20170101'
    	FROM sys.objects A, sys.objects B, sys.objects C
    ;
    INSERT INTO Table2 
    	SELECT TOP(50000) 2,1,'20170101'
    	FROM sys.objects A, sys.objects B, sys.objects C
    ;
    INSERT INTO Table2 
    	SELECT TOP(50000) 3,1,'20170101'
    	FROM sys.objects A, sys.objects B, sys.objects C
    ;
     
    WITH T as (select row_number() OVER(partition by id order by id) as rn, timestamp from table2)
    	update T set timestamp = DATEADD(DAY, rn, timestamp)
    ;
     
    CREATE INDEX IX_TABLE2_ID_TS ON Table2(id, timestamp) INCLUDE (type);

    On a donc une table contenant 3 id distinct, avec chacun 50000 timestamp sans doublon.

    Pour la première requête (que j'ai corrigée) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    SELECT    t1.type
        FROM  table2 as t1
       WHERE  t1.timestamp = ( SELECT MAX(t2.timestamp) from table2 as t2 where t2.id = t1.id )
    GROUP BY  t1.id, t1.type
    ;
    ==>
    Table 'Table2'. Nombre d'analyses 1, lectures logiques 543
    Temps UC = 16*ms, temps écoulé = 49*ms.
    
    et pour la seconde :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    SELECT           t1.type
               FROM  table2 as t1
    LEFT OUTER JOIN  table2 as t2
                 ON  t2.id = t1.id
                AND  t2.timestamp > t1.timestamp
              WHERE  t2.id is null;
    ==>
    Table 'Table2'. Nombre d'analyses 150001, lectures logiques 14148530
    Temps UC = 729382*ms, temps écoulé = 733753*ms.
    
    Citation Envoyé par Artemus24 Voir le message
    ...et vous vous rendrez compte par vous même que la seconde solution est plus rapide que la première.
    C'est même escartefigue qui m'a donné cette solution, il y a déjà fort longtemps.
    @+
    La solution 2 est donc ici 15000 fois plus lente, mais elle n'est pas forcément mauvaise en toute circonstances. Comme je le disais, la meilleure solution dépendra de la répartition des données.
    Dans mon test, j'ai volontairement mis peu d'id avec chacun beaucoup de timestamps. Mais si on inverse le ratio, la deuxième deviendra en effet plus efficace.

    Enfin tout ça nous éloigne un peu plus de la question initiale, qui est de savoir si il y avait une différence de performance entre utiliser une fonction ou mettre directement son contenu dans la requête.

  19. #19
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Points : 59
    Points
    59
    Par défaut
    Merci pour ce test, aieeuuuuu! Pour retrouver le cadre de ma question initiale, je serais curieux de connaître le temps d'exécution de la requête 1 (la plus rapide des deux tiennes) si tu mettais la sous-requête "SÉLECT" en fonction... aurais-tu encore la possibilité de faire ce test de ton côté ? Ce serait super sympa (et instructif). Je ferai le même sous mysql...

  20. #20
    Modérateur

    Profil pro
    dba
    Inscrit en
    Janvier 2010
    Messages
    5 643
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : dba

    Informations forums :
    Inscription : Janvier 2010
    Messages : 5 643
    Points : 13 092
    Points
    13 092
    Par défaut
    Pour rester comparable à ce qui est faisable sous MySQL, j'ai créé une fonction multi instructions :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    CREATE FUNCTION UDF_GET_MAX_TS(@id INT)
    RETURNS DATETIME2(3)
    WITH SCHEMABINDING
    AS
    BEGIN
    	DECLARE @result DATETIME2(3)
     
    	SELECT @Result = MAX(timestamp) from dbo.table2 where id = @id
     
    	RETURN @result
    END
    puis repris la requête 1 pour utiliser la fonction :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    SELECT    t1.type
        FROM  table2 as t1
    	WHERE  t1.timestamp = dbo.UDF_GET_MAX_TS(t1.id)
    GROUP BY  t1.id, t1.type
    ;
    ==>
    Table 'Worktable'. Nombre d'analyses 0, lectures logiques 0, lectures physiques 0, lectures anticipées 0, lectures logiques de données d'objets volumineux 0, lectures physiques de données d'objets volumineux 0, lectures anticipées de données d'objets volumineux 0.
    Table 'Workfile'. Nombre d'analyses 0, lectures logiques 0, lectures physiques 0, lectures anticipées 0, lectures logiques de données d'objets volumineux 0, lectures physiques de données d'objets volumineux 0, lectures anticipées de données d'objets volumineux 0.
    Table 'Table2'. Nombre d'analyses 1, lectures logiques 446, lectures physiques 0, lectures anticipées 0, lectures logiques de données d'objets volumineux 0, lectures physiques de données d'objets volumineux 0, lectures anticipées de données d'objets volumineux 0.
    
    , Temps UC = 8580*ms, temps écoulé = 8783*ms.
    
    On a donc bien une nette perte de performance.

    Mais sous SQL Server, on pourrait faire à la place une fonction table en ligne, et l'optimiseur peut alors faire son office :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    CREATE FUNCTION UDFIL_GET_MAX_TS(@id INT)	
    RETURNS TABLE 
    WITH SCHEMABINDING
    AS
    RETURN 
    (
    	SELECT  MAX(timestamp) AS MAX_TS from dbo.table2 where id = @id
    )
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT    t1.type
        FROM  table2 as t1
    	WHERE  t1.timestamp = (SELECT MAX_TS FROM dbo.UDFIL_GET_MAX_TS(id))
    GROUP BY  t1.id, t1.type
    ;
    ==>
    Table 'Table2'. Nombre d'analyses 1, lectures logiques 543, lectures physiques 0, lectures anticipées 0, lectures logiques de données d'objets volumineux 0, lectures physiques de données d'objets volumineux 0, lectures anticipées de données d'objets volumineux 0.
    , Temps UC = 31*ms, temps écoulé = 69*ms.
    
    Toutefois je doute que ce type de fonction existe sous MySQL.
    Par ailleurs, les fonctions multi-instructions empêchent le parallélisme sous SQL Server, mais la question ne se pose pas sous MySQL qui n'en fait pas de toute façon...

Discussions similaires

  1. vitesse d'exécution d'une fonction personalisée
    Par metaldan dans le forum Macros et VBA Excel
    Réponses: 4
    Dernier message: 08/04/2010, 18h27
  2. [VB.NET, CF 2.0, SQLite] Vitesse d'exécution d'une fonction
    Par agro dans le forum Windows Mobile
    Réponses: 1
    Dernier message: 16/04/2009, 13h07
  3. Réponses: 2
    Dernier message: 24/08/2006, 10h46
  4. Réponses: 4
    Dernier message: 02/04/2006, 18h42
  5. Créer une fonction mathématique pendant l'exécution
    Par zeprogrameur dans le forum Langage
    Réponses: 5
    Dernier message: 09/07/2004, 11h36

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo