web-dev-qa-db-fra.com

ORA-01799: une colonne ne peut pas être jointe à l'extérieur à une sous-requête

Voici ma requête

SELECT 
    COUNT(C.SETID)
FROM 
    MYCUSTOMER C
    LEFT OUTER JOIN MYCUSTOPTION CO 
    ON 
        (C.SETID = CO.SETID 
            AND C.CUST_ID = CO.CUST_ID 
            AND CO.effdt = ( 
                SELECT MAX(COI.EFFDT) 
                FROM MYCUSTOPTION COI 
                WHERE 
                    COI.SETID = CO.SETID 
                                    AND COI.CUST_ID = CO.CUST_ID 
                                    AND COI.EFFDT <=SYSDATE    
                )
    )

et voici le message d'erreur que je reçois ..

enter image description here

Qu'est-ce que je fais mal???

22
dotnet-practitioner

vous pouvez réécrire cela en poussant la sous-requête de manière à ce qu'elle ne soit pas jointe à l'extérieur:

select Count(C.setid)
  from mycustomer C
       left outer join (select *
                          from mycustoption co
                         where co.effdt <= (select Max(COI.effdt)
                                              from mycustoption COI
                                             where COI.setid = co.setid
                                               and COI.cust_id = co.cust_id
                                               and COI.effdt <= sysdate)) co
                    on ( C.setid = CO.setid
                         and C.cust_id = CO.cust_id ) 
34
DazzaL

Eh bien, Oracle ne prend apparemment pas en charge l'utilisation d'une sous-requête à l'intérieur de la condition de jointure pour une jointure externe. Vous devez donc vous débarrasser de la sous-requête.

La question est, pourquoi est-elle là? Vous avez des conditions "<=" à deux endroits, donc le prédicat dit essentiellement "tous les enregistrements dont la date effective n'est pas plus tard que la dernière date effective qui n'est pas plus tard que maintenant". Si c'est ce que vous voulez vraiment, vous pouvez le simplifier pour "tous les enregistrements dont la date d'entrée en vigueur est au plus tard", à savoir:

ON 
    (C.SETID = CO.SETID 
        AND C.CUST_ID = CO.CUST_ID 
        AND CO.effdt <= SYSDATE    
)

Voila, pas de sous-requête.

Mais est-ce vraiment ce que vous voulez, ou vouliez-vous dire que le premier "<=" soit juste "=" - c'est-à-dire trouver l'enregistrement avec la date d'entrée en vigueur la plus récente avant maintenant? Si c'est ce que vous voulez vraiment, ce sera plus complexe à réécrire.

3
Dave Costa

j'ai également fait face à ce problème aujourd'hui et

SELECT 
COUNT(C.SETID)
FROM 
MYCUSTOMER C
LEFT OUTER JOIN MYCUSTOPTION CO 
ON 
    (C.SETID = CO.SETID 
        AND C.CUST_ID = CO.CUST_ID 
        AND CO.effdt IN ( 
            SELECT MAX(COI.EFFDT) 
            FROM MYCUSTOPTION COI 
            WHERE 
                COI.SETID = CO.SETID 
                                AND COI.CUST_ID = CO.CUST_ID 
                                AND COI.EFFDT <=SYSDATE    
            )
)
1
Aqib Butt

Votre question a déjà reçu une réponse, mais quelqu'un peut avoir un cas légèrement différent où il doit obtenir le dernier EFFDT sur la base d'une colonne, au lieu d'une date fixe. Pour ces cas, je n'ai trouvé qu'une seule option IMPERFECT et une seule solution UGLY ...

Option imparfaite:

SELECT ...
FROM MYTABLE N, CUST_OPT C
WHERE  etc...
AND C.SETID           (+) = N.SETID
AND C.CUST_ID         (+) = N.CUST_ID
AND NVL(C.EFFDT,TO_DATE('01011900','DDMMYYYY')) = NVL((SELECT MAX(EFFDT)
                                                       FROM CUST_OPT SC
                                                       WHERE SC.SETID = C.SETID
                                                       AND   SC.CUST_ID = C.CUST_ID
                                                       AND   SC.EFFDT <= N.ISSUE_DT)
                                                       ,TO_DATE('01011900','DDMMYYYY'))

Il s'agit d'une option imparfaite car si la table CUST_OPT a des dates futures, mais aucune date actuelle (<= N.ISSUE_DT), la jointure externe ne fonctionnera pas et aucune ligne ne sera retournée. En termes généraux PeopleSoft (oui j'ai vu votre SETID + EFFDT là! ;-D) cela n'arriverait pas très souvent car les gens ont tendance à en créer un 01/01/1900 EFFDT pour rendre une première valeur efficace depuis "pour toujours", mais depuis ce n'est pas toujours le cas; nous avons également une solution laide:

J'ai également trouvé une option UGLY (mais je la recommande en fait, et elle résout le problème, alors appelons-la une solution), qui est la suivante:

SELECT n.field1, n.field2,
       CASE WHEN NVL(c.EFFDT,n.ISSUE_DT-1)<=n.ISSUE_DT THEN c.field1 ELSE NULL END,
       CASE WHEN NVL(c.EFFDT,n.ISSUE_DT-1)<=n.ISSUE_DT THEN c.field2 ELSE NULL END
FROM MYTABLE N, CUST_OPT C
WHERE  etc...
AND C.SETID           (+) = N.SETID
AND C.CUST_ID         (+) = N.CUST_ID
AND NVL(C.EFFDT,TO_DATE('01011900','DDMMYYYY')) = NVL((SELECT MAX(EFFDT)
                                                       FROM CUST_OPT SC
                                                       WHERE SC.SETID = C.SETID
                                                       AND   SC.CUST_ID = C.CUST_ID
                                                       AND   SC.EFFDT <= N.ISSUE_DT)
                                                     ,NVL( (SELECT MIN(EFFDT)
                                                            FROM CUST_OPT SC
                                                            WHERE SC.SETID = C.SETID
                                                            AND   SC.CUST_ID = C.CUST_ID
                                                            AND   SC.EFFDT >= N.ISSUE_DT)
                                                         ,TO_DATE('01011900','DDMMYYYY')
                                                         )
                                                     )

Cette option retournera des lignes FUTURES qui doivent être ignorées! Nous ajoutons donc les conditions sur l'instruction SELECT qui IGNORERONT les valeurs retournées, si elles n'étaient pas destinées à être récupérées. Comme je l'ai dit ... c'est une solution UGLY, mais c'est une solution.

Pour ma solution laide, si les lignes seront traitées plus tard dans un moteur d'application ou PL/SQL ou autre; vous pouvez, au lieu d'avoir une instruction CASE pour chaque colonne, ajouter simplement une nouvelle colonne qui vous dira que vous avez récupéré des données "incorrectes" et ignorer les champs plus tard dans votre code, basé sur cette colonne, comme ceci:

CASE WHEN NVL(c.EFFDT,n.ISSUE_DT-1)<=n.ISSUE_DT THEN 'N' ELSE 'Y' END AS IGNORE_CUST_OP_COLS
1
LFLFM