Solutions pour le petit jeu des requêtes SQL
   
   
   
	II. Solutions - 2° partie
			
      II-A. Solution au problème n° 11 - premiers 
         
         
         
         
         
         
         
         
         
         
      
      II-B. Solution au problème n° 12 - traduction 
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
      
      II-C. Solution au problème n° 13 - les bons joeurs 
         
         
         
         
         
         
         
      
      II-D. Solution au problème n° 14 - tranches d'âge 
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
      
      II-E. Solution au problème n° 15 - Les articles
         
         
         
         
         
         
         
         
         
      
      II-F. Solution au problème n° 16 - tri alphabétique
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
      
      II-G. Solution au problème n° 17 - appariement
         
         
         
         
         
         
         
         
      
      II-H. Solution au problème n° 18 - meilleure correspondance partielle 
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
      
      II-I. Solution au problème n° 19 - la médiane 
         
         
         
         
         II-I-1. Solution question 1 : les données à calculées sont toutes différentes (il n'y a pas de doublon) et en nombre impaires :
            
            
            
            
            
            
            
            
            
            
         
         II-I-2. Solution question 2 : les données à calculées sont toutes différentes (il n'y a pas de doublon) et en nombre paires : 
            
            
            
            
            
            
            
            
         
         II-I-3. Solution question 3 : les données à calculer admettent des doublons et un nombre de lignes impairs
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
         
      
      II-J. Solution au problème n° 20 - insertion en bloc
         
         
         
         
         
         
         
         
         
      
	
   
   
   
   
	II. Solutions - 2° partie
			
      II-A. Solution au problème n° 11 - premiers 
         
         
            Il suffit de prendre le problème à l'envers et de récupérer facilement tous les nombres non premiers, c'est-à-dire ceux pour lesquels le reste de le division entière par au moins un des nombres qui lui est inférieur vaut 0 :
         
         | SELECTEN1.ENT_NFROMT_ENTIER_ENT EN1
       CROSSJOINT_ENTIER_ENT EN2WHEREMOD(EN1.ENT_N, EN2.ENT_N)=0ANDEN2.ENT_NBETWEEN2ANDEN1.ENT_N-1;
 | 
            Pour ce faire, on réalise le produit cartésien du nombre avec tous les nombres compris entre 2 et ce nombre moins un et l'on teste si le reste de la division entière vaut zéro. Si ce reste vaut zéro, alors preuve est faite que ce nombre n'est pas premier.
         
         
            Dès que nous avons tous les nombres non premiers, il ne s'agit plus que de trouver le complément : 
         
         | SELECT*FROMT_ENTIER_ENTWHEREENT_NNOTIN(SELECTEN1.ENT_NFROMT_ENTIER_ENT EN1
                            CROSSJOINT_ENTIER_ENT EN2WHEREMOD(EN1.ENT_N, EN2.ENT_N)=0ANDEN2.ENT_NBETWEEN2ANDEN1.ENT_N-1)ORDERBY1;
 | 
            Ceci peut aussi être fait à l'aide de l'opérateur ensembliste EXCEPT
         
         | SELECT*FROMT_ENTIER_ENT
EXCEPTSELECTEN1.ENT_NFROMT_ENTIER_ENT EN1
       CROSSJOINT_ENTIER_ENT EN2WHEREMOD(EN1.ENT_N, EN2.ENT_N)=0ANDEN2.ENT_NBETWEEN2ANDEN1.ENT_N-1ORDERBY1;
 | 
            La requête pour MS SQL Server :
         
         | SELECT*FROMT_ENTIER_ENTWHEREENT_NNOTIN(SELECTEN1.ENT_NFROMT_ENTIER_ENT EN1
                            CROSSJOINT_ENTIER_ENT EN2WHEREEN1.ENT_N%EN2.ENT_N=0ANDEN2.ENT_NBETWEEN2ANDEN1.ENT_N-1)ORDERBY1;
 | 
II-B. Solution au problème n° 12 - traduction 
         
         
            Facile avec la fonction COALESCE et des sous requêtes. Voici un premier exemple de solution.
            
            Solution 1 :
         
         | SELECTTDR_LIBELLE, TDR_ID, TDR_LANGUEFROMT_TRADUCTION_TDR TDRWHERETDR_LANGUE=COALESCE((SELECTDISTINCTTDR_LANGUEFROMT_TRADUCTION_TDRWHERETDR_LANGUE='Français'ANDTDR_ID=TDR.TDR_ID), (SELECTDISTINCTTDR_LANGUEFROMT_TRADUCTION_TDRWHERETDR_LANGUE='Anglais'ANDTDR_ID=TDR.TDR_ID)) ;
 | 
            Une solution élégante nous a été donnée par Sonia.
         
         | Solution 2 : | 
| SELECTTDR_LIBELLE, TDR_ID, TDR_LANGUEFROMT_TRADUCTION_TDRWHERETDR_LANGUE='Français'UNIONSELECTTDR_LIBELLE, TDR_ID, TDR_LANGUEFROMT_TRADUCTION_TDRWHERETDR_LANGUE='Anglais'ANDTDR_IDNOTIN(SELECTTDR_IDFROMT_TRADUCTION_TDRWHERETDR_LANGUE='Français')
 | 
            Cette solution n'utilise pas la fonction COALESCE.
            
            Enfin, une solution sans utiliser de sous requêtes.
         
         | Solution 3 : | 
| SELECTCOALESCE(fr.TDR_LIBELLE, en.TDR_LIBELLE)ASTDR_LIBELLE,
       COALESCE(fr.TDR_LANGUE, en.TDR_LANGUE)ASTDR_LANGUE,
       COALESCE(fr.TDR_ID, en.TDR_ID)ASTDR_IDFROMT_TRADUCTION_TDR tLEFTOUTERJOINT_TRADUCTION_TDRasenONen.TDR_ID=t.TDR_IDANDen.TDR_LANGUE='Anglais'LEFTOUTERJOINT_TRADUCTION_TDRasfrONfr.TDR_ID=t.TDR_IDANDfr.TDR_LANGUE='Français'GROUPBYt.TDR_ID, fr.TDR_LIBELLE, en.TDR_LIBELLE, fr.TDR_LANGUE, en.TDR_LANGUE, fr.TDR_ID, en.TDR_ID
 | 
            Si l'on analyse les performances des trois requêtes à l'aide d'un outil qui visualise le plan d'exécution (MS SQL Server), on est frappé par la grande différence qui existe entre la première version et les deux suivantes...
         
         
            Solution 1 (24 phases de traitement) : 
            
         
         
         
            Solution 2 (7 phases de traitement) :
         
         
         
            Solution 3 (8 phases de traitement) :
         
         
         
            Mais la manière de faire n'indique pas toujours le coût exact. Mieux vaut s'interroger sur le nombre de lectures effectuées pour chacune des requêtes :
         
         | Solution 1 : | 
| Table 'T_TRADUCTION_TDR'. Compte d'analyses 7, lectures logiques 7, lectures physiques 0, lectures anticipées 0.
 | 
| Solution 2 : | 
| Table 'T_TRADUCTION_TDR'. Compte d'analyses 4, lectures logiques 4, lectures physiques 0, lectures anticipées 0.
 | 
| Solution 3 : | 
| Table 'T_TRADUCTION_TDR'. Compte d'analyses 9, lectures logiques 9, lectures physiques 0, lectures anticipées 0.
 | 
            Autrement dit, la solution 3, pourtant assez économe en phases de traitement, est pire en terme de lecture que la première.
         
      
      II-C. Solution au problème n° 13 - les bons joeurs 
         
         
            En fait, la difficulté réside dans la jointure... Mais un simple CROSS JOIN suffit à résoudre le problème.
             
         
         
            Pour compter les bonne réponses il suffit d'utiliser le CASE et d'attribuer la valeur 1 si la réponse est bonne et la valeur 0 si elle est fausse. La somme de ces valeurs donne le score.
         
         
            Voici donc une solution :
             
         
         | SELECTPNL_NOM,CASEWHENJ.PNL_REPONSE1=R.RPS_REPONSE1THEN1ELSE0END+CASEWHENJ.PNL_REPONSE2=R.RPS_REPONSE2THEN1ELSE0END+CASEWHENJ.PNL_REPONSE3=R.RPS_REPONSE3THEN1ELSE0END+CASEWHENJ.PNL_REPONSE4=R.RPS_REPONSE4THEN1ELSE0END+CASEWHENJ.PNL_REPONSE5=R.RPS_REPONSE5THEN1ELSE0ENDASBONNE_REPONSEFROMT_PANEL_PNL J
      CROSSJOINT_REPONSES_RPS RORDERBYBONNE_REPONSEDESC;
 | 
            
            Une solution dans le même genre nous a été donnée par Fabien C. :
         
         | SELECTPNL_NOM, 
       ((SELECTCOUNT(*)FROMT_REPONSES_RPS RWHERER.RPS_REPONSE1=J.PNL_REPONSE1)+(SELECTCOUNT(*)FROMT_REPONSES_RPS RWHERER.RPS_REPONSE2=J.PNL_REPONSE2)+(SELECTCOUNT(*)FROMT_REPONSES_RPS RWHERER.RPS_REPONSE3=J.PNL_REPONSE3)+(SELECTCOUNT(*)FROMT_REPONSES_RPS RWHERER.RPS_REPONSE4=J.PNL_REPONSE4)+(SELECTCOUNT(*)FROMT_REPONSES_RPS RWHERER.RPS_REPONSE5=J.PNL_REPONSE5))AST_REPONSES_RPSFROMT_PANE id="XV"L_PNL J ;
 | 
II-D. Solution au problème n° 14 - tranches d'âge 
         
         
            Plusieurs petites difficultés :
         
         
            - 
               le calcul de l'âge ;
            
- 
               la réalisation des tranches ;
            
- 
               les effets de bords...
            
            Pour le calcul de l'âge, je me sert de la formule suivante :
         
         | CAST(CURRENT_DATE -CLI_DATE_NAISSANCEASINTERVAL DAY)/365.2425
 | 
            
            qui me donne un age décimal.
         
         
            Pour la réalisation des tranches, il faut ajouter l'information puisqu'elle n'y est pas. Construisons donc une nouvelle table :
         
         | CREATETABLET_TRANCHE_AGE_TAG
(TAG_LIMITEINTEGER) ;
 | 
| INSERTINTOT_TRANCHE_AGE_TAGVALUES(18) ;INSERTINTOT_TRANCHE_AGE_TAGVALUES(40) ;
 | 
            
            Ces informations nous suffisent, mais il serait plus pratique d'avoir la représentation des tranches sous la forme :
         
         | TRANCHE_MIN   TRANCHE_MAX
------------- -------------
0             18
18            40
40            NULL
 | 
            Car nous pourrons alors joindre la table des clients par une fourchette de valeur à cette représentation. Bien entendu nous ferrons en sorte de représenter le NULL comme étant une valeur loin dans le futur.
         
         
            Cette requête nous donne presque la solution :
            
         
         | SELECT*FROMT_TRANCHE_AGE_TAG TA1LEFTOUTERJOINT_TRANCHE_AGE_TAG TA2ONTA1.TAG_LIMITE<TA2.TAG_LIMITE ;
 | 
| TAG_LIMITE  TAG_LIMITE  
----------- ----------- 
18          40
40          NULL
 | 
            Il faut juste rajouter la tranche commençant par zéro. Cela peut être fait à l'aide d'une opération d'UNION. Profitons-en pour mettre tout cela dans une vue :
         
         | CREATEVIEWV_TRANCHE_AGE_TAGASSELECTTA1.TAG_LIMITEASTRANCHE_MIN, TA2.TAG_LIMITEASTRANCHE_MAXFROMT_TRANCHE_AGE_TAG TA1LEFTOUTERJOINT_TRANCHE_AGE_TAG TA2ONTA1.TAG_LIMITE<TA2.TAG_LIMITEUNIONSELECT0,MIN(TAG_LIMITE)FROMT_TRANCHE_AGE_TAG ;
 | 
            Assemblons maintenant tout ceci dans la requête finale :
         
         | SELECTTRANCHE_MIN, COALESCE(TRANCHE_MAX,9999)ASTRANCHE_MAX,COUNT(*)ASNOMBREFROMV_TRANCHE_AGE_TAG TAGINNERJOINT_CLIENT_CLIONCAST(CURRENT_DATE-CLI_DATE_NAISSANCEASINTERVAL DAY)/365.2425BETWEENTRANCHE_MINANDCOALESCE(TRANCHE_MAX,9999)GROUPBYTRANCHE_MIN, TRANCHE_MAXORDERBYTRANCHE_MIN ;
 | 
| TRANCHE_MIN TRANCHE_MAX NOMBRE      
----------- ----------- ----------- 
0           18          6
18          40          4
40          9999        13
 | 
            Néanmoins cette solution n'est pas correcte. En effet ceux qui ont exactement 18 ans ou 40 ans sont comptabilisé 2 fois. Voici ce qui serait beaucoup plus correct :
         
         | SELECTTRANCHE_MIN, COALESCE(TRANCHE_MAX,9999)ASTRANCHE_MAX,COUNT(*)ASNOMBREFROMV_TRANCHE_AGE_TAG TAGINNERJOINT_CLIENT_CLIONCAST(CURRENT_DATE-CLI_DATE_NAISSANCEASINTERVAL DAY)/365.2425>TRANCHE_MINANDCAST(CURRENT_DATE-CLI_DATE_NAISSANCEASINTERVAL DAY)/365.2425<=COALESCE(TRANCHE_MAX,999)GROUPBYTRANCHE_MIN, TRANCHE_MAXORDERBY1;
 | 
            La requête pour MS SQL Server :
         
         | SELECTTRANCHE_MIN, COALESCE(TRANCHE_MAX,9999)ASTRANCHE_MAX,COUNT(*)ASNOMBREFROMV_TRANCHE_AGE_TAG TAGINNERJOINT_CLIENT_CLIONDATEDIFF(DAY, CLI_DATE_NAISSANCE, CURRENT_TIMESTAMP)/365.2425>TRANCHE_MINANDDATEDIFF(DAY, CLI_DATE_NAISSANCE, CURRENT_TIMESTAMP)/365.2425<=COALESCE(TRANCHE_MAX,999)GROUPBYTRANCHE_MIN, TRANCHE_ id="XVI"MAXORDERBY1;
 | 
II-E. Solution au problème n° 15 - Les articles
         
         
            La encore la technique consiste à rajouter de l'information à notre base de données. Notamment en créant une tables des articles parasites. Pour cela nous créons une table de nom T_ARTICLE_ATC avec l'article considéré et sa longueur incluant soit le blanc soit l'apostrophe avec le type SQL CHAR et non VARCHAR qui élimine les blancs finaux, et nous stockons la longueur considérée de cet article avec le blanc ou l'apostrophe, comme ceci :
         
         | CREATETABLET_ARTICLE_ATC 
(ATC_ARTICLEVARCHAR(8), 
 ATC_LONGUEURINTEGER);
 | 
            On y insère les principaux articles de la langue française :
             
         
         | INSERTINTOT_ARTICLE_ATCVALUES('L''',2) ;INSERTINTOT_ARTICLE_ATCVALUES('LE',3) ;INSERTINTOT_ARTICLE_ATCVALUES('LA',3) ;INSERTINTOT_ARTICLE_ATCVALUES('LES',4) ;INSERTINTOT_ARTICLE_ATCVALUES('UN',3) ;INSERTINTOT_ARTICLE_ATCVALUES('UNE',4) ;INSERTINTOT_ARTICLE_ATCVALUES('DE',3) ;INSERTINTOT_ARTICLE_ATCVALUES('DES',4) ;INSERTINTOT_ARTICLE_ATCVALUES('D''',2) ;
 | 
            Ensuite, la requête consiste à lier les deux tables par jointure externe afin de conserver tous les titres en utilisant comme critère de jointure le début du titre par rapport au contenu de la table des articles. Si jointure il y a (donc colonne ATC_ARTICLE non vide), alors on transforme le titre en supprimant l'article en tête du titre et en le repoussant en fin entre parenthèses. La requête suivante répond exactement à la demande du sieur Bouchon :
         
         | SELECTCASEWHENATC.ATC_ARTICLEISNULLTHENOVG.OVG_TITREELSESUBSTRING(OVG.OVG_TITREFROMATC.ATC_LONGUEUR+1FOR64-ATC.ATC_LONGUEUR)+' ('+TRIM(RIGHT, SUBSTRING(OVG.OVG_TITREFROM1FORATC.ATC_LONGUEUR))+')'ENDASTITREFROMT_OUVRAGES_OVG OVGLEFTOUTERJOINT_ARTICLE_ATC ATCONUPPER(SUBSTRING(OVG.OVG_TITREFROM1FORATC.ATC_LONGUEUR))=SUBSTRING(ATC.ATC_ARTICLEFROM1FORATC.ATC_LONGUEUR)  ;
 | 
            La requête pour MS SQL Server :
         
         | SELECTCASEWHENATC.ATC_ARTICLEISNULLTHENOVG.OVG_TITREELSESUBSTRING(OVG.OVG_TITRE, ATC.ATC_LONGUEUR+1,64-ATC.ATC_LONGUEUR)+' ('+RTRIM(SUBSTRING(OVG.OVG_TITRE,1, ATC.ATC_LONGUEUR))+')'ENDASTITREFROMT_OUVRAGES_OVG OVGLEFTOUTERJOINT_ARTICLE_ATC ATCONUPPER(SUBSTRING(OVG.OVG_TITRE,1, ATC.ATC_LONGUEUR))=SUBSTRING(ATC.ATC_ARTIC id="XVII"LE,1, ATC.ATC_LONGUEUR) ;
 | 
II-F. Solution au problème n° 16 - tri alphabétique
         
         
            La solution consiste, comme souvent, à rajouter des informations afin de piloter l'ordre...
         
         
            Ajoutons une table avec les nombres et leur correspondance littérale :
         
         | CREATETABLET_NOMBRE_LETTRE_NBL 
(NBL_NOMBRECHAR(1), 
 NBL_LETTRESVARCHAR(16));
 | 
            Et insérons-y les valeurs suivantes :
         
         | INSERTINTOT_NOMBRE_LETTRE_NBLVALUES('1','Un');INSERTINTOT_NOMBRE_LETTRE_NBLVALUES('2','Deux');INSERTINTOT_NOMBRE_LETTRE_NBLVALUES('3','Trois');INSERTINTOT_NOMBRE_LETTRE_NBLVALUES ('4','Quatre');INSERTINTOT_NOMBRE_LETTRE_NBLVALUES('5','Cinq');INSERTINTOT_NOMBRE_LETTRE_NBLVALUES('6','Six');INSERTINTOT_NOMBRE_LETTRE_NBLVALUES ('7','Sept');INSERTINTOT_NOMBRE_LETTRE_NBLVALUES ('8','Huit');INSERTINTOT_NOMBRE_LETTRE_NBLVALUES ('9','Neuf');INSERTINTOT_NOMBRE_LETTRE_NBLVALUES('0','Zéro');
 | 
            
            Si votre SGBDR dispose des spécifications de collations conforme à la norme SQL, alors cette table suffit.
         
         
            Dès lors, la requête s'exprime de la sorte :
         
         | SELECTLVR_TITRE,CASEWHENNBL.NBL_NOMBREISNULLTHENLVR_TITREELSENBL_LETTRES||' '||SUBSTRING(LVR.LVR_TITREFROM2FORCHARACTER_LENGTH(LVR.LVR_TITRE)-1)ENDCOLLATE SQL_latin_DictionnaryUsageFROMT_LIVRE_LVR LVRLEFTOUTERJOINT_NOMBRE_LETTRE_NBL NBLONSUBSTRING(LVR.LVR_TITREFROM1FOR1)=NBL.NBL_NOMBREORDERBY2;
 | 
            
               NOTA 
            
            : Le nom de la collation est donné à titre indicatif, les éditeurs ayant toute latitude à ce sujet.
         
         
            La requête pour MS SQL Server :
         
         | SELECTLVR_TITREFROMT_LIVRE_LVR LVRLEFTOUTERJOINT_NOMBRE_LETTRE_NBL NBLONSUBSTRING(LVR.LVR_TITRE,1,1)=NBL.NBL_NOMBREORDERBYCASEWHENNBL.NBL_NOMBREISNULLTHENLVR_TITREELSENBL_LETTRES+' '+SUBSTRING(LVR.LVR_TITRE,2, LEN(LVR.LVR_TITRE)-1)ENDCOLLATE French_CI_AI ;
 | 
            SQL Server possède l'avantage de pouvoir effectuer un tri externe, c'est à dire sans que la colonne ne figure dans la clause SELECT. Ici la collation choisie permet de faire confusion entre les caractères accentuées ou non (et plus généralement les caractères diacritiques ou non : cédille, tilde, ligature...) et de confondre aussi la casse (majuscule/minuscule).
         
         
            Si telle n'était pas le cas; il faudrait alors allonger la table T_NOMBRE_LETTRE_NBL avec de nouvelles entrées telles que :
         
         | INSERTINTOT_NOMBRE_LETTRE_NBLVALUES('À','a');INSERTINTOT_NOMBRE_LETTRE_NBLVALUES('Â','a');INSERTINTOT_NOMBRE_LETTRE_NBLVALUES('â','a');
 | 
            Etc.
         
         
            
            Un seul problème subsisterait alors, celui de la gestion de l'espace pour le remplacement des nombres par les littéraux alors que cela n'est pas nécessaire pour id="XVIII" les caractères.
         
      
      II-G. Solution au problème n° 17 - appariement
         
         
            En fait une double condition doit être simultanément vraie : que tous les PER_2 de la valeur PER_1 scrutée soit dans les valeur de PER_2 en regard ET que tous les PER_2 en regard soit dans les valeurs des PER_2 de la valeur PER_1 scrutée. En négativant cette double condition, on obtient : 
         
         
            On joint la table sur elle-même avec équivalence pour PER_1 et différence pour PER_2. Pour plus de clarté, appelons les deux occurrences de notre table ORIGINE pour la première et DESTINATION pour la seconde :
         
         | SELECTDISTINCTORIGINE.PER_1, DESTINATION.PER_1FROMT_PAIRE_PER ORIGINEINNERJOINT_PAIRE_PER DESTINATIONONORIGINE.PER_2=DESTINATION.PER_2ANDORIGINE.PER_1<>DESTINATION.PER_1WHERENOTEXISTS(SELECT*FROMT_PAIRE_PERWHEREPER_1=ORIGINE.PER_1ANDPER_2NOTIN(SELECTPER_2FROMT_PAIRE_PERWHEREPER_1=DESTINATION.PER_1))ANDNOTEXISTS(SELECT*FROMT_PAIRE_PERWHEREPER_1=DESTINATION.PER_1ANDPER_2NOTIN(SELECTPER_2FROMT_PAIRE_PERWHEREPER_1=ORIGINE.PER_1))ORDERBY1,2;
 | 
            
               | Le premier filtre conduit au jeu de
 résultat suivant :
 | Le second filtre conduit au jeu de
 résultat suivant :
 | Solution | 
            
               | | PER_1 PER_1 
----- ----- 
A2    A3
A2    A4
A2    A6
A2    A8
A2    A9
A3    A6
A3    A8
A4    A2
A4    A3
A4    A6
A4    A8
A4    A9
A5    A1
A6    A3
A6    A8
A8    A3
A8    A6
 | 
 | | PER_1 PER_1 
----- ----- 
A1    A5
A2    A4
A3    A2
A3    A4
A3    A6
A3    A8
A4    A2
A6    A2
A6    A3
A6    A4
A6    A8
A8    A2
A8    A3
A8    A4
A8    A6
A9    A2
A9    A4
 | 
 | | PER_1 PER_1
----- ----- 
A2    A4
A3    A6
A3    A8
A4    A2
A6    A3
A6    A8
A8    A3
A8    A6
 | 
 | 
         
            Les données communes ont été mise en évidence par une couleur blanche et en gras.
         
         
            On peut lire ces filtres de la manière suivante : il ne faut pas qu'il existe une valeur de PER_2 de la table origine qui ne soit pas dans la table de destination. Et vice versa pour le second filtre.
         
         
            En fait le problème s'apparente à la division relationnelle et plus particulièrement à la division de Todd que Joe Celko évoque dans son ouvrage SQL for smarties (SQ id="XIX"L Avancé, Vuibert éditeur).
         
      
      II-H. Solution au problème n° 18 - meilleure correspondance partielle 
         
         
            Pour trouver la réponse à ce problème, il faut décomposer la chaîne de caractère composant le code en autant de chaînes de caractères de longueur allant de 1 à la valeur maximum permet le format du type SQL, ici 16. Puis de comptabiliser pour chacune de ces combinaisons, celle qui sont communes à l'aide d'un GROUP BY. Enfin de retenir de ce calcul celles ayant le nombre le plus élevé d'occurrences avec la longueur la plus grande.
         
         
            Pour décomposer la chaîne représentant le code, il faut une table des nombres allant de 1 à la longueur maximale de la colonnes RTE_CODE soit 16. Pour cela reprenons la table T_ENTIER_ENT de l'exercice 6 :
         
         | CREATETABLET_ENTIER_ENT
(ENT_NINTEGER);
 | 
| INSERTINTOT_ENTIER_ENTVALUES(0);INSERTINTOT_ENTIER_ENTVALUES(1);INSERTINTOT_ENTIER_ENTVALUES(2);INSERTINTOT_ENTIER_ENTVALUES(3);INSERTINTOT_ENTIER_ENTVALUES(4);INSERTINTOT_ENTIER_ENTVALUES(5);INSERTINTOT_ENTIER_ENTVALUES(6);INSERTINTOT_ENTIER_ENTVALUES(7);INSERTINTOT_ENTIER_ENTVALUES(8);INSERTINTOT_ENTIER_ENTVALUES(9) ;
 | 
| INSERTINTOT_ENTIER_ENTSELECTTEU.ENT_N+10*TED.ENT_N+100*TEC.ENT_N+1000*TEM.ENT_NFROMT_ENTIER_ENT TEU               
       CROSSJOINT_ENTIER_ENT TED    
       CROSSJOINT_ENTIER_ENT TEC    
       CROSSJOINT_ENTIER_ENT TEMWHERETEU.ENT_N+10*TED.ENT_N+100*TEC.ENT_N+1000*TEM.ENT_N>9
 | 
            Volontairement, je la peuple avec des valeurs nettement supérieures à la taille maximale de la colonne concernée...
         
         
            La requête est alors la suivante :
         
         | SELECTDISTINCTR.RTE_DESTINATION, SUBSTRING(RTE_CODEFROM1FORLNG)asRTE_CODEFROMT_ROUTE_RTE RINNERJOIN(SELECTRTE_DESTINATION,MAX(LNG)ASLNGFROM(SELECTRTE_DESTINATION, RTE_CODE2, LNG,COUNT(*)ASNBRFROM(SELECTRTE_DESTINATION,
                                          SUBSTRING(RTE_CODEFROM1FORENT_N)asRTE_CODE2,
                                          ENT_NASLNGFROMT_ROUTE_RTE
                                          CROSSJOINT_ENTIER_ENTWHEREENT_NBETWEEN1ANDCHARACTER_LENGTH(RTE_CODE)) TGROUPBYRTE_DESTINATION, RTE_CODE2, LNGHAVINGCOUNT(*)=(SELECTCOUNT(*)FROMT_ROUTE_RTEWHERERTE_DESTINATION=T.RTE_DESTINATIONGROUPBYRTE_DESTINATION)) TTGROUPBYRTE_DESTINATION) TTTONR.RTE_DESTINATION=TTT.RTE_DESTINATION
 | 
            La même requête pour MS SQL Server :
         
         | SELECTDISTINCTR.RTE_DESTINATION, SUBSTRING(RTE_CODE,1, LNG)asRTE_CODEFROMT_ROUTE_RTE RINNERJOIN(SELECTRTE_DESTINATION,MAX(LNG)ASLNGFROM(SELECTRTE_DESTINATION, RTE_CODE2, LNG,COUNT(*)ASNBRFROM(SELECTRTE_DESTINATION,
                                          SUBSTRING(RTE_CODE,1, ENT_N)asRTE_CODE2,
                                          ENT_NASLNGFROMT_ROUTE_RTE
                                          CROSSJOINT_ENTIER_ENTWHEREENT_NBETWEEN1ANDLEN(RTE_CODE)) TGROUPBYRTE_DESTINATION, RTE_CODE2, LNGHAVINGCOUNT(*)=(SELECTCOUNT(*)FROMT_ROUTE_RTEWHERERTE_DESTINATION=T.RTE_DESTINATIONGROUPBYRTE_DESTINATION)) TTGROUPBYRTE_DESTINATION) TTTONR.RTE_DESTINATION=TTT.RTE_DESTINATION
 | 
            
               Décomposition :
            
             
         
         
            Le calcul de toutes les sous chaines se fait à l'aide d'un produit cartésien avec la table des nombres.
             
            
            Pour un maximum d'efficacité on ne découpe pas les sous chaînes dont la longueur est supérieur à la taille de la données.
         
         
            On spécifie aussi la longueur dans la colonne LNG
             
         
         | SELECTRTE_DESTINATION,
       SUBSTRING(RTE_CODEFROM1FORENT_N)asRTE_CODE2,
       ENT_NASLNGFROMT_ROUTE_RTE
       CROSSJOINT_ENTIER_ENTWHEREENT_NBETWEEN1ANDCHARACTER_LENGTH(RTE_CODE) ;
 | 
            La même requête pour MS SQL Server :
         
         | SELECTRTE_DESTINATION,
       SUBSTRING(RTE_CODE,1, ENT_N)asRTE_CODE2,
       ENT_NASLNGFROMT_ROUTE_RTE
       CROSSJOINT_ENTIER_ENTWHEREENT_NBETWEEN1ANDLEN(RTE_CODE) ;
 | 
            Ce qui donne :
             
         
         | RTE_DESTINATION                  RTE_CODE2        LNG 
-------------------------------- ---------------- ----------- 
albania-Mobile                   0                1 
albania-Mobile                   00               2 
albania-Mobile                   003              3 
albania-Mobile                   0035             4 
albania-Mobile                   00355            5 
albania-Mobile                   003553           6 
albania-Mobile                   0035538          7 
albania-Mobile                   0                1 
albania-Mobile                   00               2 
albania-Mobile                   003              3 
albania-Mobile                   0035             4 
albania-Mobile                   00355            5 
albania-Mobile                   003556           6 
albania-Mobile                   0035569          7 
albania-Mobile                   0                1 
albania-Mobile                   00               2 
albania-Mobile                   003              3 
albania-Mobile                   0035             4 
albania-Mobile                   00355            5 
albania-Mobile                   003556           6 
albania-Mobile                   0035560          7 
SFR-Mobile                       0                1 
SFR-Mobile                       00               2 
SFR-Mobile                       007              3 
SFR-Mobile                       0077             4 
SFR-Mobile                       00772            5 
SFR-Mobile                       007728           6 
SFR-Mobile                       0077280          7 
SFR-Mobile                       0                1 
SFR-Mobile                       00               2 
SFR-Mobile                       007              3 
SFR-Mobile                       0077             4 
SFR-Mobile                       00773            5 
SFR-Mobile                       007739           6 
SFR-Mobile                       0077390          7 
BOUYGE_TEL                       0                1 
BOUYGE_TEL                       00               2 
BOUYGE_TEL                       007              3 
BOUYGE_TEL                       0078             4 
BOUYGE_TEL                       00784            5 
BOUYGE_TEL                       007845           6 
BOUYGE_TEL                       0078452          7 
 | 
            De ce résultat passé en sous requête, on compte le nombre de lignes identiques groupées par 
            RTE_DESTINATION, RTE_CODE et longueur de la chaîne :
         
         | SELECTRTE_DESTINATION, RTE_CODE2, LNG,COUNT(*)ASNBRFROM(SELECTRTE_DESTINATION,
               SUBSTRING(RTE_CODEFROM1FORENT_N)asRTE_CODE2,
               ENT_NASLNGFROMT_ROUTE_RTE
               CROSSJOINT_ENTIER_ENTWHEREENT_NBETWEEN1ANDCHARACTER_LENGTH(RTE_CODE)) TGROUPBYRTE_DESTINATION, RTE_CODE2, LNG ;
 | 
            La même requête pour MS SQL Server :
         
         | SELECTRTE_DESTINATION, RTE_CODE2, LNG,COUNT(*)ASNBRFROM(SELECTRTE_DESTINATION,
               SUBSTRING(RTE_CODE,1, ENT_N)asRTE_CODE2,
               ENT_NASLNGFROMT_ROUTE_RTE
               CROSSJOINT_ENTIER_ENTWHEREENT_NBETWEEN1ANDLEN(RTE_CODE)) TGROUPBYRTE_DESTINATION, RTE_CODE2, LNG ;
 | 
| RTE_DESTINATION                  RTE_CODE2        LNG         NBR 
-------------------------------- ---------------- ----------- ----------- 
BOUYGE_TEL                       0                1           1
BOUYGE_TEL                       00               2           1
BOUYGE_TEL                       007              3           1
BOUYGE_TEL                       0078             4           1
BOUYGE_TEL                       00784            5           1
BOUYGE_TEL                       007845           6           1
BOUYGE_TEL                       0078452          7           1
SFR-Mobile                       0                1           2
SFR-Mobile                       00               2           2
SFR-Mobile                       007              3           2
SFR-Mobile                       0077             4           2
SFR-Mobile                       00772            5           1
SFR-Mobile                       007728           6           1
SFR-Mobile                       0077280          7           1
SFR-Mobile                       00773            5           1
SFR-Mobile                       007739           6           1
SFR-Mobile                       0077390          7           1
albania-Mobile                   0                1           3
albania-Mobile                   00               2           3
albania-Mobile                   003              3           3
albania-Mobile                   0035             4           3
albania-Mobile                   00355            5           3
albania-Mobile                   003553           6           1
albania-Mobile                   0035538          7           1
albania-Mobile                   003556           6           2
albania-Mobile                   0035560          7           1
albania-Mobile                   0035569          7           1
 | 
            Le résultat nous apparaît comme étant celui pour lequel pour chaque RTE_DESTINATION, NBR est maximum et en second LNG est maximum. C'est ce que les lignes mise en exergue en blanc gras indique.
         
         
            Filtrons d'abord sur nombre au moyen d'une clause HAVING :
         
         | SELECTRTE_DESTINATION, RTE_CODE2, LNG,COUNT(*)ASNBRFROM(SELECTRTE_DESTINATION,
               SUBSTRING(RTE_CODEFROM1FORENT_N)asRTE_CODE2,
               ENT_NASLNGFROMT_ROUTE_RTE
               CROSSJOINT_ENTIER_ENTWHEREENT_NBETWEEN1ANDCHARACTER_LENGTH(RTE_CODE)) TGROUPBYRTE_DESTINATION, RTE_CODE2, LNGHAVINGCOUNT(*)=(SELECTCOUNT(*)FROMT_ROUTE_RTEWHERERTE_DESTINATION=T.RTE_DESTINATIONGROUPBYRTE_DESTINATION)
 | 
            La même requête pour MS SQL Server :
         
         | SELECTRTE_DESTINATION, RTE_CODE2, LNG,COUNT(*)ASNBRFROM(SELECTRTE_DESTINATION,
               SUBSTRING(RTE_CODE,1, ENT_N)asRTE_CODE2,
               ENT_NASLNGFROMT_ROUTE_RTE
               CROSSJOINT_ENTIER_ENTWHEREENT_NBETWEEN1ANDLEN(RTE_CODE)) TGROUPBYRTE_DESTINATION, RTE_CODE2, LNGHAVINGCOUNT(*)=(SELECTCOUNT(*)FROMT_ROUTE_RTEWHERERTE_DESTINATION=T.RTE_DESTINATIONGROUPBYRTE_DESTINATION) ;
 | 
            
            Qui donne :
         
         | RTE_DESTINATION                  RTE_CODE2        LNG         NBR
-------------------------------- ---------------- ----------- ----------- 
BOUYGE_TEL                       0                1           1
BOUYGE_TEL                       00               2           1
BOUYGE_TEL                       007              3           1
BOUYGE_TEL                       0078             4           1
BOUYGE_TEL                       00784            5           1
BOUYGE_TEL                       007845           6           1
BOUYGE_TEL                       0078452          7           1
SFR-Mobile                       0                1           2
SFR-Mobile                       00               2           2
SFR-Mobile                       007              3           2
SFR-Mobile                       0077             4           2
albania-Mobile                   0                1           3
albania-Mobile                   00               2           3
albania-Mobile                   003              3           3
albania-Mobile                   0035             4           3
albania-Mobile                   00355            5           3
 | 
            Dans ce résultat passé en sous requête, on recherche ensuite la longueur la plus grande pour chaque RTE_DESTINATION :
         
         | SELECTRTE_DESTINATION,MAX(LNG)ASLNGFROM(SELECTRTE_DESTINATION, RTE_CODE2, LNG,COUNT(*)ASNBRFROM(SELECTRTE_DESTINATION,
                       SUBSTRING(RTE_CODEFROM1FORENT_N)asRTE_CODE2,
                       ENT_NASLNGFROMT_ROUTE_RTE
                       CROSSJOINT_ENTIER_ENTWHEREENT_NBETWEEN1ANDCHARACTER_LENGTH(RTE_CODE)) TGROUPBYRTE_DESTINATION, RTE_CODE2, LNGHAVINGCOUNT(*)=(SELECTCOUNT(*)FROMT_ROUTE_RTEWHERERTE_DESTINATION=T.RTE_DESTINATIONGROUPBYRTE_DESTINATION)) TTGROUPBYRTE_DESTINATION ;
 | 
            La même requête pour MS SQL Server :
         
         | SELECTRTE_DESTINATION,MAX(LNG)ASLNGFROM(SELECTRTE_DESTINATION, RTE_CODE2, LNG,COUNT(*)ASNBRFROM(SELECTRTE_DESTINATION,
                       SUBSTRING(RTE_CODE,1, ENT_N)asRTE_CODE2,
                       ENT_NASLNGFROMT_ROUTE_RTE
                       CROSSJOINT_ENTIER_ENTWHEREENT_NBETWEEN1ANDLEN(RTE_CODE)) TGROUPBYRTE_DESTINATION, RTE_CODE2, LNGHAVINGCOUNT(*)=(SELECTCOUNT(*)FROMT_ROUTE_RTEWHERERTE_DESTINATION=T.RTE_DESTINATIONGROUPBYRTE_DESTINATION)) TTGROUPBYRTE_DESTINATION ;
 | 
            Qui donne :
         
         | RTE_DESTINATION                  LNG 
-------------------------------- ----------- 
BOUYGE_TEL                       7
albania-Mobile                   5
SFR-Mobile                       4
 | 
            Il ne suffit plus que de recoller les données avec la table d'origine et c'est ce qui donne la requête vue au début.
             
         
         |  | Il est important que le type de la colonne RTE_CODE soit en fixe (CH id="XX"AR) et non en alfa variable (VARCHAR). | 
      
      II-I. Solution au problème n° 19 - la médiane 
         
         
            La définition stricte de la médiane est la suivante : une valeur pour laquelle, lorsque toutes les valeurs sont classées, il y a autant de valeurs ordonnées avant qu'après. Pour réaliser ce traitement, on peut considérer que la médiane consiste à classer un ensemble en deux sous ensembles :
             
         
         
            - 
               l'un contenant les valeurs en dessous de la valeur cherchée,
                
            
- 
               l'autre contenant les valeurs au dessus des valeurs cherchées,
                
            
            et à condition que ces deux ensembles comportent le même nombre de lignes.
         
         II-I-1. Solution question 1 : les données à calculées sont toutes différentes (il n'y a pas de doublon) et en nombre impaires :
            
            | SSTT_ID      STT_VALEUR
----------- ----------------------------------------------------- 
1           22.0
3           22.5
5           23.0 <-- la médiane est ici : 23
4           24.0
2           27.5
 | 
               Pour calculer le nombre de valeurs en dessous, on peut faire :
                
            
            | SELECTCOUNT(*)FROMT_STATISTIQUES_STT SOUWHERESTT_VALEUR<[la valeur cherchée];
 | 
               Idem pour les valeurs au dessus
                :
            
            | SELECTCOUNT(*)FROMT_STATISTIQUES_STT SURWHERESTT_VALEUR>[la valeur cherchée];
 | 
               Lorsque les deux sous ensemble compte le même nombre de lignes
                
               à une ligne près, alors la valeur cherchée est la bonne.
                
               Une autre manière de formuler cela est de dire que le nombre de ligne de l'ensemble SUR moins le nombre de lignes de l'ensemble SOU doit être 0, 1 ou -1, suivant que le nombre de lignes total est pair ou impair. On peut exprime cela avec l'expression suivante :
                
            
            | ABS((SELECTCOUNT(*)FROMT_STATISTIQUES_STT SOUWHERESTT_VALEUR<[la valeur cherchée])-(SELECTCOUNT(*)FROMT_STATISTIQUES_STT SURWHERESTT_VALEUR>[la valeur cherchée]))<=1
 | 
               Pour obtenir la valeur cherchée, il suffit d'encapsuler cette expression en tant que sous requête corrélée :
            
            | SELECT*FROMT_STATISTIQUES_STT STTWHEREABS((SELECTCOUNT(*)FROMT_STATISTIQUES_STT SOUWHERESOU.STT_VALEUR<STT.STT_VALEUR)-(SELECTCOUNT(*)FROMT_STATISTIQUES_STT SURWHERESUR.STT_VALEUR>STT.STT_VALEUR))<=1;
 | 
II-I-2. Solution question 2 : les données à calculées sont toutes différentes (il n'y a pas de doublon) et en nombre paires : 
            
            
               Seulement cette solution ne permet de trouver la bonne valeur que dans le cas ou les occurrences sont en nombre impaires. Dans le cas ou les lignes sont en nombre pair, par exemple en rajoutant la ligne (INSERT INTO T_STATISTIQUES1_ST1 VALUES (8, 23.5)), la médiane doit maintenant être calculée comme suit : 
            
            | STT_ID      STT_VALEUR
----------- ----------------------------------------------------- 
1           22.0
3           22.5
5           23.0
                  <-- la médiane est ici : moyenne de 23.0 et 23.5, soit 23.25
6           23.5
4           24.0
2           27.5
 | 
               la requête précédente fournit le résultat suivant :
            
            | STT_ID      STT_VALEUR
----------- ----------------------------------------------------- 
5           23.0
6           23.5
 | 
               
               Il convient donc de réaliser la moyenne de ce résultat pour exprimer la médiane. Cela s'effectue à l'aide de l'expression :
            
            | SELECTAVG(STT_VALEUR)ASMEDIANEFROM(SELECTSTT_VALEURFROMT_STATISTIQUES_STT STTWHEREABS((SELECTCOUNT(*)FROMT_STATISTIQUES_STT SOUWHERESOU.STT_VALEUR<STT.STT_VALEUR)-(SELECTCOUNT(*)FROMT_STATISTIQUES_STT SURWHERESUR.STT_VALEUR>STT.STT_VALEUR))<=1) T
 | 
| MEDIANE
--------------
23.75
 | 
II-I-3. Solution question 3 : les données à calculer admettent des doublons et un nombre de lignes impairs
            
            On a ajouté des lignes à notre jeu d'essai :
            | INSERTINTOT_STATISTIQUES_STTVALUES(7,22.0);INSERTINTOT_STATISTIQUES_STTVALUES(8,22.0);INSERTINTOT_STATISTIQUES_STTVALUES(9,22.0);INSERTINTOT_STATISTIQUES_STTVALUES(10,22.0);INSERTINTOT_STATISTIQUES_STTVALUES(11,22.0);
 | 
               Dès lors, la médiane à calculer se trouve ici :
            
            | STT_ID      STT_VALEUR
----------- ----------------------------------------------------- 
1           22.0
10          22.0
11          22.0
8           22.0
7           22.0
9           22.0 <-- la médiane est ici : 22.0
3           22.5
5           23.0
6           23.5
4           24.0
2           27.5
 | 
               
               L'application de notre requête précédemment mise au point nous donne :
            
            | MEDIANE
--------------
NULL
 | 
               Ce qui n'est pas tout à fait la bonne réponse.... ! Que s'est-il passé ?
            
            
               Constatons tout d'abord que l'ensemble inférieur (SOU) est formé d'une seule valeur. Dès lors les opérations ensemblistes ne sont pas capable de faire la distinction entre les différentes valeurs. La limite entre les deux 
               ensembles se positionne donc en dehors (ici dessous) de l'équilibre des lignes, et rien n'est retourné par la requête principale.
            
            
               Une solution pour contourner le problème des lignes paires et impaires est de dédoubler le nombre des lignes. Cela ne change rien à la médiane de multiplier par deux les occurrences si ces nouvelles occurrences sont des lignes dupliquées, mais cela permet d'avoir l'assurance que l'on travaille sur un nombre le lignes paires. Faisons cela à l'aide d'une vue :
            
            | CREATEVIEWV_STATISTIQUES_STTASSELECT*FROMT_STATISTIQUES_STTUNIONALLSELECT*FROMT_STATISTIQUES_STT
 | 
               Dès lors, on peut exprimer notre précédente requête par :
            
            | SELECTSTT_VALEURFROMV_STATISTIQUES_STT STTWHERE(SELECTCOUNT(*)FROMT_STATISTIQUES_STT)<=(SELECTCOUNT(*)FROMV_STATISTIQUES_STTASSOUWHERESOU.STT_VALEUR>=STT.STT_VALEUR)AND(SELECTCOUNT(*)FROMT_STATISTIQUES_STT)<=(SELECTCOUNT(*)FROMV_STATISTIQUES_STTASSOUWHERESOU.STT_VALEUR<=STT.STT_VALEUR)
 | 
               
               Remarquez comment nous avons changé notre requête pour nous adapté au nouveau cas en exigeant que le nombre de lignes de la vue en dessous (ou au dessus) de la valeur soit égal au nombre de ligne original de la table... Bien entendu cette requête renvoie systématiquement un multiple de deux lignes puisqu'elle opère sur un dédoublement des valeurs de la table. Il faut alors faire une moyenne des valeurs retournées :
            
            | SELECTAVG(STT_VALEUR)ASMEDIANEFROM(SELECTSTT_VALEURFROMV_STATISTIQUES_STT STTWHERE(SELECTCOUNT(*)FROMT_STATISTIQUES_STT)<=(SELECTCOUNT(*)FROMV_STATISTIQUES_STTASSOUWHERESOU.STT_VALEUR<=STT.STT_VALEUR)AND(SELECTCOUNT(*)FROMT_STATISTIQUES_STT)<=(SELECTCOUNT(*)FROMV_STATISTIQUES_STTASSURWHERESUR.STT_VALEUR>=STT.STT_VALEUR) ) T
 | 
               Mais cette requête n'est pas encore parfaite... En effet, si nous ajoutons la ligne suivante :
            
            | INSERTINTOT_STATISTIQUES_STTVALUES(12,22.5)
 | 
               La médiane devrait être :
            
            | STT_ID      STT_VALEUR
----------- ----------------------------------------------------- 
1           22.0
7           22.0
8           22.0
9           22.0
10          22.0
11          22.0
                  <-- la médiane est ici : moyenne de 22.0 et 22.5, soit 22.25
3           22.5
12          22.5
5           23.0
6           23.5
4           24.0
2           27.5
 | 
               Or la requête précédente nous donne pour valeur :
            
            | MEDIANE
-----------
22.125
 | 
               Simplement parce que le dédoublement des valeurs peut conduire à des occurrences quadruples ou plus encore des valeurs. Mais par définition la médiane dans ce cas étant la moyenne des deux valeurs, il suffit d'ajouter un DISTINCT dans le calcul de la fonction SQL AVG...
            
            
               Finalement notre requête pour exprimer la médiane est en définitive :
            
            | SELECTAVG(DISTINCTSTT_VALEUR)ASMEDIANEFROM(SELECTSTT_VALEURFROMV_STATISTIQUES_STT STTWHERE(SELECTCOUNT(*)FROMT_STATISTIQUES_STT)<=(SELECTCOUNT(*)FROMV_STATISTIQUES_STTASSOUWHERESOU.STT_VALEUR<=STT.STT_VALEUR)AND(SELECTCOUNT(*)FROMT_STATISTIQUES_STT)<=(SELECTCOUNT(*)FROMV_STATISTIQUES_STTASSURWHERESUR.STT_VALEUR>=STT.STT_VALEUR) ) T ;
 | 
               Et si nous ne voulons pas de vue, cette requête peut finalement s'écrire : 
            
            | SELECTAVG(DISTINCTSTT_VALEUR)ASMEDIANEFROM(SELECTSTT_VALEURFROM(SELECT*FROMT_STATISTIQUES_STTUNIONALLSELECT*FROMT_STATISTIQUES_STT) STTWHERE(SELECTCOUNT(*)FROMT_STATISTIQUES_STT)<=(SELECTCOUNT(*)FROM(SELECT*FROMT_STATISTIQUES_STTUNIONALLSELECT*FROMT_STATISTIQUES_STT)ASSOUWHERESOU.STT_VALEUR<=STT.STT_VALEUR)AND(SELECTCOUNT(*)FROMT_STATISTIQUES_STT)<=(SELECTCOUNT(*)FROM(SELECT*FROMT_STATISTIQUES_STTUNIONALLSELECT*FROMT_STATISTIQUES_STT)ASSURWHERESUR.STT_VALEUR>=STT.STT_VALEUR) ) T ;
 | 
Chris Date et Joe Celko se sont bien battus par papiers interposés pour trouver les failles de ces différentes problématiques de l'expression de la médiane. Ils en ont tirés différentes expressions, dont celle que je viens de vous donner est la première version corrigés de la médiane de Date.
            Joe Celko propose de calculer le nombre de lignes à retourner par les deux sous ensembles à l'aide de la demi valuation du nombre total des lignes. La, requête est intéressante à décomposer :
            | 1. Les valeurs au dessus sont exprimées par : | 
| SELECTST1.STT_ID, ST1.STT_VALEURFROMT_STATISTIQUES_STTASST1INNERJOINT_STATISTIQUES_STTASST2ONST1.STT_VALEUR<=ST2.STT_VALEURGROUPBYST1.STT_ID, ST1.STT_VALEURHAVINGCOUNT(*)<=(SELECTCOUNT(*)/2+0.5FROMT_STATISTIQUES_STT);
 | 
| STT_ID      STT_VALEUR
----------- ----------------------------------------------------- 
3           22.5
12          22.5
5           23.0
6           23.5
4           24.0
2           27.5
 | 
| 2. Les valeurs en dessous sont exprimées par : | 
| SELECTST1.STT_ID, ST1.STT_VALEURFROMT_STATISTIQUES_STTASST1INNERJOINT_STATISTIQUES_STTASST2ONST1.STT_VALEUR>=ST2.STT_VALEURGROUPBYST1.STT_ID, ST1.STT_VALEURHAVINGCOUNT(*)<=(SELECTCOUNT(*)/2+0.5FROMT_STATISTIQUES_STT)
 | 
| STT_ID      STT_VALEUR
----------- ----------------------------------------------------- 
1           22.0
7           22.0
8           22.0
9           22.0
10          22.0
11          22.0
 | 
               Il ne suffit plus que de prendre le minimum des valeurs au dessus et le maximum des valeurs en dessous pour obtenir notre solution en une ou deux lignes :
            
            | SELECTMIN(STT_VALEUR)ASSTT_VALEURFROM(SELECTST1.STT_ID, ST1.STT_VALEURFROMT_STATISTIQUES_STTASST1INNERJOINT_STATISTIQUES_STTASST2ONST1.STT_VALEUR<=ST2.STT_VALEURGROUPBYST1.STT_ID, ST1.STT_VALEURHAVINGCOUNT(*)<=(SELECTCOUNT(*)/2+0.5FROMT_STATISTIQUES_STT) ) SURUNIONALLSELECTMAX(STT_VALEUR)ASSTT_VALEURFROM(SELECTST1.STT_ID, ST1.STT_VALEURFROMT_STATISTIQUES_STTASST1INNERJOINT_STATISTIQUES_STTASST2ONST1.STT_VALEUR>=ST2.STT_VALEURGROUPBYST1.STT_ID, ST1.STT_VALEURHAVINGCOUNT(*)<=(SELECTCOUNT(*)/2+0.5FROMT_STATISTIQUES_STT) ) SOU ;
 | 
               Qui donne :
               
            
            | STT_VALEUR
-----------------------------------------------------
22.5
22.0
 | 
               Nous savons désormais quoi faire, et la requête globale s'exprime de la sorte :
            
            | SELECTAVG(STT_VALEUR)ASMEDIANEFROM(SELECTMIN(STT_VALEUR)ASSTT_VALEURFROM(SELECTST1.STT_ID, ST1.STT_VALEURFROMT_STATISTIQUES_STTASST1INNERJOINT_STATISTIQUES_STTASST2ONST1.STT_VALEUR<=ST2.STT_VALEURGROUPBYST1.STT_ID, ST1.STT_VALEURHAVINGCOUNT(*)<=(SELECTCOUNT(*)/2+0.5FROMT_STATISTIQUES_STT) ) SURUNIONSELECTMAX(STT_VALEUR)ASSTT_VALEURFROM(SELECTST1.STT_ID, ST1.STT_VALEURFROMT_STATISTIQUES_STTASST1INNERJOINT_STATISTIQUES_STTASST2ONST1.STT_VALEUR>=ST2.STT_VALEURGROUPBYST1.STT_ID, ST1.STT_VALEURHAVINGCOUNT(*)<=(SELECTCOUNT(*)/2+0.5FROMT_STATISTIQUES_STT) ) SOU ) T ;
 | 
               Une façon plus concise d'exprimer la médiane est celle-ci :
            
            | SELECTAVG(STT_VALEUR)FROM(SELECTE.STT_VALEURFROMT_STATISTIQUES_STT E
             CROSSJOINT_STATISTIQUES_STT DGROUPBYE.STT_VALEURHAVINGSUM(CASEWHENE.STT_VALEUR=D.STT_VALEURTHEN1ELSE0END)>=ABS(SUM(SIGN(E.STT_VALEUR-D.STT_VALEUR)))
     )AST
 | 
               D'autres formulations sont possibles, notamment en faisant usage d'une fonction de fenêtrage (norme SQL:2003) :
            
            | SELECTAVG(STT_VALEUR)ASMEDIANEFROM(SELECT*FROM(SELECT*, ROW_NUMBER() OVER(ORDERBYSTT_VALEUR)ASRANG,COUNT(*) OVER()ASNOMBREFROMT_STATISTIQUES_STT)ASTWHERE(    NOMBRE%2=0ANDRANGIN(NOMBRE/2, (NOMBRE/2)+1))OR(    NOMBRE%2=1ANDRANG=NOMBRE/2) )AST ;
 | 
               Enfin, si votre SGBDR supporte une fonction limitant le nombre de lignes retournées par une requête telle que TOP (MS SQL Server) ou LIMIT (MySQL), alors vous pouvez construire votre requête ainsi :
            
            | SELECTAVG(VAL)FROM(SELECTMAX(STT_VALEUR)ASVALFROM(SELECTTOP(SELECTCASEWHENN%1=0THEN(N/2)+1ELSEN/2ENDASTOPNFROM(SELECTCOUNT(*)ASNFROMdbo.T_STATISTIQUES_STT)AST) 
                                                                 STT_VALEURFROMdbo.T_STATISTIQUES_STTORDERBYSTT_VALEUR)AST1UNIONSELECTMIN(STT_VALEUR)ASVALFROM(SELECTTOP(SELECTCASEWHENN%1=0THEN(N/2)+1ELSEN/2ENDASTOPNFROM(SELECTCOUNT(*)ASNFROMdbo.T_STATISTIQUES_STT)AST) 
                                                                 STT_VALEURFROMdbo.T_STATISTIQUES_STTORDERBYSTT_VALEURDESC)AST2
        )AST ;
 | 
               Un bon exercice complémentaire serait de voir quel est le coût de chacune de ces formulations et d'en déduire la meilleure. Cela risque de vous réserver que id=""lques surprises... !
            
         
      
      II-J. Solution au problème n° 20 - insertion en bloc
         
         
            Une première manière de faire est d'utiliser le produit cartésien des deux tables restreinte à l'univers visé :
         
         | INSERTINTOT_OBJET_COULEUR_OBC (OBJ_OBJET, CLR_COULEUR)SELECTOBJ_OBJET, CLR_COULEURFROMT_OBJET_OBJ
       CROSSJOINT_COULEUR_CLRWHEREOBJ_OBJET='feu tricolore'ANDCLR_COULEURIN('rouge','vert','orange')OROBJ_OBJET='drapeau français'ANDCLR_COULEURIN('bleu','blanc','rouge') ;
 | 
            Une façon plus basique est de réaliser une union des lignes à insérer :
         
         | INSERTINTOT_OBJET_COULEUR_OBC (OBJ_OBJET, CLR_COULEUR)SELECTOBJ_OBJET,'rouge'FROMT_OBJET_OBJWHEREOBJ_OBJET='feu tricolore'UNIONSELECTOBJ_OBJET,'vert'FROMT_OBJET_OBJWHEREOBJ_OBJET='feu tricolore'UNIONSELECTOBJ_OBJET,'orange'FROMT_OBJET_OBJWHEREOBJ_OBJET='feu tricolore'UNIONSELECTOBJ_OBJET,'bleu'FROMT_OBJET_OBJWHEREOBJ_OBJET='drapeau français'UNIONSELECTOBJ_OBJET,'blanc'FROMT_OBJET_OBJWHEREOBJ_OBJET='drapeau français'UNIONSELECTOBJ_OBJET,'rouge'FROMT_OBJET_OBJWHEREOBJ_OBJET='drapeau français';
 | 
            C'est très verbeux, mais certains SGBDR acceptent de s'affranchir de la clause FROM. Dans ce cas c'est plus rapide.
            
            
            Exemple pour MS SQL Server :
         
         | INSERTINTOT_OBJET_COULEUR_OBC (OBJ_OBJET, CLR_COULEUR)SELECT'feu tricolore','rouge'UNIONSELECT'feu tricolore','vert'UNIONSELECT'feu tricolore','orange'UNIONSELECT'drapeau français','bleu'UNIONSELECT'drapeau français','blanc'UNIONSELECT'drapeau français','rouge';
 | 
            Oracle, quant à lui, exige la clause FROM, mais autorise de préciser une table virtuelle de nom DUAL :
         
         | INSERTINTOT_OBJET_COULEUR_OBC (OBJ_OBJET, CLR_COULEUR)SELECT'feu tricolore','rouge'FROMDUALUNIONSELECT'feu tricolore','vert'FROMDUALUNIONSELECT'feu tricolore','orange'FROMDUALUNIONSELECT'drapeau français','bleu'FROMDUALUNIONSELECT'drapeau français','b id=""lanc'FROMDUALUNIONSELECT'drapeau français','rouge'FROMDUAL ;
 | 


 
Copyright ©  SQLPro.
Aucune reproduction, même partielle, ne peut être faite
de ce site ni de l'ensemble de son contenu : textes, documents, images, etc.
sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à
trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.