Отрицание произвольного условия условия where (включая нулевые тесты) - PullRequest
4 голосов
/ 14 июля 2009

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

В 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> равно имя = 'Аарон' , а <условие B> равно возраст = 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...)

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

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

Ответы [ 5 ]

1 голос
/ 14 июля 2009

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

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, и он должен иметь тот же конечный результат.

1 голос
/ 14 июля 2009

Предполагая, что у вашей таблицы есть первичный ключ 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 голосов
/ 14 июля 2009

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

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

Замена нулей:

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

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

0 голосов
/ 14 июля 2009

Если у вас есть поле id, попробуйте:

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

0 голосов
/ 14 июля 2009

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

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

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

что переводится внутренне как:

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

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

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

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

что переводится внутренне как:

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

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

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

...