Нам нужен метод для разделения условий.Однако мы не можем разделить AND и OR как равные, потому что AND имеют более высокий приоритет по сравнению с OR .
Так в примере, подобном этому:
Cond1 AND Cond2 OR Cond3
Мы не можем разделить на AND|OR
потому что мы пропустили бы Cond1 AND Cond2
в целом.
Поэтому первое, что нужно сделать, это добавить дополнительные скобки (с регулярными выражениями), где это необходимо, чтобы следующий алгоритм правильно разделил условия.В предыдущем примере это было бы (Cond1 AND Cond2) OR Cond3
.
После настройки мы используем регулярное выражение для извлечения условий для текущего уровня.Нам нужно использовать рекурсивные регулярные выражения для обнаружения открывающих / закрывающих скобок.
Каждое условие сохраняется в массиве и затем отправляется для обработки (рекурсивно).Это связано с тем, что некоторые условия могут быть сложными и иметь вложенные условия.
Все эти условия и подусловия хранятся в массиве.
Как только вы выполнили все условия (и подусловия)) у вас есть две альтернативы для монтирования SQL.
Первым вариантом будет один запрос без предложения WHERE и одна SUM для каждого условия.Это, вероятно, лучше всего, если в таблице не так много строк
Второй вариант - запуск нескольких запросов SELECT count(*)
со всеми условиями.
Я оставляю здесь код php.Я также добавил опцию для настройки максимального количества уровней гнезда при разделении условий.
У вас есть демо на Ideone , здесь .
<?php
$conditions = 'C++ AND ((UML OR Python) OR (not Perl))';
// Other tests...
//$conditions = "C++ AND Python OR Perl";
//$conditions = "C++ AND Python OR Perl OR (Perl AND (Ruby AND Docker AND (Lisp OR (C++ AND Ada) AND Java)))";
///////// CONFIGURATION /////////
$maxNest = 0; // Set to 0 for unlimited nest levels
/////////////////////////////////
print "Original Input:\n";
print $conditions . "\n\n";
// Add implicit parenthesis...
// For example: `A AND B OR C` should be: `(A AND B) OR C`
$addParenthesis = '/(?|(((?:\bNOT\b\s*+)?+[^)(\s]++|(?:\bNOT\b\s*+)?+[(](?:\s*+(?2)\s*+)*+[)])(?:\s*+\bAND\b\s*+((?2)))++)(?=\s*+\bOR\b\s*+)|\s*+\bOR\b\s*+\K((?1)))/im';
while (preg_match($addParenthesis, $conditions)) {
$conditions = preg_replace($addParenthesis, '(\1)', $conditions);
}
print "Input after adding implicit parenthesis (if needed):\n";
print $conditions . "\n\n";
// Optional cleanup: Remove useless NOT () parenthesis
$conditions = preg_replace('/[(]\s*((?:NOT\s*)?+(\S+))\s*[)]/i', '\1', $conditions);
// Optional cleanup: Remove useless NOT NOT...
$conditions = preg_replace('/\bNOT\s+NOT\b/i', '', $conditions);
$list_conditions = [$conditions];
function split_conditions($input, $level = 0) {
global $list_conditions, $maxNest;
if ($maxNest > 0 && $level >= $maxNest) { return; }
// If it is a logic operator, skip
if ( preg_match('/^\s*(?:AND|OR)\s*$/i', $input) ) {
return;
}
// Add condition to the list:
array_push($list_conditions, $input);
// Don't go on if this is a single filter
if ( preg_match('/^\s*(?:NOT\s+)?+[^)(\s]+\s*$/i', $input) ) {
return;
}
// Remove parenthesis (if exists) before evaluating sub expressions
// Do this only for level > 0. Level 0 is not guaranteed to have
// sorrounding parenthesis, so It may remove wanted parenthesis
// such in this expression: `(Cond1 AND Cond2) OR (Cond3 AND Cond4)`
if ($level > 0) {
$input = preg_replace('/^\s*(?:NOT\b\s*)?+[(](.*)[)]\s*$/i', '\1', $input);
}
// Fetch all sub-conditions at current level:
$next_conds = '/((?:\bNOT\b\s*+)?+[^)(\s]++|(?:\bNOT\b\s*+)?+[(](?:\s*+(?1)\s*+)*+[)])/i';
preg_match_all($next_conds, $input, $matches);
// Evaluate subexpressions
foreach ($matches[0] as $match) {
split_conditions($match, $level + 1);
}
}
split_conditions($conditions);
// Trim and remove duplicates
$list_conditions = array_unique(array_map(function($x){
return preg_replace('/^\s*|\s*$/', '', $x);
}, $list_conditions));
// Add columns
$list_conditions = array_map(function($x){
return preg_replace('/([^\s()]++)(?<!\bAND\b)(?<!\bOR\b)(?<!\bNOT\b)/i', "skill='$1'", $x);
}, $list_conditions);
print "Just the conditions...\n\n";
print_r($list_conditions);
print "\n\n";
print "Method 1) Single query with multiple SUM\n\n";
$sum_conditions = implode(",\n", array_map(function($x){
return " SUM( $x )";
}, $list_conditions));
$sumSQL = "SELECT\n$sum_conditions\nFROM candidates;";
print $sumSQL . "\n\n";
print "Method 2) Multiple queries\n\n";
$queries = implode("\n", array_map(function($x){
return "SELECT count(*) from candidates WHERE $x;";
}, $list_conditions));
print $queries . "\n\n";