将布尔搜索字符串拆分为其组成部分/计算导致SQL SELECT匹配的原因

时间:2019-04-09 18:43:44

标签: php mysql regex

根据my previous question,软件招聘人员可以输入布尔文本字符串,例如, C++ AND ((UML OR Python) OR (not Perl)),我将其翻译为{{ 1}}。

[更新]我突出显示了 (例如 ),因为某些答案似乎认为我只对此查询感兴趣。这只是一个例子。我寻求用PHP编码的通用解决方案。也许是正则表达式?只需一些代码即可找到查询的每个子项,以便我可以分别查询子项。 [/更新]

我想SELECT * FROM candidates WHERE skill=C++ AND ((skill=UML OR skill=Python) OR (not skill=Perl))命中数,但是我也很想知道查询的每个“子句”(如果是正确的术语)对结果的贡献。 / p>

例如可能有200名使用C ++的候选人,但50名不合适,因为他们既没有UML也没有Python经验。

因此,使用PHP(和rexex?)或MySql,如何将其分解以查看搜索词的哪些部分对结果有贡献?

即,将COUNT(*)分解为skill=C++ AND ((skill=UML OR skill=Python) OR (not skill=Perl))和`COUNT(*)WHERE(技能= UML或skill = Python)等

我不知道MySql是否为此使用了某种COUNT(*) WHERE skill=C++,但怀疑没有,所以我将不得不像描述的那样EXPLAIN分解每个SELECT -分别条款。

我希望我已经清楚地解释了这一点;如果没有,请要求澄清。我根本不知道从哪里开始

6 个答案:

答案 0 :(得分:1)

我们需要一种分割条件的方法。但是,我们不能将AND和OR划分为相等,因为 AND的优先级高于OR

因此在这样的示例中:

Cond1 AND Cond2 OR Cond3

我们无法除以AND|OR,因为我们整体上会缺少Cond1 AND Cond2

因此,第一件事是在需要的地方添加额外的括号(使用正则表达式),以便以下算法可以正确分割条件。在上一个示例中,它将为(Cond1 AND Cond2) OR Cond3

一旦设置,我们将使用正则表达式来获取当前Level的条件。我们需要使用递归正则表达式来检测开/关括号。

每个条件都存储在一个数组中,然后发送以进行处理(递归)。这是因为某些条件可能很复杂并且具有嵌套条件。

所有这些条件和子条件都存储在数组中。

一旦拥有所有条件(和子条件),就有两种安装SQL的选择。

第一个选项是没有WHERE子句的单个查询,每个条件一个SUM。如果表上没有那么多行,那可能是最好的方法

第二个选项是在所有条件下运行多个SELECT count(*)查询。

我在这里留下了php代码。我还添加了一个选项,用于在拆分条件时自定义最大嵌套级别数。

您有一个关于 Ideone here的演示。

<?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";

答案 1 :(得分:1)

虽然不是最优雅的解决方案,但WITH ROLLUP Mysql函数可能会很有用。参见https://dev.mysql.com/doc/refman/8.0/en/group-by-modifiers.html

在最简单的方法中,您可以编写以下查询来捕获独特的技能:

SELECT skill, COUNT(skill) AS mycount
FROM cands
GROUP BY skill WITH ROLLUP

这将返回所有技能的总数,在总数的底部有NULL行,如下所示:

|skill   |mycount  |
|--------|---------|
|C++     |  2      |
|Java    |  3      |
|Python  |  4      |
|NULL    |  9      |

通过添加布尔操作,可以获得更复杂的结果:

SELECT skill, COUNT(skill) AS mycount, SUM(IF(skill='C++' || skill='Python', 1, 0)) AS CorPython
FROM cands
GROUP BY skill WITH ROLLUP

使用第二个选项,CorPython列将汇总-在最后的NULL行中-总人数为“ C或Python”。您可以将此布尔部分设为必需的复杂部分。

|skill   |mycount  |CorPython  |
|--------|---------|-----------|
|C++     |  2      |  2        |
|Java    |  3      |  0        |
|Python  |  4      |  4        |
|NULL    |  9      |  6        |   <-- This is the value you want (6)

答案 2 :(得分:0)

如何使用内置的MySQL 全文搜索功能?返回值自动排名,最匹配的项位于顶部。

您可以创建一个新列,其中包含候选人的所有技能。然后在该字段上进行搜索将为您提供排名结果。

Full-Text Search Functions

答案 3 :(得分:0)

SELECT
 count(*),
 sum(skill=C++),
 sum(skill=UML),
 sum(skill=Python),
 sum(not skill=Perl)
FROM candidates WHERE TRUE
AND skill=C++
AND (FALSE
  OR (FALSE
       OR skill=UML
       OR skill=Python)
  OR (not skill=Perl)
)

答案 4 :(得分:0)

  1. 根据SELECT skill, COUNT(*) FROM tbl和补码重新计算表格。
  2. 提供第1步中的完整表格;让招聘人员关注清单。

要想获得更出色的表现,只需从文本字符串中删除括号,OR和AND即可获得所提到的各种技能。然后只显示那些。

但是这些都不处理(UML OR Python)(C++ and not Perl)之类的不相邻内容。无论如何,您希望从示例中得到多少个计数?还有(UML OR Python) AND C++和另外几个。

甚至都不考虑通过SQL进行解析;使用某些客户端语言。或向候选人提出问题。

代码提示

在Perl中,可以这样做:

$str =~ s{[()]|AND|OR|NOT}{ }ig;
$str =~ s{ +}{ }g;
@skills = split('  ', $str);

PHP代码将使用preg_replaceexplode,但在其他方面类似。在您的示例中,C++ AND ((UML OR Python) OR (not Perl))将成为数组['C ++','UML','Python','Perl']

答案 5 :(得分:0)

嗨,这没什么要提防的

$sqlresult =array ('php, html, php, c++, perl');
//that is array result from MySQL and now we need to count every term alone only in php

//now I create this 
function getcount ($word, $paragraphp){
if (preg_match("/$word/i", $paragraph))
    $count = 1;
else
    $count = 0;

return $count;
}

foreach ( $sqlresult as $key ) {
$finalresult = array ();
$finalresult['$key'] += getcount($key, $key);
}

//now retrieve results as following 

$php = " results for php word is $finalresult[php]";
$perl = "results for perl word is $finalresult[perl]";
echo $php;
echo $perl;

如果您的段落包含很多单词,则应首先使用explode php函数将其转换为数组,然后按上述步骤开始

在不合适的大项目中,您需要MySQL的良好替代品 在这种情况下,我建议使用SPHINX搜索 在SPHINX中运行查询后 运行此查询

SHOW META;

这将为您搜索的每个单词提供命中数,以获取更多详细信息,请检查此http://sphinxsearch.com/docs/current/sphinxql-show-meta.html

相关问题