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

Langage SQL Discussion :

Meilleure requête de calcul d'âge et d'anniversaire approchant


Sujet :

Langage SQL

  1. #1
    Rédacteur

    Avatar de SQLpro
    Homme Profil pro
    Expert bases de données / SQL / MS SQL Server / Postgresql
    Inscrit en
    Mai 2002
    Messages
    21 768
    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 768
    Points : 52 719
    Points
    52 719
    Billets dans le blog
    5
    Par défaut Meilleure requête de calcul d'âge et d'anniversaire approchant
    Bonjour à tous.

    Dans cet article :
    Calculs SQL avec des dates : age exact révolu et anniversaires...
    je propose deux requêtes (SQL Server, mais faciles à traduire dans les autres dialectes) permettant :
    • de calculer l'age exact révolu en année des personnes
    • de trouver les personnes dont l'anniversaire tombe entre deux dates données


    Age exact révolu en années (fonctions SQL Server) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    SELECT *,
           DATEDIFF(YEAR, PRS_DATE_NAISSANCE, GETDATE())
           - CASE WHEN MONTH(PRS_DATE_NAISSANCE) < MONTH(GETDATE())  THEN -1
                  WHEN MONTH(PRS_DATE_NAISSANCE) = MONTH(GETDATE())
                       AND DAY(PRS_DATE_NAISSANCE) <= DAY(GETDATE()) THEN -1
                  ELSE 0 END - 1 AS AGE
    FROM   T_PERSONNE_PRS;
    La même en pseudo SQL normatif :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    SELECT *,
           EXTRACT(YEAR FROM CURRENT_DATE - PRS_DATE_NAISSANCE)
           - CASE WHEN EXTRACT(MONTH FROM PRS_DATE_NAISSANCE) < EXTRACT(MONTH FROM CURRENT_DATE)  THEN -1
                  WHEN EXTRACT(MONTH FROM PRS_DATE_NAISSANCE) = EXTRACT(MONTH FROM CURRENT_DATE)
                       AND EXTRACT(DAY FROM PRS_DATE_NAISSANCE) <= EXTRACT(DAY FROM CURRENT_DATE) THEN -1
                  ELSE 0
             END - 1 AS AGE
    FROM   T_PERSONNE_PRS;
    Anniversaire entre deux dates (fonctions MS SQL Server) :
    -- avec dates du 20 décembre au 10 janvier (an + 1)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT *
    FROM   T_PERSONNE_PRS
    WHERE          (MONTH(PRS_DATE_NAISSANCE) * 31 + DAY(PRS_DATE_NAISSANCE) ) % (31 * 12)
           BETWEEN (MONTH('2014-12-20')       * 31 + DAY('2014-12-20'))        % (31 * 12)
               AND (MONTH('2015-01-10')       * 31 + DAY('2015-01-10') + 372)  % (31 * 12);
    La même en SQL normatif :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT *
    FROM   T_PERSONNE_PRS
    WHERE          MOD((EXTRACT(MONTH FROM PRS_DATE_NAISSANCE) * 31 + EXTRACT(DAY FROM PRS_DATE_NAISSANCE) ) , (31 * 12))
           BETWEEN MOD((EXTRACT(MONTH FROM '2014-12-20')       * 31 + EXTRACT(DAY FROM '2014-12-20'))        , (31 * 12))
               AND MOD((EXTRACT(MONTH FROM '2015-01-10')       * 31 + EXTRACT(DAY FROM '2015-01-10') + 372)  , (31 * 12));
    Mais je pense qu'il y a peut être des requêtes plus performantes...

    Auriez vous quelques idées de rechange ?

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

  2. #2
    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,

    Pour l'âge exact, je verrais bien ceci, mais ça doit pas se jouer à grand chose (peut-être quelques calculs en moins...)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    SELECT *,
           DATEDIFF(MONTH, PRS_DATE_NAISSANCE, GETDATE()) / 12
           - CASE 
    			WHEN MONTH(PRS_DATE_NAISSANCE) = MONTH(GETDATE()) 
    			AND  DAY(PRS_DATE_NAISSANCE) > DAY(GETDATE()) 
    				THEN 1 
    			ELSE 0 
    		END AS AGE
    FROM   T_PERSONNE_PRS;

  3. #3
    Rédacteur/Modérateur

    Avatar de SergioMaster
    Homme Profil pro
    Développeur informatique retraité
    Inscrit en
    Janvier 2007
    Messages
    15 042
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 67
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur informatique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Janvier 2007
    Messages : 15 042
    Points : 40 955
    Points
    40 955
    Billets dans le blog
    62
    Par défaut
    Bonjour,

    en ajoutant +1 à la date du jour , on n'aurait même pas besoin du CASE (a mon humble avis et sans l'avoir encore testé)
    [Edit] au temps pour moi , en tout cas sous Firebird la proposition est fausse
    MVP Embarcadero
    Delphi installés : D3,D7,D2010,XE4,XE7,D10 (Rio, Sidney), D11 (Alexandria), D12 (Athènes)
    SGBD : Firebird 2.5, 3, SQLite
    générateurs États : FastReport, Rave, QuickReport
    OS : Window Vista, Windows 10, Windows 11, Ubuntu, Androïd

  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 768
    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 768
    Points : 52 719
    Points
    52 719
    Billets dans le blog
    5
    Par défaut
    Je viens de faire les métriques :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    SET STATISTICS TIME ON;
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    SELECT *, 
           DATEDIFF(YEAR, PRS_DATE_NAISSANCE, GETDATE())
           - CASE WHEN MONTH(PRS_DATE_NAISSANCE) < MONTH(GETDATE())  THEN -1
                  WHEN MONTH(PRS_DATE_NAISSANCE) = MONTH(GETDATE()) 
                       AND DAY(PRS_DATE_NAISSANCE) <= DAY(GETDATE()) THEN -1
                  ELSE 0 END - 1 AS AGE
    FROM   T_PERSONNE_PRS;
    -- Temps UC = 687*ms, temps écoulé = 8997*ms.
    -- Temps UC = 577*ms, temps écoulé = 8839*ms.
    -- Temps UC = 499*ms, temps écoulé = 9092*ms.
    -- Temps UC = 406*ms, temps écoulé = 8962*ms.
    -- Temps UC = 484*ms, temps écoulé = 9100*ms.
    -- MOY : UC = 531 ms, temps écoulé = 8998

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    SELECT *,
           DATEDIFF(MONTH, PRS_DATE_NAISSANCE, GETDATE()) / 12
           - CASE 
    			WHEN MONTH(PRS_DATE_NAISSANCE) = MONTH(GETDATE()) 
    			AND  DAY(PRS_DATE_NAISSANCE) > DAY(GETDATE()) 
    				THEN 1 
    			ELSE 0 
    		END AS AGE
    FROM   T_PERSONNE_PRS;
    -- Temps UC = 452*ms, temps écoulé = 8816*ms.
    -- Temps UC = 546*ms, temps écoulé = 8662*ms.
    -- Temps UC = 608*ms, temps écoulé = 8822*ms.
    -- Temps UC = 546*ms, temps écoulé = 8554*ms.
    -- Temps UC = 499*ms, temps écoulé = 8781*ms
    -- MOY : UC = 530 ms, temps écoulé = 8727

    Bref aucun gain significatif de calcul...

    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
    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 SQLpro Voir le message
    Bref aucun gain significatif de calcul...

    A +
    Non, en effet, je pense de toute façon qu'il va être difficile d'obtenir un gain significatif sans passer par quelque artifice, comme des colonnes calculées persistantes (l'avantage, c'est que les dates de naissances changent rarement, la mienne en tout cas n'a jamais changé )

    En revanche, la requête générique de calcul de date d'anniversaire approchante est incorrecte si la plage comprend le 1er décembre (en fait les calculs pour gérer le changement d'année ne font que déplacer le problème) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    SELECT *
    FROM   T_PERSONNE_PRS
    WHERE          (MONTH(PRS_DATE_NAISSANCE) * 31 + DAY(PRS_DATE_NAISSANCE) ) % (31 * 12)
           BETWEEN (MONTH('2014-11-20')       * 31 + DAY('2014-11-20'))        % (31 * 12)
               AND (MONTH('2014-12-10')       * 31 + DAY('2014-12-10') + 372)  % (31 * 12);
    --> aucun résultat.

  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 768
    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 768
    Points : 52 719
    Points
    52 719
    Billets dans le blog
    5
    Par défaut
    Oui, tu as raison, faut qu'on recale les 1 des mois et jours à 0...

    Je pense que cette formulation est bonne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    SELECT *
    FROM   T_PERSONNE_PRS
    WHERE          ((MONTH(PRS_DATE_NAISSANCE) -1 ) * 31 + (DAY(PRS_DATE_NAISSANCE) -1 ) ) % (31 * 12)
           BETWEEN ((MONTH('2013-11-20') - 1 )      * 31 + (DAY('2013-11-20') - 1) )       % (31 * 12)
               AND ((MONTH('2013-12-10') - 1 )      * 31 + (DAY('2013-12-10') + 371) )     % (31 * 12);
    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
    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
    Non, c'est toujours faux.
    Pas de résultat si la plage comprend le 1er janvier :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    SELECT *
    FROM   T_PERSONNE_PRS
    WHERE          ((MONTH(PRS_DATE_NAISSANCE) -1 ) * 31 + (DAY(PRS_DATE_NAISSANCE) -1 ) ) % (31 * 12)
           BETWEEN ((MONTH('2013-12-20') - 1 )      * 31 + (DAY('2013-12-20') - 1) )       % (31 * 12)
               AND ((MONTH('2014-01-10') - 1 )      * 31 + (DAY('2014-01-10') + 371) )     % (31 * 12);
    (D'ailleurs, il me semble que tu as remplacé la mauvaise requête dans l'article, celle du calcul d'âge au lieu de celle de la recherche d'anniversaire)
    ---


    En fait je ne pense pas qu'il soit possible de traiter tous les cas avec un simple BETWEEN (à moins de le truffer de CASE WHEN), car le modulo ne fait que "déplacer" la rupture annuelle...

    Personnellement, si je devais mettre en place ces deux types de requête, sachant qu'elle seraient appelées régulièrement, je ferais ceci :

    Ajout de colonnes calculées et index :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
     ALTER TABLE T_PERSONNE_PRS
    	ADD	PRS_MOIS_JOUR_NAISSANCE AS (MONTH(PRS_DATE_NAISSANCE) * 100 + DAY(PRS_DATE_NAISSANCE) )  PERSISTED
    	/* eventuellement : 	,PRS_ANNEE_NAISSANCE	AS (YEAR(PRS_DATE_NAISSANCE)) PERSISTED */
     GO 
     CREATE INDEX IX_PRS_MOIS_JOUR_NAISSANCE 
    	ON T_PERSONNE_PRS(PRS_MOIS_JOUR_NAISSANCE) 
    	INCLUDE(PRS_ID, PRS_DATE_NAISSANCE/*, PRS_ANNEE_NAISSANCE*/)
    Puis j'écrirais la requête de calcul d'âge comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    SELECT	PRS_ID
    	,PRS_DATE_NAISSANCE
    	,
           YEAR(GETDATE()) 
           - YEAR(PRS_DATE_NAISSANCE)
           - CASE 
    			WHEN MONTH(PRS_DATE_NAISSANCE)*100 + DAY(PRS_DATE_NAISSANCE) >  MONTH(GETDATE())*100+ DAY(GETDATE()) 
    				THEN 1 
    			ELSE 0 
    		END AS AGE
    FROM   T_PERSONNE_PRS

    Et la requête de recherche d'anniversaire comme ceci :
    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
     
    DECLARE @Deb DATE = '2014-12-20'
    DECLARE @Fin DATE = '2015-01-10'
     
    SELECT	PRS_ID
    	,PRS_DATE_NAISSANCE
    FROM	T_PERSONNE_PRS 
    WHERE
    	( 
    		YEAR(@Deb) = YEAR(@Fin) 
    		AND
    		MONTH(PRS_DATE_NAISSANCE)*100 + DAY(PRS_DATE_NAISSANCE)  
    			BETWEEN 	MONTH(@Deb)*100 + DAY(@Deb) 
    			AND		MONTH(@Fin)*100 + DAY(@Fin) 
    	)
    	OR
    	( 
    		YEAR(@Deb) < YEAR(@Fin) 
    		AND
    		MONTH(PRS_DATE_NAISSANCE)*100 + DAY(PRS_DATE_NAISSANCE) 
    			NOT  BETWEEN 	MONTH(@Fin)*100 + DAY(@Fin) + 1 
    			AND		MONTH(@Deb)*100 + DAY(@Deb) - 1
    	)
    OPTION (RECOMPILE)
    -->
    Temps UC = 16*ms, temps écoulé = 472*ms
    En forçant la recompilation, ou deux requêtes distinctes (une pour chaque cas) au sein d'une SP sans le OPTION(RECOMPILE).

Discussions similaires

  1. [SQL] Grosse requête SQL + calculs
    Par nicopoal dans le forum PHP & Base de données
    Réponses: 22
    Dernier message: 19/01/2007, 14h18
  2. Requête pour calculer le temps entre deux dates
    Par Badboy62cfp dans le forum Access
    Réponses: 2
    Dernier message: 19/05/2006, 13h50
  3. Ma requête ne calcul pas pour tout les champs
    Par leloup84 dans le forum Requêtes
    Réponses: 10
    Dernier message: 01/03/2006, 12h59
  4. Ma requête ne calcul pas pour tout les champs
    Par leloup84 dans le forum Langage SQL
    Réponses: 2
    Dernier message: 01/03/2006, 10h11
  5. [Debutant]Problème Requête et Calcul
    Par ghan77 dans le forum Bases de données
    Réponses: 9
    Dernier message: 04/01/2006, 14h47

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