Честно, зачем?Пока ключ, к которому вы обращаетесь, индексируется, просто поместите туда несколько запросов:
WHERE (foo BETWEEN 1 AND 1000
OR foo BETWEEN 1500 AND 1600
OR foo BETWEEN 1250 AND 1300
) AND (
foo NOT BETWEEN 25 AND 50
)
Вы можете немного повысить эффективность, создав диссектор, но я бы спросил, стоит лиЭто.Все элементы предложения WHERE не входят в индекс, поэтому вы не препятствуете выполнению какой-либо сложной операции (то есть вы не прекращаете полное сканирование таблицы, выполняя это).
Таким образом, вместо того, чтобы тратить время на создание системы, которая сделает это за вас, просто внедрите простое решение (OR
объединение разрешений и AND
объединение Denys) и переходите к более важным вещам,Тогда, если это станет проблемой позже, посетите это тогда.Но я действительно не думаю, что это когда-либо станет слишком большой проблемой ...
Edit Хорошо, вот очень простой алгоритм для этого.Он использует строки в качестве хранилища данных, поэтому он достаточно эффективен для меньших чисел (менее 1 миллиона):
class Dissector {
protected $range = '';
public function allow($low, $high) {
$this->replaceWith($low, $high, '1');
}
public function deny($low, $high) {
$this->replaceWith($low, $high, '0');
}
public function findRanges() {
$matches = array();
preg_match_all(
'/(?<!1)1+(?!1)/',
$this->range,
$matches,
PREG_OFFSET_CAPTURE
);
return $this->decodeRanges($matches[0]);
}
public function generateSql($field) {
$ranges = $this->findRanges();
$where = array();
foreach ($ranges as $range) {
$where[] = sprintf(
'%s BETWEEN %d AND %d',
$field,
$range['from'],
$range['to']
);
}
return implode(' OR ', $where);
}
protected function decodeRanges(array $matches) {
$range = array();
foreach ($matches as $match) {
$range[] = array(
'from' => $match[1] + 1,
'to' => ($match[1] + strlen($match[0]))
);
}
return $range;
}
protected function normalizeLengthTo($size) {
if (strlen($this->range) < $size) {
$this->range = str_pad($this->range, $size, '0');
}
}
protected function replaceWith($low, $high, $character) {
$this->normalizeLengthTo($high);
$length = $high - $low + 1;
$stub = str_repeat($character, $length);
$this->range = substr_replace($this->range, $stub, $low - 1, $length);
}
}
Использование:
$d = new Dissector();
$d->allow(1, 10);
$d->deny(5, 15);
$d->allow(10, 20);
var_dump($d->findRanges());
var_dump($d->generateSql('foo'));
Генерирует:
array(2) {
[0]=>
array(2) {
["from"]=>
int(1)
["to"]=>
int(4)
}
[1]=>
array(2) {
["from"]=>
int(10)
["to"]=>
int(20)
}
}
string(44) "foo BETWEEN 1 AND 4 OR foo BETWEEN 10 AND 20"