SQL с 3 условиями с объединением: «где X в (foo)», «где X не в (foo)» или правило «где» - PullRequest
1 голос
/ 21 июня 2011

Я пытаюсь создать запрос, в котором, в зависимости от переменной, включенные результаты являются результатом одного из трех условий:

1) WHERE am.process     IN (SELECT process_name FROM it.special_processes)
2) WHERE am.process NOT IN (SELECT process_name FROM it.special_processes)
3) (equivalent of no WHERE clause for the process)

Этот запрос работает для условия 1:

SELECT info.sortingorder sort, info.stage, SUM(am.qty) boh
FROM it.snpshot am, it.info info, it.naming nm
WHERE info.stage = nm.stage(+)
AND am.process  IN (SELECT process_name FROM it.special_processes)
GROUP BY info.sortingorder, info.stage

Это работает для условия 1 или, в зависимости от: флага, условия 3:

SELECT info.sortingorder sort, info.stage, SUM(am.qty) boh
FROM it.snpshot am, it.info info, it.naming nm
WHERE info.stage = nm.stage(+)
AND :FLAG = 1 OR am.process IN (SELECT process_name FROM it.special_processes)
GROUP BY info.sortingorder, info.stage

Однако, когда я хочу обработать условие 3, все усложняется.Я пытался что-то вроде:

SELECT info.sortingorder sort, info.stage, SUM(am.qty) boh
FROM it.snpshot am, it.info info, it.naming nm
WHERE info.stage = nm.stage(+)
AND :flag = 0    -- (this is for condition 3)
OR (:flag = 1 AND am.process     IN (SELECT process_name FROM it.special_processes))
OR (:flag = 2 AND am.process NOT IN (SELECT process_name FROM it.special_processes))
GROUP BY info.sortingorder, info.stage

... Но я получаю:

ORA-01719: outer join operator (+) not allowed in operand of OR or IN
*Cause:    An outer join appears in an or clause.
*Action:   If A and B are predicates, to get the effect of (A(+) or B),
           try (SELECT WHERE (A(+) AND NOT B)) UNION ALL (SELECT WHERE (B)).
Error at Line: 5 Column: 19

Сообщение об ошибке пытается дать какой-то совет, но я не вижу, как оно относится кнабор условий, как показано выше (условное UNION ALL).Удаление внешнего соединения (+) устраняет ошибку, но она есть по причине.Сложность начинает возрастать (особенно учитывая, что вышеизложенное является результатом гораздо более крупного запроса), и у меня уже есть SQL «NOT IN ()», который я обычно вижу в виде красного флага.Мой предыдущий подход состоял в том, чтобы динамически генерировать «NOT IN» или «IN» часть строки запроса, но это всегда выглядело как ужасный хак.Примечание: PL / SQL не подходит по разным причинам.

Мой вопрос: как бы вы справились с этой ситуацией?

Ответы [ 4 ]

3 голосов
/ 21 июня 2011

Полагаю, вы просто упускаете здесь свои логические аргументы.

SELECT info.sortingorder sort, info.stage, SUM(am.qty) boh
FROM it.snpshot am, it.info info, it.naming nm
WHERE info.stage = nm.stage(+)
AND (:flag = 0    -- (this is for condition 3)
OR (:flag = 1 AND am.process     IN (SELECT process_name FROM it.special_processes))
OR (:flag = 2 AND am.process NOT IN (SELECT process_name FROM it.special_processes))
)
GROUP BY info.sortingorder, info.stage

Теперь все термины объединены.Ранее вам не хватало одного набора ();Я добавил его после слова «и» перед: flag = 0 и перед группой по.Поверьте, это поможет вам.Вы понимаете почему?правда и ложь и ложь или правда = правда.

2 голосов
/ 21 июня 2011

Лично я бы очень старался поместить это в блок IF и написать три версии запроса; ПЛАН EXPLAIN для «условных» запросов, таких как эти, имеет тенденцию быть довольно плохим ...

Что произойдет, если вы поменяете нотацию на явные СОЕДИНЕНИЯ?

SELECT
  info.sortingorder sort,
  info.stage,
  SUM(am.qty) boh
FROM
  it.snpshot am
CROSS JOIN
  it.info    info
LEFT JOIN
  it.naming  nm
    ON info.stage = nm.stage
WHERE
       (:flag = 0)   -- (this is for condition 3)
    OR (:flag = 1 AND am.process     IN (SELECT process_name FROM it.special_processes))
    OR (:flag = 2 AND am.process NOT IN (SELECT process_name FROM it.special_processes))
GROUP BY
  info.sortingorder,
  info.stage

EDIT

Это может или не может помочь вашей ошибке, но может привести к лучшему ОБЪЯСНИТЬ ПЛАН ...

WHERE
  CASE WHEN EXISTS (SELECT * FROM it.special_processes WHERE process_name = am.process)
       THEN 2 ELSE 1 END
  <>
  :flag
  • flag = 0 => неравный (true) всегда (CASE никогда не возвращает 0)
  • flag = 1 => неравный (true), если в нем существует am.process.special_processes
  • flag = 2 => неравный (true), если am.process не существует в нем.special_processes
0 голосов
/ 21 июня 2011

Во-первых, я бы потерял старый синтаксис внешнего соединения и переписал бы с использованием ANSI (это может сразу избавить вас от сообщения об ошибке - это недостаток старого (+) синтаксиса). Затем я бы попытался включить логику CASE в предложение where (не проверено):

SELECT info.sortingorder sort, info.stage, SUM(am.qty) boh
FROM it.snapshot am
   , it.info info LEFT JOIN it.naming nm ON info.stage = nm.stage
WHERE CASE WHEN 
           (:flag = 1 AND am.process IN 
              (SELECT process_name FROM it.special_processes)) THEN 'Y'
           WHEN 
           (:flag = 2 AND am.process NOT IN 
              (SELECT process_name FROM it.special_processes)) THEN 'Y'
           WHEN :flag = 0 THEN 'Y'
           ELSE 'N'
      end = 'Y'
GROUP BY info.sortingorder, info.stage;
0 голосов
/ 21 июня 2011

Мое решение для "условия без указания места", как правило, заключается в предоставлении предиката как такового:

WHERE 1 = 1

Поскольку оно всегда оценивается как истинное, оно не должно иметь влияния, даже если оно включено, наряду с другими условиями предиката. Однако я видел, как он изменил план выполнения запросов в Microsoft SQL Server.

...