lundi 23 décembre 2013

Les statistiques étendues (2)

Il y'a un peu plus de 2 ans déjà j'avais rédigé un article décrivant le principe des statistiques étendues en 11g.
J'avais tenté d'expliquer comment les stats étendues pouvaient aider le CBO à estimer de meilleures cardinalités lorsqu'on avait des colonnes corrélées ou des expressions dans nos prédicats.

Ces derniers jours j'ai justement eu affaire à 2 problèmes de performances (sur des bases 11g) liés à l'absence de stats sur des colonnes corrélées et des fonctions appliquées à certaines colonnes.

Je me suis donc dit que ces 2 cas réels pouvaient constituer un second article permettant d'illustrer l'article que j'avais écrit.


Cas 1: Statistiques étendues sur une expression

Le premier problème concernait la requête suivante:
SELECT
   to_char(SYSDATE, 'YYYYMMDD') AS DATE_EXTRACTION,
   RBQ.RBQ_NUM_CONTR AS CPP_NUM_CONTR,
   RBQ.RBQ_NUM_CONTR,
   CB.CBQ_CD_ETABLISSEMENT,
   CB.CBQ_CD_GUICHET,
   CB.CBQ_CLE_COMPTE,
   RBQ.RBQ_CD_NAT_COORD_BQE,
   Substr(CB.CBQ_NUM_COMPTE,1,7) AS CBQ_NUM_COMPTE1,
   Substr(CB.CBQ_NUM_COMPTE,8,3) AS CBQ_NUM_COMPTE2,
   Substr(CB.CBQ_NUM_COMPTE,11,1) AS CBQ_NUM_COMPTE3 FROM
   TDO_D_ASSIN_COORD_BQE      CB,
   TDO_D_ASSIN_ROLE_COORD_BQE RBQ,
   TDO_D_ASSIN_CONTR_ASSU     CON,
   TDO_R_PDV_DISTRIB          PDVD
WHERE
   RBQ.RBQ_NUM_CONTR in  (select NUM_CONTRAT from    GIC_DEM_LISTE_CONTRATS  )
   AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '398'
   AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '399'
   AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '400'
   AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '412'
   --
   AND CB.CBQ_ID_PERSONNE = RBQ.RBQ_ID_PERSONNE
   AND CB.CBQ_ID_TEC_COORD_BQE = RBQ.RBQ_ID_TEC_COORD_BQE
   --
   AND CON_NUM_CONTR(+) = RBQ.RBQ_NUM_CONTR
   AND (      CON.CON_CD_PVE_SOUS IS NULL
            OR (       PDVD_CD_POINT_VENTE = CON.CON_CD_PVE_SOUS
                  AND PDVD_CD_DISTRIB = '400000'
                  )
            );
Cette requête existait depuis longtemps mais, suite à l'implémentation du partitioning sur certaines tables, elle s'était mise à tourner pendant des dizaines d'heures.
Même si la clause de partitioning n'était effectivement pas indiquée dans la clause WHERE de la requête (et ça c'est pas bien!!!) le client voulait quand même que cette requête puisse s'exécuter correctement en attendant que la correction soit apportée.
Dans la requête on peut noter les expressions suivantes:
substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '398'    AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '399'    AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '400'    AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '412'
En fait le critère de partitionnement est un champ qui correspond justement aux 3 premiers caractères du champ RBQ_NUM_CONTR manipulé dans les clauses ci-dessus.
Jetons un œil au plan d’exécution de la requête(la requête n'ayant pas pu aboutir j'ai uniquement le plan sans les stats d’exécutions):
----------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                               | Name                         | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |                              |    89 | 12460 | 31322   (8)| 00:01:50 |       |       |
|   1 |  NESTED LOOPS                           |                              |    89 | 12460 | 31322   (8)| 00:01:50 |       |       |
|   2 |   NESTED LOOPS                          |                              |    89 | 12460 | 31322   (8)| 00:01:50 |       |       |
|*  3 |    HASH JOIN                            |                              |    89 |  7565 | 31056   (8)| 00:01:49 |       |       |
|   4 |     NESTED LOOPS                        |                              |    89 |  6675 | 31033   (8)| 00:01:49 |       |       |
|   5 |      NESTED LOOPS OUTER                 |                              |   115 |  7015 | 26689   (8)| 00:01:34 |       |       |
|   6 |       PARTITION LIST ALL                |                              |   115 |  5060 | 26459   (8)| 00:01:33 |     1 |   227 |
|*  7 |        TABLE ACCESS FULL                | TDO_D_ASSIN_ROLE_COORD_BQE   |   115 |  5060 | 26459   (8)| 00:01:33 |     1 |   227 |
|   8 |       TABLE ACCESS BY GLOBAL INDEX ROWID| TDO_D_ASSIN_CONTR_ASSU       |     1 |    17 |     2   (0)| 00:00:01 | ROWID | ROWID |
|*  9 |        INDEX UNIQUE SCAN                | PK_ASSIN_CON_NUM_CONTR       |     1 |       |     1   (0)| 00:00:01 |       |       |
|* 10 |      INDEX FAST FULL SCAN               | UK_TDO_R_PDV_DISTRIB         |     1 |    14 |    38   (6)| 00:00:01 |       |       |
|  11 |     INDEX FAST FULL SCAN                | PK_GIC_DEM_LISTE_CONTRATS    | 20000 |   195K|    22   (5)| 00:00:01 |       |       |
|* 12 |    INDEX RANGE SCAN                     | PK_ASSIN_CBQ_TEC_COORD_PERSO |     1 |       |     2   (0)| 00:00:01 |       |       |
|  13 |   TABLE ACCESS BY INDEX ROWID           | TDO_D_ASSIN_COORD_BQE        |     1 |    55 |     3   (0)| 00:00:01 |       |       |
----------------------------------------------------------------------------------------------------------------------------------------


Predicate Information (identified by operation id):
---------------------------------------------------


   3 - access("RBQ"."RBQ_NUM_CONTR"="NUM_CONTRAT")
   7 - filter(SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'398' AND SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'399' AND
              SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'400' AND SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'412')
   9 - access("CON_NUM_CONTR"(+)="RBQ"."RBQ_NUM_CONTR")
  10 - filter("CON"."CON_CD_PVE_SOUS" IS NULL OR "PDVD_CD_POINT_VENTE"="CON"."CON_CD_PVE_SOUS" AND "PDVD_CD_DISTRIB"='400000')
  12 - access("CB"."CBQ_ID_PERSONNE"="RBQ"."RBQ_ID_PERSONNE" AND "CB"."CBQ_ID_TEC_COORD_BQE"="RBQ"."RBQ_ID_TEC_COORD_BQE")
On voit que le CBO a choisi d'attaquer en premier lieu la table TDO_D_ASSIN_ROLE_COORD_BQE en estimant que celle-ci retournerait (après avoir appliqué les critères avec la fonction SUBSTR) seulement 115 lignes .
Si on effectue un comptage sur cette table on se rend compte que le nombre de lignes retournées est en réalité de 18 millions:
select count(*) from TDO_D_ASSIN_ROLE_COORD_BQE RBQ
where substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '398'
AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '399'
AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '400'
AND substr(RBQ.RBQ_NUM_CONTR, 1, 3) <> '412';
   
  COUNT(*)
----------
  18449114
  
-------------------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name                   | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                        |      1 |        |      1 |00:00:48.70 |   31949 |  31825 |
|   1 |  SORT AGGREGATE       |                        |      1 |      1 |      1 |00:00:48.70 |   31949 |  31825 |
|*  2 |   INDEX FAST FULL SCAN| CK_ASSIN_RBQ_NUM_CONTR |      1 |    115 |     18M|00:00:44.48 |   31949 |  31825 |
-------------------------------------------------------------------------------------------------------------------


Predicate Information (identified by operation id):
---------------------------------------------------


   2 - filter((SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'398' AND SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'399' AND
              SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'400' AND SUBSTR("RBQ"."RBQ_NUM_CONTR",1,3)<>'412'))
Puisque le CBO est incapable d'estimer une sélectivité correcte lorsqu'une fonction est appliquée à une colonne, d'où diable sort-il ces 115 lignes?
D'après le chapitre 5 du livre de Jonathan Lewis, l'optimiseur appliquerait une sélectivité de 5% pour chaque prédicat (avec expression) de type NOT EQUAL.
Vu qu'on a 4 prédicats on se retrouve au final avec une sélectivité de 0.05*0.05*0.05*0.05 = 0.00000625
La colonne NUM_ROWS de DBA_TABLES pour la table TDO_D_ASSIN_ROLE_COORD_BQE renvoyant 18 449 114 lignes, si on y applique la sélectivité précédente on obtient bien 115 lignes:
18449114*0.00000625 = 115.30

Cette mauvaise estimation conduit le CBO à choisir cette table comme table directrice du NESTED LOOP pour la joindre avec la table TDO_D_ASSIN_CONTR_ASSU.
Les estimations étant totalement biaisées c'est tout le reste du plan qui est faussé.
La solution en 11g consiste à calculer des statistiques étendues sur l'expression substr(RBQ.RBQ_NUM_CONTR, 1, 3):
BEGIN
 DBMS_STATS.gather_table_stats
 (ownname => 'ADWH00',
 tabname => 'TDO_D_ASSIN_ROLE_COORD_BQE',
 method_opt => 'FOR COLUMNS (substr(RBQ_NUM_CONTR, 1, 3)) size 1'
 );
END;
/
J'ai mis le paramètre d'instance ENABLE_DDL_LOGGING à TRUE et voici ce qu'on voit dans le fichier ALERT lorsqu'on calcule des stats étendues sur l'expression substr(RBQ_NUM_CONTR, 1, 3):
alter table "ADWH00"."TDO_D_ASSIN_ROLE_COORD_BQE" add (SYS_STUXQ$Y7DH65G7U2BC3$3Y_3NF as (substr(RBQ_NUM_CONTR, 1, 3)) virtual BY USER for statistics)

Le calcul de stats étendues sur l'expression génère en réalité la création d'une colonne virtuelle sur la table TDO_D_ASSIN_ROLE_COORD_BQE.

Une fois les stats étendues calculées (c-a-d une fois la colonne virtuelle créée), on obtient le plan suivant lorsqu'on exécute de nouveau la requête:
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                | Name                         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                         |                              |      1 |        |  18229 |00:03:09.28 |     429K|    311K|       |       |          |
|   1 |  CONCATENATION                           |                              |      1 |        |  18229 |00:03:09.28 |     429K|    311K|       |       |          |
|*  2 |   HASH JOIN                              |                              |      1 |  20717 |  18229 |00:02:03.16 |     288K|    228K|  2494K|  1736K| 2540K (0)|
|*  3 |    INDEX FAST FULL SCAN                  | UK_TDO_R_PDV_DISTRIB         |      1 |  28501 |  28497 |00:00:00.02 |     167 |      0 |       |       |          |
|   4 |    NESTED LOOPS OUTER                    |                              |      1 |  20717 |  18229 |00:02:03.07 |     288K|    228K|       |       |          |
|*  5 |     HASH JOIN                            |                              |      1 |  20717 |  18229 |00:02:02.62 |     232K|    228K|  2327K|  1129K| 3297K (0)|
|*  6 |      HASH JOIN                           |                              |      1 |  20717 |  18261 |00:01:22.93 |   86307 |  83282 |  2129K|  2004K| 2289K (0)|
|   7 |       INDEX FAST FULL SCAN               | PK_GIC_DEM_LISTE_CONTRATS    |      1 |  20000 |  20000 |00:00:00.02 |    2718 |      0 |       |       |          |
|   8 |       PARTITION LIST ALL                 |                              |      1 |     18M|     18M|00:01:09.89 |   83589 |  83282 |       |       |          |
|*  9 |        TABLE ACCESS FULL                 | TDO_D_ASSIN_ROLE_COORD_BQE   |    227 |     18M|     18M|00:01:02.25 |   83589 |  83282 |       |       |          |
|  10 |      TABLE ACCESS FULL                   | TDO_D_ASSIN_COORD_BQE        |      1 |     16M|     16M|00:00:27.26 |     146K|    145K|       |       |          |
|  11 |     TABLE ACCESS BY GLOBAL INDEX ROWID   | TDO_D_ASSIN_CONTR_ASSU       |  18229 |      1 |  18229 |00:00:00.41 |   55907 |      0 |       |       |          |
|* 12 |      INDEX UNIQUE SCAN                   | PK_ASSIN_CON_NUM_CONTR       |  18229 |      1 |  18229 |00:00:00.23 |   37677 |      0 |       |       |          |
|  13 |   NESTED LOOPS                           |                              |      1 |      1 |      0 |00:01:06.09 |     141K|  83282 |       |       |          |
|  14 |    NESTED LOOPS                          |                              |      1 |      1 |      0 |00:01:06.09 |     141K|  83282 |       |       |          |
|  15 |     NESTED LOOPS                         |                              |      1 |      1 |      0 |00:01:06.09 |     141K|  83282 |       |       |          |
|* 16 |      FILTER                              |                              |      1 |        |      0 |00:01:06.09 |     141K|  83282 |       |       |          |
|  17 |       NESTED LOOPS OUTER                 |                              |      1 |      1 |  18261 |00:01:06.08 |     141K|  83282 |       |       |          |
|* 18 |        HASH JOIN                         |                              |      1 |  20717 |  18261 |00:01:05.80 |   86306 |  83282 |  2129K|  2004K| 2283K (0)|
|  19 |         INDEX FAST FULL SCAN             | PK_GIC_DEM_LISTE_CONTRATS    |      1 |  20000 |  20000 |00:00:00.02 |    2718 |      0 |       |       |          |
|  20 |         PARTITION LIST ALL               |                              |      1 |     18M|     18M|00:00:52.92 |   83588 |  83282 |       |       |          |
|* 21 |          TABLE ACCESS FULL               | TDO_D_ASSIN_ROLE_COORD_BQE   |    227 |     18M|     18M|00:00:45.32 |   83588 |  83282 |       |       |          |
|  22 |        TABLE ACCESS BY GLOBAL INDEX ROWID| TDO_D_ASSIN_CONTR_ASSU       |  18261 |      1 |  18261 |00:00:00.26 |   54798 |      0 |       |       |          |
|* 23 |         INDEX UNIQUE SCAN                | PK_ASSIN_CON_NUM_CONTR       |  18261 |      1 |  18261 |00:00:00.13 |   36537 |      0 |       |       |          |
|* 24 |      INDEX FAST FULL SCAN                | UK_TDO_R_PDV_DISTRIB         |      0 |  36746 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
|* 25 |     INDEX RANGE SCAN                     | PK_ASSIN_CBQ_TEC_COORD_PERSO |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
|  26 |    TABLE ACCESS BY INDEX ROWID           | TDO_D_ASSIN_COORD_BQE        |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("PDVD_CD_POINT_VENTE"="CON"."CON_CD_PVE_SOUS")
   3 - filter("PDVD_CD_DISTRIB"='400000')
   5 - access("CB"."CBQ_ID_PERSONNE"="RBQ"."RBQ_ID_PERSONNE" AND "CB"."CBQ_ID_TEC_COORD_BQE"="RBQ"."RBQ_ID_TEC_COORD_BQE")
   6 - access("RBQ"."RBQ_NUM_CONTR"="NUM_CONTRAT")
   9 - filter((SUBSTR("RBQ_NUM_CONTR",1,3)<>'398' AND SUBSTR("RBQ_NUM_CONTR",1,3)<>'399' AND SUBSTR("RBQ_NUM_CONTR",1,3)<>'400' AND 
              SUBSTR("RBQ_NUM_CONTR",1,3)<>'412'))
  12 - access("CON_NUM_CONTR"="RBQ"."RBQ_NUM_CONTR")
  16 - filter("CON"."CON_CD_PVE_SOUS" IS NULL)
  18 - access("RBQ"."RBQ_NUM_CONTR"="NUM_CONTRAT")
  21 - filter((SUBSTR("RBQ_NUM_CONTR",1,3)<>'398' AND SUBSTR("RBQ_NUM_CONTR",1,3)<>'399' AND SUBSTR("RBQ_NUM_CONTR",1,3)<>'400' AND 
              SUBSTR("RBQ_NUM_CONTR",1,3)<>'412'))
  23 - access("CON_NUM_CONTR"="RBQ"."RBQ_NUM_CONTR")
  24 - filter((LNNVL("PDVD_CD_POINT_VENTE"="CON"."CON_CD_PVE_SOUS") OR LNNVL("PDVD_CD_DISTRIB"='400000')))
  25 - access("CB"."CBQ_ID_PERSONNE"="RBQ"."RBQ_ID_PERSONNE" AND "CB"."CBQ_ID_TEC_COORD_BQE"="RBQ"."RBQ_ID_TEC_COORD_BQE")

Tout d'abord on note que la requête s'exécute en 3 minutes (alors qu'elle n'aboutissait pas sans les stats étendues). Ensuite on voit que la cardinalité est bien estimée pour la table TDO_D_ASSIN_ROLE_COORD_BQE (18M pour les colonnes E-ROWS et A-ROWS).
Enfin, on constate que grâce à cette bonne estimation la table n'est plus attaquée via un NESTED LOOP mais que le CBO a opté pour un HASH JOIN tout à fait justifié.


Cas 2: Stats étendues sur des colonnes corrélées

Le second problème concernait la requête suivante qui mettait 2h34 pour s'exécuter:
SELECT DISTINCT
 FLC_DT_FIN_ECH_FLUX Date_arrete,
 ASS_NUM_CONTR_COLLECT_CNP Num_contrat,
 ASS_NUM_CONTRACTANT Id_coll,
 SUM (
 DECODE (ASS_NUM_CONTRACTANT,
 '00698', 12 * ADH_SOMME_MVT_RELATIFS_TTC,
 '77009', 12 * ADH_SOMME_MVT_RELATIFS_TTC,
 '90074', 12 * ADH_SOMME_MVT_RELATIFS_TTC,
 4 * ADH_SOMME_MVT_RELATIFS_TTC))
FROM odd_flux_cid a, odd_assure b, odd_info_adhesion d
where a.num_integration in (8118,8112,8069,8186,8148,8119,8094,8070,8187,8149,8120,8095,8071,8121,8096,8072,8188,8150,8122,8097,8073,8189,8151,8123,8098,8074,8190,8152,8125,8099,8075,8191,8153,8127,
8100,8192,8154,8129,8101,8077,8193,8155,8130,8102,8078,8194,8156,8131,8103,8079,8195,8158,8132,8104,8080,8197,8159,8133,8105,8081,8162,8134,8106,8082,8198,8163,8135,8107,8083,8200,8164,8136,8108,8084,
8137,8110,8085,8165,8138,8109,8086,8202,8166,8139,8111,8087)
 AND b.num_integration = d.num_integration
 AND ASS_ID_FLUX = ADH_ID_FLUX
 AND ASS_NUM_CCOLTE = ADH_NUM_CCOLTE
 AND ASS_NUM_REF_ASS = ADH_NUM_REF_ASS
 AND a.num_integration = b.num_integration
 AND ASS_ID_FLUX = flc_id_flux
 AND ASS_ID_DELEGATAIRE = flc_id_delegataire
GROUP BY FLC_DT_FIN_ECH_FLUX, ASS_NUM_CONTR_COLLECT_CNP, ASS_NUM_CONTRACTANT;




1469 rows selected.


Elapsed: 02:34:56.28


Plan hash value: 863059560


-------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name                 | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                      |      1 |        |   1469 |00:11:35.38 |      13M|    392K|       |       |          |
|   1 |  HASH GROUP BY                         |                      |      1 |  11546 |   1469 |00:11:35.38 |      13M|    392K|   855K|   855K| 2582K (0)|
|   2 |   NESTED LOOPS                         |                      |      1 |        |     11M|02:34:29.63 |      13M|    392K|       |       |          |
|   3 |    NESTED LOOPS                        |                      |      1 |  11546 |     11M|02:32:29.42 |    2327K|    258K|       |       |          |
|   4 |     NESTED LOOPS                       |                      |      1 |   7504 |   7949K|00:01:24.63 |    1442K|    184K|       |       |          |
|   5 |      PARTITION RANGE INLIST            |                      |      1 |   1310 |     86 |00:00:01.02 |     173 |    172 |       |       |          |
|*  6 |       TABLE ACCESS FULL                | ODD_FLUX_CID         |     86 |   1310 |     86 |00:00:01.02 |     173 |    172 |       |       |          |
|   7 |      PARTITION RANGE AND               |                      |     86 |      6 |   7949K|00:01:20.55 |    1442K|    184K|       |       |          |
|*  8 |       TABLE ACCESS BY LOCAL INDEX ROWID| ODD_ASSURE           |     86 |      6 |   7949K|00:01:17.40 |    1442K|    184K|       |       |          |
|*  9 |        INDEX RANGE SCAN                | PK_ASS_ASSURE        |     86 |     56 |   7949K|00:00:17.86 |   34929 |  34746 |       |       |          |
|  10 |     PARTITION RANGE AND                |                      |   7949K|      1 |     11M|02:30:54.44 |     884K|  74033 |       |       |          |
|* 11 |      INDEX RANGE SCAN                  | PK_ADH_INFO_ADHESION |   7949K|      1 |     11M|00:01:04.85 |     884K|  74033 |       |       |          |
|  12 |    TABLE ACCESS BY LOCAL INDEX ROWID   | ODD_INFO_ADHESION    |     11M|      2 |     11M|00:01:46.81 |      11M|    134K|       |       |          |
-------------------------------------------------------------------------------------------------------------------------------------------------------------


Predicate Information (identified by operation id):
---------------------------------------------------


   6 - filter(("A"."NUM_INTEGRATION"=8069 OR "A"."NUM_INTEGRATION"=8070 OR "A"."NUM_INTEGRATION"=8071 OR "A"."NUM_INTEGRATION"=8072 OR
              "A"."NUM_INTEGRATION"=8073 OR "A"."NUM_INTEGRATION"=8074 OR "A"."NUM_INTEGRATION"=8075 OR "A"."NUM_INTEGRATION"=8077 OR "A"."NUM_INTEGRATION"=8078
              OR "A"."NUM_INTEGRATION"=8079 OR "A"."NUM_INTEGRATION"=8080 OR "A"."NUM_INTEGRATION"=8081 OR "A"."NUM_INTEGRATION"=8082 OR
              "A"."NUM_INTEGRATION"=8083 OR "A"."NUM_INTEGRATION"=8084 OR "A"."NUM_INTEGRATION"=8085 OR "A"."NUM_INTEGRATION"=8086 OR "A"."NUM_INTEGRATION"=8087
              OR "A"."NUM_INTEGRATION"=8094 OR "A"."NUM_INTEGRATION"=8095 OR "A"."NUM_INTEGRATION"=8096 OR "A"."NUM_INTEGRATION"=8097 OR
              "A"."NUM_INTEGRATION"=8098 OR "A"."NUM_INTEGRATION"=8099 OR "A"."NUM_INTEGRATION"=8100 OR "A"."NUM_INTEGRATION"=8101 OR "A"."NUM_INTEGRATION"=8102
              OR "A"."NUM_INTEGRATION"=8103 OR "A"."NUM_INTEGRATION"=8104 OR "A"."NUM_INTEGRATION"=8105 OR "A"."NUM_INTEGRATION"=8106 OR
              "A"."NUM_INTEGRATION"=8107 OR "A"."NUM_INTEGRATION"=8108 OR "A"."NUM_INTEGRATION"=8109 OR "A"."NUM_INTEGRATION"=8110 OR "A"."NUM_INTEGRATION"=8111
              OR "A"."NUM_INTEGRATION"=8112 OR "A"."NUM_INTEGRATION"=8118 OR "A"."NUM_INTEGRATION"=8119 OR "A"."NUM_INTEGRATION"=8120 OR
              "A"."NUM_INTEGRATION"=8121 OR "A"."NUM_INTEGRATION"=8122 OR "A"."NUM_INTEGRATION"=8123 OR "A"."NUM_INTEGRATION"=8125 OR "A"."NUM_INTEGRATION"=8127
              OR "A"."NUM_INTEGRATION"=8129 OR "A"."NUM_INTEGRATION"=8130 OR "A"."NUM_INTEGRATION"=8131 OR "A"."NUM_INTEGRATION"=8132 OR
              "A"."NUM_INTEGRATION"=8133 OR "A"."NUM_INTEGRATION"=8134 OR "A"."NUM_INTEGRATION"=8135 OR "A"."NUM_INTEGRATION"=8136 OR "A"."NUM_INTEGRATION"=8137
              OR "A"."NUM_INTEGRATION"=8138 OR "A"."NUM_INTEGRATION"=8139 OR "A"."NUM_INTEGRATION"=8148 OR "A"."NUM_INTEGRATION"=8149 OR
              "A"."NUM_INTEGRATION"=8150 OR "A"."NUM_INTEGRATION"=8151 OR "A"."NUM_INTEGRATION"=8152 OR "A"."NUM_INTEGRATION"=8153 OR "A"."NUM_INTEGRATION"=8154
              OR "A"."NUM_INTEGRATION"=8155 OR "A"."NUM_INTEGRATION"=8156 OR "A"."NUM_INTEGRATION"=8158 OR "A"."NUM_INTEGRATION"=8159 OR
              "A"."NUM_INTEGRATION"=8162 OR "A"."NUM_INTEGRATION"=8163 OR "A"."NUM_INTEGRATION"=8164 OR "A"."NUM_INTEGRATION"=8165 OR "A"."NUM_INTEGRATION"=8166
              OR "A"."NUM_INTEGRATION"=8186 OR "A"."NUM_INTEGRATION"=8187 OR "A"."NUM_INTEGRATION"=8188 OR "A"."NUM_INTEGRATION"=8189 OR
              "A"."NUM_INTEGRATION"=8190 OR "A"."NUM_INTEGRATION"=8191 OR "A"."NUM_INTEGRATION"=8192 OR "A"."NUM_INTEGRATION"=8193 OR "A"."NUM_INTEGRATION"=8194
              OR "A"."NUM_INTEGRATION"=8195 OR "A"."NUM_INTEGRATION"=8197 OR "A"."NUM_INTEGRATION"=8198 OR "A"."NUM_INTEGRATION"=8200 OR
              "A"."NUM_INTEGRATION"=8202))
   8 - filter("ASS_ID_DELEGATAIRE"="FLC_ID_DELEGATAIRE")
   9 - access("A"."NUM_INTEGRATION"="B"."NUM_INTEGRATION" AND "ASS_ID_FLUX"="FLC_ID_FLUX")
       filter(("B"."NUM_INTEGRATION"=8069 OR "B"."NUM_INTEGRATION"=8070 OR "B"."NUM_INTEGRATION"=8071 OR "B"."NUM_INTEGRATION"=8072 OR
              "B"."NUM_INTEGRATION"=8073 OR "B"."NUM_INTEGRATION"=8074 OR "B"."NUM_INTEGRATION"=8075 OR "B"."NUM_INTEGRATION"=8077 OR "B"."NUM_INTEGRATION"=8078
              OR "B"."NUM_INTEGRATION"=8079 OR "B"."NUM_INTEGRATION"=8080 OR "B"."NUM_INTEGRATION"=8081 OR "B"."NUM_INTEGRATION"=8082 OR
              "B"."NUM_INTEGRATION"=8083 OR "B"."NUM_INTEGRATION"=8084 OR "B"."NUM_INTEGRATION"=8085 OR "B"."NUM_INTEGRATION"=8086 OR "B"."NUM_INTEGRATION"=8087
              OR "B"."NUM_INTEGRATION"=8094 OR "B"."NUM_INTEGRATION"=8095 OR "B"."NUM_INTEGRATION"=8096 OR "B"."NUM_INTEGRATION"=8097 OR
              "B"."NUM_INTEGRATION"=8098 OR "B"."NUM_INTEGRATION"=8099 OR "B"."NUM_INTEGRATION"=8100 OR "B"."NUM_INTEGRATION"=8101 OR "B"."NUM_INTEGRATION"=8102
              OR "B"."NUM_INTEGRATION"=8103 OR "B"."NUM_INTEGRATION"=8104 OR "B"."NUM_INTEGRATION"=8105 OR "B"."NUM_INTEGRATION"=8106 OR
              "B"."NUM_INTEGRATION"=8107 OR "B"."NUM_INTEGRATION"=8108 OR "B"."NUM_INTEGRATION"=8109 OR "B"."NUM_INTEGRATION"=8110 OR "B"."NUM_INTEGRATION"=8111
              OR "B"."NUM_INTEGRATION"=8112 OR "B"."NUM_INTEGRATION"=8118 OR "B"."NUM_INTEGRATION"=8119 OR "B"."NUM_INTEGRATION"=8120 OR
              "B"."NUM_INTEGRATION"=8121 OR "B"."NUM_INTEGRATION"=8122 OR "B"."NUM_INTEGRATION"=8123 OR "B"."NUM_INTEGRATION"=8125 OR "B"."NUM_INTEGRATION"=8127
              OR "B"."NUM_INTEGRATION"=8129 OR "B"."NUM_INTEGRATION"=8130 OR "B"."NUM_INTEGRATION"=8131 OR "B"."NUM_INTEGRATION"=8132 OR
              "B"."NUM_INTEGRATION"=8133 OR "B"."NUM_INTEGRATION"=8134 OR "B"."NUM_INTEGRATION"=8135 OR "B"."NUM_INTEGRATION"=8136 OR "B"."NUM_INTEGRATION"=8137
              OR "B"."NUM_INTEGRATION"=8138 OR "B"."NUM_INTEGRATION"=8139 OR "B"."NUM_INTEGRATION"=8148 OR "B"."NUM_INTEGRATION"=8149 OR
              "B"."NUM_INTEGRATION"=8150 OR "B"."NUM_INTEGRATION"=8151 OR "B"."NUM_INTEGRATION"=8152 OR "B"."NUM_INTEGRATION"=8153 OR "B"."NUM_INTEGRATION"=8154
              OR "B"."NUM_INTEGRATION"=8155 OR "B"."NUM_INTEGRATION"=8156 OR "B"."NUM_INTEGRATION"=8158 OR "B"."NUM_INTEGRATION"=8159 OR
              "B"."NUM_INTEGRATION"=8162 OR "B"."NUM_INTEGRATION"=8163 OR "B"."NUM_INTEGRATION"=8164 OR "B"."NUM_INTEGRATION"=8165 OR "B"."NUM_INTEGRATION"=8166
              OR "B"."NUM_INTEGRATION"=8186 OR "B"."NUM_INTEGRATION"=8187 OR "B"."NUM_INTEGRATION"=8188 OR "B"."NUM_INTEGRATION"=8189 OR
              "B"."NUM_INTEGRATION"=8190 OR "B"."NUM_INTEGRATION"=8191 OR "B"."NUM_INTEGRATION"=8192 OR "B"."NUM_INTEGRATION"=8193 OR "B"."NUM_INTEGRATION"=8194
              OR "B"."NUM_INTEGRATION"=8195 OR "B"."NUM_INTEGRATION"=8197 OR "B"."NUM_INTEGRATION"=8198 OR "B"."NUM_INTEGRATION"=8200 OR
              "B"."NUM_INTEGRATION"=8202))
  11 - access("B"."NUM_INTEGRATION"="D"."NUM_INTEGRATION" AND "ASS_ID_FLUX"="ADH_ID_FLUX" AND "ASS_NUM_CCOLTE"="ADH_NUM_CCOLTE" AND
              "ASS_NUM_REF_ASS"="ADH_NUM_REF_ASS")
       filter(("D"."NUM_INTEGRATION"=8069 OR "D"."NUM_INTEGRATION"=8070 OR "D"."NUM_INTEGRATION"=8071 OR "D"."NUM_INTEGRATION"=8072 OR
              "D"."NUM_INTEGRATION"=8073 OR "D"."NUM_INTEGRATION"=8074 OR "D"."NUM_INTEGRATION"=8075 OR "D"."NUM_INTEGRATION"=8077 OR "D"."NUM_INTEGRATION"=8078
              OR "D"."NUM_INTEGRATION"=8079 OR "D"."NUM_INTEGRATION"=8080 OR "D"."NUM_INTEGRATION"=8081 OR "D"."NUM_INTEGRATION"=8082 OR
              "D"."NUM_INTEGRATION"=8083 OR "D"."NUM_INTEGRATION"=8084 OR "D"."NUM_INTEGRATION"=8085 OR "D"."NUM_INTEGRATION"=8086 OR "D"."NUM_INTEGRATION"=8087
              OR "D"."NUM_INTEGRATION"=8094 OR "D"."NUM_INTEGRATION"=8095 OR "D"."NUM_INTEGRATION"=8096 OR "D"."NUM_INTEGRATION"=8097 OR
              "D"."NUM_INTEGRATION"=8098 OR "D"."NUM_INTEGRATION"=8099 OR "D"."NUM_INTEGRATION"=8100 OR "D"."NUM_INTEGRATION"=8101 OR "D"."NUM_INTEGRATION"=8102
              OR "D"."NUM_INTEGRATION"=8103 OR "D"."NUM_INTEGRATION"=8104 OR "D"."NUM_INTEGRATION"=8105 OR "D"."NUM_INTEGRATION"=8106 OR
              "D"."NUM_INTEGRATION"=8107 OR "D"."NUM_INTEGRATION"=8108 OR "D"."NUM_INTEGRATION"=8109 OR "D"."NUM_INTEGRATION"=8110 OR "D"."NUM_INTEGRATION"=8111
              OR "D"."NUM_INTEGRATION"=8112 OR "D"."NUM_INTEGRATION"=8118 OR "D"."NUM_INTEGRATION"=8119 OR "D"."NUM_INTEGRATION"=8120 OR
              "D"."NUM_INTEGRATION"=8121 OR "D"."NUM_INTEGRATION"=8122 OR "D"."NUM_INTEGRATION"=8123 OR "D"."NUM_INTEGRATION"=8125 OR "D"."NUM_INTEGRATION"=8127
              OR "D"."NUM_INTEGRATION"=8129 OR "D"."NUM_INTEGRATION"=8130 OR "D"."NUM_INTEGRATION"=8131 OR "D"."NUM_INTEGRATION"=8132 OR
              "D"."NUM_INTEGRATION"=8133 OR "D"."NUM_INTEGRATION"=8134 OR "D"."NUM_INTEGRATION"=8135 OR "D"."NUM_INTEGRATION"=8136 OR "D"."NUM_INTEGRATION"=8137
              OR "D"."NUM_INTEGRATION"=8138 OR "D"."NUM_INTEGRATION"=8139 OR "D"."NUM_INTEGRATION"=8148 OR "D"."NUM_INTEGRATION"=8149 OR
              "D"."NUM_INTEGRATION"=8150 OR "D"."NUM_INTEGRATION"=8151 OR "D"."NUM_INTEGRATION"=8152 OR "D"."NUM_INTEGRATION"=8153 OR "D"."NUM_INTEGRATION"=8154
              OR "D"."NUM_INTEGRATION"=8155 OR "D"."NUM_INTEGRATION"=8156 OR "D"."NUM_INTEGRATION"=8158 OR "D"."NUM_INTEGRATION"=8159 OR
              "D"."NUM_INTEGRATION"=8162 OR "D"."NUM_INTEGRATION"=8163 OR "D"."NUM_INTEGRATION"=8164 OR "D"."NUM_INTEGRATION"=8165 OR "D"."NUM_INTEGRATION"=8166
              OR "D"."NUM_INTEGRATION"=8186 OR "D"."NUM_INTEGRATION"=8187 OR "D"."NUM_INTEGRATION"=8188 OR "D"."NUM_INTEGRATION"=8189 OR
              "D"."NUM_INTEGRATION"=8190 OR "D"."NUM_INTEGRATION"=8191 OR "D"."NUM_INTEGRATION"=8192 OR "D"."NUM_INTEGRATION"=8193 OR "D"."NUM_INTEGRATION"=8194
              OR "D"."NUM_INTEGRATION"=8195 OR "D"."NUM_INTEGRATION"=8197 OR "D"."NUM_INTEGRATION"=8198 OR "D"."NUM_INTEGRATION"=8200 OR
              "D"."NUM_INTEGRATION"=8202))
Si on regarde le plan on voit que sur les 2h34 d'exécution on a 2h30 passées sur l'accès à la table ODD_INFO_ADHESION via l'opération "PARTITION RANGE AND".
Cette opération est exécutée 7949000 fois (cf. colonne STARTS) car la jointure précédente entre les tables ODD_FLUX_CID et ODD_ASSURE retourne 7949000 lignes (cf. opération 4 du plan).
Si on regarde la colonne E-ROWS on voit que l'optimiseur estime que la jointure ne retournerait que 7504 lignes. C'est cette mauvaise estimation qui induit derrière le NESTED LOOP hyper couteux sur la table ODD_INFO_ADHESION.
La jointure avec la table ODD_ASSURE s'effectue sur les colonnes NUM_INTEGRATION, ASS_ID_FLUX et ASS_ID_DELEGATAIRE.
Effectuons un comptage sur la table ODD_ASSURE juste avec la clause NUM_INTEGRATION:
select count(*) from ODD_ASSURE b
where b.num_integration in (8118,8112,8069,8186,8148,8119,8094,8070,8187,8149,8120,8095,8071,8121,8096,8072,8188,8150,8122,8097,8073,8189,8151,8123,8098,8074,8190,8152,8125,8099,8075,8191,8153,8127,
   8100,8192,8154,8129,8101,8077,8193,8155,8130,8102,8078,8194,8156,8131,8103,8079,8195,8158,8132,8104,8080,8197,8159,8133,8105,8081,8162,8134,8106,8082,8198,8163,8135,8107,8083,8200,8164,8136,8108,8084,
   8137,8110,8085,8165,8138,8109,8086,8202,8166,8139,8111,8087);
   
---------------------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |               |      1 |        |      1 |00:00:12.96 |   34926 |  34925 |
|   1 |  SORT AGGREGATE            |               |      1 |      1 |      1 |00:00:12.96 |   34926 |  34925 |
|   2 |   INLIST ITERATOR          |               |      1 |        |   7949K|00:00:11.72 |   34926 |  34925 |
|   3 |    PARTITION RANGE ITERATOR|               |     86 |   7967K|   7949K|00:00:09.01 |   34926 |  34925 |
|*  4 |     INDEX RANGE SCAN       | PK_ASS_ASSURE |     86 |   7967K|   7949K|00:00:06.37 |   34926 |  34925 |
---------------------------------------------------------------------------------------------------------------
On voit que le nombre de lignes retournées est toujours de 7949K alors que cette fois on a utilisé uniqument la colonne NUM_INTEGRATION.
Il semble que les 2 autres colonnes n'influent pas sur la cardinalité. Cela revient donc à dire qu'il existe une corrélation entre les 3 colonnes NUM_INTEGRATION, ASS_ID_FLUX et ASS_ID_DELEGATAIRE.
Il serait donc intéressant de calculer des stats étendues pour la table ODD_ASSURE sur ces 3 colonnes ainsi que pour la table ODD_FLUX_CID:
BEGIN
 DBMS_STATS.gather_table_stats('AODD02','ODD_ASSURE',
 method_opt => 'FOR COLUMNS (NUM_INTEGRATION,ASS_ID_FLUX,ASS_ID_DELEGATAIRE) size 1',
 NO_INVALIDATE => FALSE,
 FORCE => TRUE
 );
END;
/


BEGIN
 DBMS_STATS.gather_table_stats('AODD02','ODD_FLUX_CID',
 method_opt => 'FOR COLUMNS (NUM_INTEGRATION,FLC_ID_FLUX,FLC_ID_DELEGATAIRE) size 1',
 NO_INVALIDATE => FALSE,
 FORCE => TRUE
 );
END;
/
Si on relance la requête on obtient désormais le plan suivant:
------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem | Used-Tmp|
------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                   |      1 |        |   1469 |00:01:35.85 |     283K|    288K|   5872 |       |       |          |         |
|   1 |  HASH GROUP BY        |                   |      1 |    355K|   1469 |00:01:35.85 |     283K|    288K|   5872 |   855K|   855K|   17M (0)|         |
|   2 |   PARTITION RANGE AND |                   |      1 |     53M|     11M|00:01:19.64 |     283K|    288K|   5872 |       |       |          |         |
|*  3 |    HASH JOIN          |                   |     86 |     53M|     11M|00:01:15.20 |     283K|    288K|   5872 |   972K|   972K|  372K (0)|         |
|*  4 |     TABLE ACCESS FULL | ODD_FLUX_CID      |     86 |   1322 |     86 |00:00:00.52 |     172 |    172 |      0 |       |       |          |         |
|*  5 |     HASH JOIN         |                   |     86 |     53M|     11M|00:01:01.45 |     282K|    288K|   5872 |  1889M|    35M|   20M (0)|   12288 |
|*  6 |      TABLE ACCESS FULL| ODD_ASSURE        |     86 |     37M|   7949K|00:00:11.45 |     146K|    146K|      0 |       |       |          |         |
|*  7 |      TABLE ACCESS FULL| ODD_INFO_ADHESION |     86 |     54M|     11M|00:00:19.97 |     136K|    136K|      0 |       |       |          |         |
------------------------------------------------------------------------------------------------------------------------------------------------------------
Les stats étendues ont permis d'obtenir une cardinalité pas tout à fait exact mais en tout cas plus proche de la réalité.
Les NESTED LOOP ont laissé place à des HASH JOIN ce qui permet de faire tourner la requête en 1 minute 35 au lieu de 2h34.

J'espère que ces 2 exemples tirés de la réalité vous permettront de comprendre comment grâce aux statistiques étendues on peut aider l'optimiseur à avoir une connaissance plus intelligente des données et ainsi lui permettre de nous trouver un plan adequat.

3 commentaires:

  1. Salam Ahmed,
    Excellent article. J’ai pris du plaisir à le lire. N’hésites pas à partager ce genre de situation. Depuis que j’ai assisté à un webinar de Jonathan Lewis sur les statistiques j’ai compris l’importance cruciale des statistiques étendues lorsqu’il s’agit d’avoir une bonne estimation et donc un plan d’exécution optimal.
    Si je reviens à ton article, quelle drôle d’idée a eu ce client de partitionner sur une partie du contenu d’une colonne en l’occurrence ici les trois premières positions. Ils doivent faire attention quand ils devront indexer ce genre de combinaison et en même temps utiliser le cursor_sharing a FORCE (voir mon commentaire ici http://jonathanlewis.wordpress.com/2013/09/27/virtual-stats/#comments)
    J’ai entamé une série d’article sur le partition pruning et ce genre de partitionnement me donne une autre idée d’investigation.
    Donc, créer des statistiques étendues reviens en fait à créer une colonne virtuelle. Je pense qu’il serait peut être plus intéressant dans votre cas de créer une colonne virtuelle (substr(RBQ_NUM_CONTR,1,3)) et partitionner par cette colonne.
    Pour le premier cas exposé, après le calcul des statistiques étendues, tu dis que l’estimation est devenue bonne car E-Rows=A-Rows=18M. Mais on voit bien que l’opération a été exécutée 227 fois (Starts=227). Il faudrait comparer Start*E-Rows avec A-Rows. Mais bon ici, les chiffres sont tellement grands que des exceptions peuvent avoir lieu.

    Quant au second cas, je suis intrigué par l’opération PARTITION RANGE AND. Je ne l’ai jamais croisée. Une petite recherche sur Google n’a rien donné. Peux-tu m’en toucher quelques mots.
    En suivant ton explication et en regardant le deuxième plan, je n’arrive pas à la même lecture du plan que tu as décrite ici.

    Néanmoins, je trouve cet article très instructif et ouvrant plusieurs pistes d’investigation.

    Bien à toi
    Mohamed Houri
    www.hourim.wordpress.com

    RépondreSupprimer
    Réponses
    1. Hello Mohamed,
      C'est toujours un plaisir de lire un commentaire de ta part.
      Mon client n'a pas partitionné sur l'expression SUBSTR mais sur un autre champ qui correspond en fait aux 3 premiers caractères du champ RBQ_NUM_CONTR. Ce champ est bien utilisé dans la plupart des requêtes mais il existe certaines requêtes (qui doivent surement dater d'avant l'existence de la colonne sur laquelle est basée le partitioning ) qui utilisent l'expression (substr(RBQ_NUM_CONTR,1,3)) au lieu de se baser sur le critère de partitioning.

      Concernant ta remarque sur l'estimation de 18M après le calcul de stats étendues sur l'expression, je me suis basé sur l'opération PARTITION LIST ALL qui a un START à 1 et un E-ROWS et A-ROWS tous les 2 à 18M. Alors c'est vrai que l'opération enfant TABLE ACCESS FULL a un START de 227 (ce qui correspond par ailleurs au nombre de partitions de la table) et que normalement A-ROWS doit être égal à E-ROWS*STARTS mais je ne suis pas sûr que ça s'applique pour les scans de partitions puisque chaque opération TABLE ACCESS FULL correspond en fait à un scan de partition et que chaque partition peut avoir un E-ROWS différent.

      Enfin concernant le PARTITION RANGE AND, je te rassure moi non plus je ne l'avais jamais vu. J'avais d'abord cherché dans le livre de Christian Antognini mais je n'ai rien trouvé. Je pense qu'il s'agit d'une opération de DYNAMIC PRUNING qui apparaît lorsqu'on joint plusieurs tables selon le critère de partitioning mais que le critère n'est précisé réellement que pour une seule table. Je vais essayer de faire un testcase pour confirmer cela mais c'est vrai que c'est bizarre que ça ne soit pas documenté.

      Supprimer
  2. Ahmed

    Tu as raison en ce qui concerne le Starts lorsqu'il s'agit d'une opération PARTITION RANGE xxxx. Voici un exemple

    SQL> select count(1) from partitioned_tab;

    COUNT(1)
    ----------
    1493

    SQL> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));

    ---------------------------------------------------------------------------
    | Id | Operation | Name | Starts | E-Rows | A-Rows |
    ---------------------------------------------------------------------------
    | 0 | SELECT STATEMENT | | 1 | | 1 |
    | 1 | SORT AGGREGATE | | 1 | 1 | 1 |
    | 2 | PARTITION RANGE ALL| | 1 | 1493 | 1493 |
    | 3 | TABLE ACCESS FULL | PARTITIONED_TAB | 1493 | 1493 | 1493 |
    ---------------------------------------------------------------------------

    Je vois aussi que dans ta deuxième requête, si je m'abuse, que tu peux la ré-ecrire (sans aucune garantie car je ne l'ai pas essayée et je ne la connais pas ) en sortant la somme du distinct, faire le distinct sur les colonnes et puis après appliquer la somme. Quelque chose comme ça

    SELECT
    x.Date_arrete
    ,x.Num_contrat
    SUM (
    DECODE (x.id_col1,
    '00698', 12 * rttc,
    '77009', 12 * rttc,
    '90074', 12 * rttc, 4 * rttc)
    )
    FROM
    (
    select
    DISTINCT
    FLC_DT_FIN_ECH_FLUX Date_arrete,
    ASS_NUM_CONTR_COLLECT_CNP Num_contrat,
    ASS_NUM_CONTRACTANT Id_coll,
    ADH_SOMME_MVT_RELATIFS_TTC rttc
    FROM odd_flux_cid a
    , odd_assure b
    , odd_info_adhesion d
    where a.num_integration in (8118,8112,8069,8186,8148,8119,8094,8070,8187,8149,8120,8095,8071,8121,8096,8072,8188,8150,8122,8097,8073,8189,8151,8123,8098,8074,8190,8152,8125,8099,8075,8191,8153,8127,
    8100,8192,8154,8129,8101,8077,8193,8155,8130,8102,8078,8194,8156,8131,8103,8079,8195,8158,8132,8104,8080,8197,8159,8133,8105,8081,8162,8134,8106,8082,8198,8163,8135,8107,8083,8200,8164,8136,8108,8084,
    8137,8110,8085,8165,8138,8109,8086,8202,8166,8139,8111,8087)
    AND b.num_integration = d.num_integration
    AND ASS_ID_FLUX = ADH_ID_FLUX
    AND ASS_NUM_CCOLTE = ADH_NUM_CCOLTE
    AND ASS_NUM_REF_ASS = ADH_NUM_REF_ASS
    AND a.num_integration = b.num_integration
    AND ASS_ID_FLUX = flc_id_flux
    AND ASS_ID_DELEGATAIRE = flc_id_delegataire
    ) x
    GROUP BY FLC_DT_FIN_ECH_FLUX
    , ASS_NUM_CONTR_COLLECT_CNP
    , ASS_NUM_CONTRACTANT;

    Bien à toi

    RépondreSupprimer