ORA-01799: столбец не может быть внешним соединен с подзапросом

Вот мой запрос

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    
                )
    )

и вот сообщение об ошибке, которое я получаю.

enter image description here

Что я делаю неправильно?

Ответ 1

вы можете переписать это, нажав вспомогательный запрос так, чтобы его внешняя сторона не присоединилась:

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 ) 

Ответ 2

Ну, Oracle, по-видимому, не поддерживает использование подзапроса внутри условия соединения для внешнего соединения. Поэтому вам нужно избавиться от подзапроса.

Вопрос в том, почему он вообще существует? У вас есть условия "< =" в двух местах, поэтому предикат в основном говорит "все записи, дата вступления в силу которых не позднее последней даты вступления в силу, которая не позднее, чем сейчас". Если это то, что вы действительно хотите, вы можете упростить его до "всех записей, дата вступления в силу которых не позднее, чем сейчас", то есть:

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

Voila, нет подзапроса.

Но действительно ли это то, что вы хотите, или вы имели в виду, что первый "< =" должен быть просто "=" - т.е. найти запись с самой последней эффективной датой до этого? Если это то, что вы действительно хотите, будет сложнее переписать.

Ответ 3

На ваш вопрос уже был дан ответ, но у кого-то может быть несколько другой случай, когда им нужно получить последний EFFDT на основе столбца вместо фиксированной даты. В этих случаях я нашел только один вариант IMPERFECT и одно решение UGLY...

Неверный вариант:

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'))

Это несовершенный вариант, потому что если таблица CUST_OPT имеет будущие даты, но не текущие (< = N.ISSUE_DT) даты, внешнее соединение не будет работать и никакие строки не будут возвращены. В общем, термины PeopleSoft (да, я видел ваш SETID + EFFDT там!; -D) это не произойдет очень часто, так как люди стремятся создать один 01/01/1900 EFFDT, чтобы сделать первое значение эффективным с "навсегда", но поскольку это не всегда так; у нас также есть уродливое решение:

Я также нашел один вариант UGLY (но я на самом деле рекомендую его, и он решает проблему, поэтому позвольте ему назвать это решение):

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')
                                                         )
                                                     )

Эта опция вернет строки FUTURE, которые необходимо игнорировать! Таким образом, мы добавляем условия в оператор SELECT, который будет IGNORE возвращаемым значениям, если они не предназначены для получения. Как я уже сказал, это решение UGLY, но это решение.

Для моего уродливого решения, если строки будут обработаны позже в приложении Engine или PL/SQL или что-то еще; вы можете вместо того, чтобы иметь оператор CASE для каждого столбца, просто добавьте новый столбец, который скажет вам, что вы извлекли "неправильные" данные и проигнорировали поля позже в своем коде на основе этого столбца, например:

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