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

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

SQL Oracle Discussion :

[10g] Bug de l'optimiseur Oracle quand il ré-écrit la requête


Sujet :

SQL Oracle

  1. #1
    Membre émérite Avatar de nuke_y
    Profil pro
    Indépendant en analyse de données
    Inscrit en
    Mai 2004
    Messages
    2 076
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Indépendant en analyse de données

    Informations forums :
    Inscription : Mai 2004
    Messages : 2 076
    Points : 2 370
    Points
    2 370
    Par défaut [10g] Bug de l'optimiseur Oracle quand il ré-écrit la requête
    Bonjour à tous,

    je rencontre ce que je considère être un bug de l'optimiseur Oracle sur une 10g. Je voudrais savoir si pour vous j'ai fait une requête horrible qui plante naturellement Oracle ou si ça devrait marcher (même si on peut sûrement l'écrire mieux).

    Donc j'ai 4 tables:

    CLIENT contient le client, qui ne varie pas.

    CONTRAT contient des contrats de prestation de service. Elle est liée à CLIENT en référençant l'ID de client. Il y a de 1 à 4 contrats par client dans la base mais il n'en existe jamais plus d'1 à la fois. Donc quand 1 contrat démarre, le précédent a été fermé (mais pour cet exemple on ne va pas s'en pré-occuper).

    PRESTATION contient des prestations qui ont lieu à une date donnée. Elle est liée à CONTRAT en référençant l'ID de CONTRAT. Il y a de 1 à 4 prestations par contrat dans la base.

    TARIF contient des tarifs définis sur des périodes pour chaque contrat. Elle est liée à CONTRAT en référençant l'ID de CONTRAT et il faut préciser 1 date pour trouver le tarif qui s'applique sur 1 contrat à 1 date. Il y a de 1 à 10 tarifs (et donc intervales de tarifs) par contrat.

    Fonctionnellement il s'agit donc de clients qui passent des contrats pour des prestations, avec des tarifs qui peuvent varier dans le temps. Quand ils bénéficient d'une prestation, à une date donnée c'est le tarif lié à leur contrat à la date de la prestation qui s'applique.

    La requête suivante sort la liste des clients, contrats, prestations et tarifs
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    WITH
    "CLIENT" as
    (
     Select '1ER CLIENT' "NAME", 1 "ID" from dual
    ),
    "CONTRAT" as
    (
     Select '1ER CONTRAT' "NAME", 1 "ID", 1 "ID_CLIENT" from dual
     union
     Select '2E CONTRAT' "NAME", 2 "ID", 1 "ID_CLIENT" from dual
    ),
    "PRESTATION" as
    (
     Select '1ER PRESTATION' "NAME", 1 "ID", 1 "ID_CONTRAT", sysdate-3 "DATE" from dual
     union
      Select '2E PRESTATION' "NAME", 2 "ID", 1 "ID_CONTRAT", sysdate-2 "DATE" from dual
      union
       Select '3E PRESTATION' "NAME", 3 "ID", 1 "ID_CONTRAT", sysdate-1 "DATE" from dual
       union
       Select '4E PRESTATION' "NAME", 4 "ID", 2 "ID_CONTRAT", sysdate-3 "DATE" from dual
     union
      Select '5E PRESTATION' "NAME", 5 "ID", 2 "ID_CONTRAT", sysdate-20 "DATE" from dual
      union
       Select '6E PRESTATION' "NAME", 6 "ID", 2 "ID_CONTRAT", sysdate-1 "DATE" from dual
    ),
    "TARIF" as
    (
     Select 1 "ID_CONTRAT", sysdate-20 "DATE_START", sysdate-4 "DATE_END", 1 "TARIF" from dual
     union
     Select 1 "ID_CONTRAT", sysdate-4 "DATE_START", sysdate "DATE_END", 2 "TARIF" from dual
     union
     Select 2 "ID_CONTRAT", sysdate-20 "DATE_START", sysdate-4 "DATE_END", 3 "TARIF" from dual
     union
     Select 2 "ID_CONTRAT", sysdate-4 "DATE_START", sysdate "DATE_END", 4 "TARIF" from dual
    )
    select
    *
    from
    "CLIENT"
    inner join "CONTRAT" on ("CLIENT"."ID" = "CONTRAT"."ID_CLIENT")
    inner join "PRESTATION" on ("CONTRAT"."ID" = "PRESTATION"."ID_CONTRAT")
    left outer join "TARIF" on ("CONTRAT"."ID" = "TARIF"."ID_CONTRAT" and "PRESTATION"."DATE" between "TARIF"."DATE_START" and "TARIF"."DATE_END")
    order by 2, 4, 7
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    NAME;ID;NAME_1;ID_1;ID_CLIENT;NAME_2;ID_2;ID_CONTRAT;DATE;ID_CONTRAT_1;DATE_START;DATE_END;TARIF
    1ER CLIENT;1;1ER CONTRAT;1;1;1ER PRESTATION;1;1;28/03/2011 09:18:46;1;27/03/2011 09:18:46;31/03/2011 09:18:46;2
    1ER CLIENT;1;1ER CONTRAT;1;1;2E PRESTATION;2;1;29/03/2011 09:18:46;1;27/03/2011 09:18:46;31/03/2011 09:18:46;2
    1ER CLIENT;1;1ER CONTRAT;1;1;3E PRESTATION;3;1;30/03/2011 09:18:46;1;27/03/2011 09:18:46;31/03/2011 09:18:46;2
    1ER CLIENT;1;2E CONTRAT;2;1;4E PRESTATION;4;2;28/03/2011 09:18:46;2;27/03/2011 09:18:46;31/03/2011 09:18:46;4
    1ER CLIENT;1;2E CONTRAT;2;1;5E PRESTATION;5;2;11/03/2011 09:18:46;2;11/03/2011 09:18:46;27/03/2011 09:18:46;3
    1ER CLIENT;1;2E CONTRAT;2;1;6E PRESTATION;6;2;30/03/2011 09:18:46;2;27/03/2011 09:18:46;31/03/2011 09:18:46;4
    Ma problèmatique est de sortir pour chaque client, en 1 seule requête, les contrats et pour celui sur lequel a eu lieu la 1ere prestation du client (chronologiquement et pour tous les contrats confondus) le tarif qui s'est appliqué. Comme il s'agit en fait d'une requête générée par un générateur de requête tel que Business Object, je ne peux pas faire ce que je veux.

    Bon gré mal gré je suis arrivé à faire ça: je calcule en 1 seule sous-requête, pour chaque client, la date de 1ere prestation et le contrat qui y correspond (en utilisant min("CONTRAT"."ID") KEEP (DENSE_RANK FIRST ORDER BY "PRESTATION"."DATE" ASC) ) et je joint cette "vue" contruite à la volée à ma table des tarifs pour en sortir le tarif du contrat (TARIF_FOR_FIRST_PREST), que je lie ensuite aux contrats et aux clients.
    Encore une fois, oui on peut faire sûrement mieux, mais je travaille sur un générateur de code, les volumes sont faibles, et c'est plus maintenable parce que je peux réutiliser le bout de code à plusieurs endroits.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    WITH
    "CLIENT" as
    (
     Select '1ER CLIENT' "NAME", 1 "ID" from dual
    ),
    "CONTRAT" as
    (
     Select '1ER CONTRAT' "NAME", 1 "ID", 1 "ID_CLIENT" from dual
     union
     Select '2E CONTRAT' "NAME", 2 "ID", 1 "ID_CLIENT" from dual
    ),
    "PRESTATION" as
    (
     Select '1ER PRESTATION' "NAME", 1 "ID", 1 "ID_CONTRAT", sysdate-3 "DATE" from dual
     union
      Select '2E PRESTATION' "NAME", 2 "ID", 1 "ID_CONTRAT", sysdate-2 "DATE" from dual
      union
       Select '3E PRESTATION' "NAME", 3 "ID", 1 "ID_CONTRAT", sysdate-1 "DATE" from dual
       union
       Select '4E PRESTATION' "NAME", 4 "ID", 2 "ID_CONTRAT", sysdate-3 "DATE" from dual
     union
      Select '5E PRESTATION' "NAME", 5 "ID", 2 "ID_CONTRAT", sysdate-20 "DATE" from dual
      union
       Select '6E PRESTATION' "NAME", 6 "ID", 2 "ID_CONTRAT", sysdate-1 "DATE" from dual
    ),
    "TARIF" as
    (
     Select 1 "ID_CONTRAT", sysdate-20 "DATE_START", sysdate-4 "DATE_END", 1 "TARIF" from dual
     union
     Select 1 "ID_CONTRAT", sysdate-4 "DATE_START", sysdate "DATE_END", 2 "TARIF" from dual
     union
     Select 2 "ID_CONTRAT", sysdate-20 "DATE_START", sysdate-4 "DATE_END", 3 "TARIF" from dual
     union
     Select 2 "ID_CONTRAT", sysdate-4 "DATE_START", sysdate "DATE_END", 4 "TARIF" from dual
    )
    SELECT   "CLIENT"."NAME" "Client Name",
             "CONTRAT"."NAME" "Contrat Name",
    		 "TARIF_FOR_FIRST_PREST"."TARIF" "Tarif",
    		 "CONTRAT"."ID",
    		 "TARIF_FOR_FIRST_PREST"."ID_CONTRAT"
        FROM 
    		 "CLIENT"
    		 inner join "CONTRAT" on ("CLIENT"."ID" = "CONTRAT"."ID_CLIENT")
    		 left outer join
             (SELECT
    		 		"TARIF"."ID_CONTRAT",
                    "TARIF"."TARIF"
                FROM
    				"TARIF"
    				inner join
                     (SELECT
    				 		"CONTRAT"."ID_CLIENT",
                            min("CONTRAT"."ID") KEEP (DENSE_RANK FIRST ORDER BY "PRESTATION"."DATE" ASC) "ID_ON_FIRST_DATE",
                            min("PRESTATION"."DATE") "FIRST_DATE"
                      FROM
    					  	"CONTRAT"
    						INNER JOIN "PRESTATION" ON ("CONTRAT"."ID" = "PRESTATION"."ID_CONTRAT")   
                      GROUP BY
    				  		"CONTRAT"."ID_CLIENT"
    				 )"FIRST_PREST_DT_AND_CONTRACT_ID"
                     on ( "TARIF"."ID_CONTRAT" = "FIRST_PREST_DT_AND_CONTRACT_ID"."ID_ON_FIRST_DATE" and "FIRST_PREST_DT_AND_CONTRACT_ID"."FIRST_DATE" between "TARIF"."DATE_START" and "TARIF"."DATE_END")
    		 ) "TARIF_FOR_FIRST_PREST" on ("TARIF_FOR_FIRST_PREST"."ID_CONTRAT" = "CONTRAT"."ID")
       WHERE "CLIENT"."NAME" = ('1ER CLIENT')
    order by 1
    qui me donne ce résultat
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Client Name;Contrat Name;Tarif;ID;ID_CONTRAT
    1ER CLIENT;2E CONTRAT;3;2;2
    1ER CLIENT;1ER CONTRAT;;1;
    La 1ere prestation du client 1, qui a eu lieu à sysdate-20, était bien liée au 2e contrat et le tarif qui s'appliquait était bien 3.

    Donc là ça marche très bien.

    Mon problème c'est que si je remplace mes tables par de vraies tables, la requête me donne ce résultat:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Client Name;Contrat Name;Tarif;ID;ID_CONTRAT
    1ER CLIENT;2E CONTRAT;;2;
    1ER CLIENT;1ER CONTRAT;;1;
    Par contre si je ne restreint pas les résultats à UN client (donc j'enlève WHERE "CLIENT"."NAME" = ('1ER CLIENT'), il trouve bien mes données pour chaque client (dont client 1).
    Donc il ne trouve pas de tarif associé si je limite à 1 client. Mais si je remplace left outer join entre mes contrats et ma "vue" TARIF_FOR_FIRST_PREST par une inner join, il trouve bien mes données.
    Pire, si je garde la jointure ouverte et que j'utilise un trunc sur l'ID pour "bloquer" l'optimiseur, il trouve bien mes données:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    on (trunc("TARIF_FOR_FIRST_PREST"."ID_CONTRAT") = "CONTRAT"."ID"
    Enfin, si j'abandonne le min("CONTRAT"."ID") KEEP ... et que je fais une sous-requête classique, il trouve bien les données.

    Ma conclusion est donc que, pour optimiser, Oracle ré-écrit ma requête et se trompe sur la "vue" où il y a des min("CONTRAT"."ID") KEEP ..., et que pour "désactiver" l'optimiseur il faut ajouter quelque chose pour l'empêcher de joindre les index entre eux, un trunc sur l'ID par exemple.

    Pour moi il s'agit clairement d'un bug, et je vais faire avec. Mais je voulais avoir votre avis sur ma requête et savoir si elle était vraiment trop crade, et donc si c'est bien fait pour moi d'avoir ce problème, ou si elle est tolérable et donc Oracle mérite la corde.

    Merci

    10.2.0.4.0 - 64bit
    With the Partitioning, OLAP, Data Mining and Real Application Testing options
    Il vaut mieux monopoliser son intelligence sur des bêtises que sa bêtise sur des choses intelligentes.

  2. #2
    Membre expérimenté Avatar de ojo77
    Homme Profil pro
    Architecte de base de données
    Inscrit en
    Décembre 2010
    Messages
    680
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Architecte de base de données
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Décembre 2010
    Messages : 680
    Points : 1 597
    Points
    1 597
    Par défaut
    Tu peux poster les divers plans d'exécution (en autotrace traceonly) ?

  3. #3
    Membre émérite Avatar de nuke_y
    Profil pro
    Indépendant en analyse de données
    Inscrit en
    Mai 2004
    Messages
    2 076
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Indépendant en analyse de données

    Informations forums :
    Inscription : Mai 2004
    Messages : 2 076
    Points : 2 370
    Points
    2 370
    Par défaut
    Bien sûr. Voici les plans d'exécutions avec les vraies tables:

    Le "mauvais" plan, celui qu'Oracle fait par défaut avec la requête de base, et qui renvoie les données fausses:
    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
    -----------------------------------------------------------------------------------------------------
    | Id  | Operation                      | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT               |                    |     3 |   219 |    41   (8)| 00:00:01 |
    |   1 |  NESTED LOOPS OUTER            |                    |     3 |   219 |    41   (8)| 00:00:01 |
    |*  2 |   HASH JOIN                    |                    |     1 |    45 |     8  (13)| 00:00:01 |
    |   3 |    TABLE ACCESS BY INDEX ROWID | TCLIENT            |     1 |    19 |     2   (0)| 00:00:01 |
    |*  4 |     INDEX RANGE SCAN           | IDX_TCLIENT_NAME_1 |     1 |       |     1   (0)| 00:00:01 |
    |   5 |    TABLE ACCESS FULL           | TCONTRACTS_CLT     |  1016 | 26416 |     5   (0)| 00:00:01 |
    |   6 |   VIEW PUSHED PREDICATE        |                    |     1 |    28 |    33   (7)| 00:00:01 |
    |*  7 |    HASH JOIN                   |                    |     1 |    23 |    33   (7)| 00:00:01 |
    |   8 |     TABLE ACCESS BY INDEX ROWID| TPRICE_LISTS       |     2 |    20 |     2   (0)| 00:00:01 |
    |*  9 |      INDEX RANGE SCAN          | FK_TPRICE_LISTS_01 |     2 |       |     1   (0)| 00:00:01 |
    |  10 |     VIEW                       |                    |    15 |   195 |    31   (7)| 00:00:01 |
    |* 11 |      FILTER                    |                    |       |       |            |          |
    |  12 |       SORT GROUP BY            |                    |    15 |   345 |    31   (7)| 00:00:01 |
    |* 13 |        HASH JOIN               |                    |  1454 | 33442 |    30   (4)| 00:00:01 |
    |  14 |         TABLE ACCESS FULL      | TCONTRACTS_CLT     |  1016 | 13208 |     5   (0)| 00:00:01 |
    |  15 |         TABLE ACCESS FULL      | TPRESTATIONS       |  1474 | 14740 |    24   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------------------------
    Le "bon" plan, celui qu'Oracle fait quand je "bloque" l'optimiseur en faisant une jointure avec un trunc(id), et qui renvoie les données justes:
    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
    -----------------------------------------------------------------------------------------------------
    | Id  | Operation                      | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT               |                    |    28 |  2044 |    41   (8)| 00:00:01 |
    |   1 |  NESTED LOOPS OUTER            |                    |    28 |  2044 |    41   (8)| 00:00:01 |
    |*  2 |   HASH JOIN                    |                    |     1 |    45 |     8  (13)| 00:00:01 |
    |   3 |    TABLE ACCESS BY INDEX ROWID | TCLIENT            |     1 |    19 |     2   (0)| 00:00:01 |
    |*  4 |     INDEX RANGE SCAN           | IDX_TCLIENT_NAME_1 |     1 |       |     1   (0)| 00:00:01 |
    |   5 |    TABLE ACCESS FULL           | TCONTRACTS_CLT     |  1016 | 26416 |     5   (0)| 00:00:01 |
    |   6 |   VIEW PUSHED PREDICATE        |                    |     1 |    28 |    33   (7)| 00:00:01 |
    |*  7 |    HASH JOIN                   |                    |    27 |   621 |    33   (7)| 00:00:01 |
    |   8 |     TABLE ACCESS BY INDEX ROWID| TPRICE_LISTS       |     2 |    20 |     2   (0)| 00:00:01 |
    |*  9 |      INDEX RANGE SCAN          | FK_TPRICE_LISTS_01 |     2 |       |     1   (0)| 00:00:01 |
    |  10 |     VIEW                       |                    |  1454 | 18902 |    31   (7)| 00:00:01 |
    |  11 |      SORT GROUP BY             |                    |  1454 | 33442 |    31   (7)| 00:00:01 |
    |* 12 |       HASH JOIN                |                    |  1454 | 33442 |    30   (4)| 00:00:01 |
    |  13 |        TABLE ACCESS FULL       | TCONTRACTS_CLT     |  1016 | 13208 |     5   (0)| 00:00:01 |
    |  14 |        TABLE ACCESS FULL       | TPRESTATIONS       |  1474 | 14740 |    24   (0)| 00:00:01 |
    -----------------------------------------------------------------------------------------------------
    Un autre "bon" plan, celui qu'Oracle fait quand je remplace la jointure ouverte par une fermée et qui renvoie des données justes (excepté le fait que pour ma requête finale j'en ai besoin de la jointure ouverte):
    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
    ------------------------------------------------------------------------------------------------------
    | Id  | Operation                       | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                |                    |     3 |   189 |    41   (8)| 00:00:01 |
    |*  1 |  HASH JOIN                      |                    |     3 |   189 |    41   (8)| 00:00:01 |
    |   2 |   TABLE ACCESS BY INDEX ROWID   | TPRICE_LISTS       |     2 |    20 |     2   (0)| 00:00:01 |
    |   3 |    NESTED LOOPS                 |                    |     2 |   100 |    10  (10)| 00:00:01 |
    |*  4 |     HASH JOIN                   |                    |     1 |    40 |     8  (13)| 00:00:01 |
    |   5 |      TABLE ACCESS BY INDEX ROWID| TCLIENT            |     1 |    19 |     2   (0)| 00:00:01 |
    |*  6 |       INDEX RANGE SCAN          | IDX_TCLIENT_NAME_1 |     1 |       |     1   (0)| 00:00:01 |
    |   7 |      TABLE ACCESS FULL          | TCONTRACTS_CLT     |  1016 | 21336 |     5   (0)| 00:00:01 |
    |*  8 |     INDEX RANGE SCAN            | FK_TPRICE_LISTS_01 |     2 |       |     1   (0)| 00:00:01 |
    |   9 |   VIEW                          |                    |  1454 | 18902 |    31   (7)| 00:00:01 |
    |  10 |    HASH GROUP BY                |                    |  1454 | 33442 |    31   (7)| 00:00:01 |
    |* 11 |     HASH JOIN                   |                    |  1454 | 33442 |    30   (4)| 00:00:01 |
    |  12 |      TABLE ACCESS FULL          | TCONTRACTS_CLT     |  1016 | 13208 |     5   (0)| 00:00:01 |
    |  13 |      TABLE ACCESS FULL          | TPRESTATIONS       |  1474 | 14740 |    24   (0)| 00:00:01 |
    ------------------------------------------------------------------------------------------------------
    Merci
    Il vaut mieux monopoliser son intelligence sur des bêtises que sa bêtise sur des choses intelligentes.

  4. #4
    Expert éminent sénior Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Points : 11 252
    Points
    11 252
    Par défaut
    Faitez vos jointures des tables naturellement et appliquez Min… Keep … avec Group BY sur la jointure. Quelque chose de type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
     
    WITH
    "CLIENT" AS
    (
     SELECT '1ER CLIENT' "NAME", 1 "ID" FROM dual
    ),
    "CONTRAT" AS
    (
     SELECT '1ER CONTRAT' "NAME", 1 "ID", 1 "ID_CLIENT" FROM dual
     union
     SELECT '2E CONTRAT' "NAME", 2 "ID", 1 "ID_CLIENT" FROM dual
    ),
    "PRESTATION" AS
    (
     SELECT '1ER PRESTATION' "NAME", 1 "ID", 1 "ID_CONTRAT", sysdate-3 "DATE" FROM dual
     union
      SELECT '2E PRESTATION' "NAME", 2 "ID", 1 "ID_CONTRAT", sysdate-2 "DATE" FROM dual
      union
       SELECT '3E PRESTATION' "NAME", 3 "ID", 1 "ID_CONTRAT", sysdate-1 "DATE" FROM dual
       union
       SELECT '4E PRESTATION' "NAME", 4 "ID", 2 "ID_CONTRAT", sysdate-3 "DATE" FROM dual
     union
      SELECT '5E PRESTATION' "NAME", 5 "ID", 2 "ID_CONTRAT", sysdate-20 "DATE" FROM dual
      union
       SELECT '6E PRESTATION' "NAME", 6 "ID", 2 "ID_CONTRAT", sysdate-1 "DATE" FROM dual
    ),
    "TARIF" AS
    (
     SELECT 1 "ID_CONTRAT", sysdate-20 "DATE_START", sysdate-4 "DATE_END", 1 "TARIF" FROM dual
     union
     SELECT 1 "ID_CONTRAT", sysdate-4 "DATE_START", sysdate "DATE_END", 2 "TARIF" FROM dual
     union
     SELECT 2 "ID_CONTRAT", sysdate-20 "DATE_START", sysdate-4 "DATE_END", 3 "TARIF" FROM dual
     union
     SELECT 2 "ID_CONTRAT", sysdate-4 "DATE_START", sysdate "DATE_END", 4 "TARIF" FROM dual
    )
    SELECT Min("CLIENT"."NAME") KEEP (DENSE_RANK FIRST ORDER BY "PRESTATION"."DATE" ASC ) "Client Name",
           Min("CONTRAT"."NAME") KEEP (DENSE_RANK FIRST ORDER BY "PRESTATION"."DATE" ASC ) "Contrat Name",
    		   Min("TARIF"."TARIF") KEEP (DENSE_RANK FIRST ORDER BY "PRESTATION"."DATE" ASC ) "Tarif",
    		   "CONTRAT"."ID",
    		   Min("TARIF"."ID_CONTRAT") KEEP (DENSE_RANK FIRST ORDER BY "PRESTATION"."DATE" ASC )
    FROM
    "CLIENT"
    JOIN "CONTRAT" ON ("CLIENT"."ID" = "CONTRAT"."ID_CLIENT")
    JOIN "PRESTATION" ON ("CONTRAT"."ID" = "PRESTATION"."ID_CONTRAT")
    LEFT OUTER JOIN "TARIF" ON ("CONTRAT"."ID" = "TARIF"."ID_CONTRAT" AND "PRESTATION"."DATE" BETWEEN "TARIF"."DATE_START" AND "TARIF"."DATE_END")
    Group By "CONTRAT"."ID"

  5. #5
    Membre émérite Avatar de nuke_y
    Profil pro
    Indépendant en analyse de données
    Inscrit en
    Mai 2004
    Messages
    2 076
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Indépendant en analyse de données

    Informations forums :
    Inscription : Mai 2004
    Messages : 2 076
    Points : 2 370
    Points
    2 370
    Par défaut
    Merci mnitu. Alors pour la science j'ai appliqué cette méthode et techniquement ça marche, je n'en doutais pas. Excepté que fonctionnellement je perds le fait de pouvoir repérer sur quel contrat la première prestation a eu lieu, or c'est la moitié de l'intérêt de mon truc.

    De plus le fait de faire des min keep... au niveau le plus élevé de la requête est inapplicable avec mon générateur de requête, je dois donc garder une logique:
    "CLIENT"
    -jointure simple- "CONTRAT"
    -jointure simple ouvert- "une vue qui sort le tarif pour le contrat client qui inclu la première prestation"

    Bon de toutes façons, je ne doute pas qu'on puisse écrire la requête de plusieurs manières différentes, moi-même je ne l'aurai pas naturellement écrite comme ça.

    Non ce qui me turlupine c'est ce bug. A défaut d'identifier quel patch le corrige, j'aurai voulu avoir votre avis de pro expérimentés sur la "saleté" de la requête. En gros une sorte de sondage:
    1) Pas de chance, c'est un sale bug, tu ne pouvais pas prévoir
    2) Tu l'as bien mérité, c'était prévisible qu'un truc pareil te saute à la figure

    Merci
    Il vaut mieux monopoliser son intelligence sur des bêtises que sa bêtise sur des choses intelligentes.

  6. #6
    Expert éminent sénior Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Points : 11 252
    Points
    11 252
    Par défaut
    Cachez tout ça derrière une vue pour votre générateur de requête.

    C'est un bug si vous arrivez à fournir un jeu d'essai qui démontre vos propos. Sinon ça peut être n’importe quoi, la requête, des données corrompues, des erreurs humaine, etc.

  7. #7
    Membre émérite Avatar de nuke_y
    Profil pro
    Indépendant en analyse de données
    Inscrit en
    Mai 2004
    Messages
    2 076
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Indépendant en analyse de données

    Informations forums :
    Inscription : Mai 2004
    Messages : 2 076
    Points : 2 370
    Points
    2 370
    Par défaut
    Merci.

    Malheureusement je ne peux pas créer de vue, ça aurait sûrement résolu mon problème.

    Je vais partir de la manière dont je suis parti et demander aux DBA de contrôler s'il s'agit d'un bug ou pas.

    Merci
    Il vaut mieux monopoliser son intelligence sur des bêtises que sa bêtise sur des choses intelligentes.

  8. #8
    Expert éminent sénior Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Points : 11 252
    Points
    11 252
    Par défaut
    J’ai un mauvais souvenir avec une base Oracle 9R2 c’est vrai et une requête similaire (sous-requête avec Keep) ou l’optimiseur n’arrivait pas à faire le predicate pushing dans la sous-requête et par conséquence les indexes n’était pas utilisés, bref un désastre dpv performance, que j’ai résolu en appliquant le Keep sur la jointure comme je vous l’ai suggère.

    Si vous trouvez que c’est vraiment un bug faite nous un retour.

Discussions similaires

  1. [BUG] perte des drivers Oracle sous ECLIPSE
    Par d_token dans le forum JDBC
    Réponses: 4
    Dernier message: 21/08/2006, 13h30
  2. utiliser optimiseur oracle
    Par infsaf dans le forum Oracle
    Réponses: 5
    Dernier message: 16/08/2006, 14h13
  3. Réponses: 7
    Dernier message: 03/05/2006, 09h05
  4. [10g] Importer base excel sous Oracle
    Par Le Mad dans le forum Oracle
    Réponses: 5
    Dernier message: 01/02/2006, 14h12
  5. 10g: à part OEM, quel outil Oracle pour l'admin ?
    Par smartiz dans le forum Entreprise Manager
    Réponses: 11
    Dernier message: 01/09/2005, 12h45

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