2009-07-14 1 views
4

Я хочу эффективно проверить, содержит ли таблица любые строки, которые соответствуют < условиям A > и не соответствуют < условиях B >, где условия произвольные.Отрицание произвольного условия условия where (включая нулевые тесты)

В Oracle, это почти работы:

select count(*) from dual 
where exists (
    select * from people 
    where (<condition A>) 
    and not (<condition B>) 
); 
-- returns zero if all rows that match <condition A> also match <condition B> 
-- (well, almost) 

Проблема заключается в помышляли нулевые значения. Допустим, < состояние A > is name = 'Aaron' и < состояние B > is возраст = 21. Запрос будет правильно идентифицировать любые аароны, возраст которых не равен 21, но он не может идентифицировать Ааронов, возраст которых равен нулю.

Вот это правильное решение, но на столе с миллионами записей может занять некоторое время:

select (
    select count(*) from people 
    where (<condition A>) 
) - (
    select count(*) from people 
    where (<condition A>) 
    and (<condition B>) 
) from dual; 
-- returns zero if all rows that match <condition A> also match <condition B> 
-- (correct, but it is s l o w...) 

К сожалению, эти два условия будут произвольными, комплекс, меняется, и вообще вне моего контроля. Они генерируются из структуры персистентности приложений из пользовательских запросов, и, хотя мы стараемся поддерживать наши индексы с нашими пользователями, много времени они будут вызывать большое сканирование таблицы (поэтому первый запрос с предложением «существует» намного быстрее, чем второй - он может остановиться, как только он найдет одну подходящую запись, и ей не нужно делать два отдельных сканирования).

Как я могу сделать это эффективно, не запускаясь по нулям?

+0

где nvl2 (age_column, age_parameter, null)? – glasnt

ответ

0

Если у вас есть поле идентификатора, попробуйте:

выберите COUNT (*) от двойного где существует ( выберите * от людей где (конд а) и не zzz.id в (выберите идентификатор из люди, где (cond b)) );

+0

Спасибо. Я думаю, что это самое быстрое решение, которое позволяет избежать предварительной обработки условий.В моем тестировании скорость сравнима с решением Алекса. –

0

Я не думаю, что вы можете сделать что-либо, если условия полностью произвольно. Возможно ли «переписать» условия в какой-то момент на основе некоторых правил?

Я считаю, что если вы сделаете это:

... where not (age = 21) .... 

который переводит внутренне в:

... where (age != 21) ... 

вы получаете слишком мало записей, так как он не соответствует нулевые значения, верно?

Но если вы сделаете это:

... where not (age = 21 and age is not null) .... 

, который переводит внутренне в:

... where (age != 21 or age is null) .... 

, то вы получите ожидаемые результаты. (не так ли?)

Итак, можете ли вы привести все сравнения в своих условиях, чтобы включить нулевой тест, либо в форме (... или x равно null), либо (... и x не является нулевым)?

+0

То, что вы сказали, правильно. Это интересный подход, но немного сложнее, чем я надеялся. Условия могут быть сложными, например: ((woclass = 'ACTIVITY' или woclass = 'WORKORDER') и ​​reportdate <= TO_TIMESTAMP ('2001-07-01 23: 59: 59.000', 'YYYY-MM-DD HH24: MI: SS.FF ') и верхний (с сообщением), как «% STEVE%» и historyflag = 0 и istask = 0 и (существует (выберите siteid из locancestor, где ((такой предк, как «% SEG100%»)) и (location = workorder.location и systemid = (выберите systemid из locsystem, где primarysystem = '1' и siteid = workorder.siteid) и siteid = workorder.siteid))) –

+0

Да, пост-обработка, которая является сложной задачей. изменяя код, который строит выражения? –

1

Если предположить, что таблица имеет первичный ключ id, один из возможных подходов является:

select count(*) 
from people p1 
left join people p2 
    on (p1.id = p2.id 
    and (p2.<condition A>) 
    and (p2.<contition B>)) 
where p1.<condition A> 
    and p2.id IS NULL 

Вам нужен некоторый простой предварительной обработки на условиях (предваряя каждое имя столбца с p1. или p2. в соответствующих случаях), но это намного проще, чем правильно отрицать условия с упомянутыми вами проблемами NULL.

LEFT JOIN sometable ON whatever WHERE ... AND sometable.id IS NULL является популярным способом выразить «и нет соответствующей записи в sometable, что удовлетворял whatever ограничение, так что я бы ожидать, хороший двигатель хорошо настроен, чтобы оптимизировать эту идиому, насколько это разрешено доступными индексами.

0

Одно из решений заключается в том, чтобы избавиться от любых значений в параметрах сравнения, то есть добавить строки к значениям или заменить нули с невозможными значениями для этого столбца. Для примера из первого:

select x, y 
    from foo 
    join bar on bar.a||'x' = foo.a||'x' /* Replace "=" with "<>" for opposite result */ 
; 

Замена NULLS:

select x, y 
    from foo 
    join bar on nvl(bar.a, 'x') = nvl(foo.a, 'x') -- Ditto 
; 

Теперь второй вариант сложнее (по крайней мере, в Oracle 9.2), потому что вы должны убедиться, что стоимость замены является тот же тип данных, что и столбец, который он заменяет (NVL немного глупо), и что это значение за пределами точности типа данных столбца (например, 9999 для number(3)), но возможно, что это позволит работать с индексами. Конечно, это невозможно, если столбец уже использует максимальную точность/длину.

1

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

select count(*) from dual 
where exists (
    select * from (
    select NVL(name, 'No Name') name, NVL(age, -1) age from people 
    ) 
    where (<condition A>) 
    and not (<condition B>) 
); 

Вы, вероятно, хотите создать функцию на основе индексов на тех, выражения.

Это, безусловно, проще, чем синтаксический анализ условий во время выполнения и попытка заменить имена столбцов выражениями NVL, и он должен иметь тот же конечный результат.