首先,也许您最好看一下可用于 PHP/MySQL 的几个 ORM(对象关系管理)系统中的任何一个。
但是重新发明轮子相对容易,只要你保持它很小(这意味着你只能解决非常简单的查询)。
如果是这种情况,假设我们只需要内部连接,并且它们都是一对多类型(实际上这不是严格的要求,我们将看到)。我们可以构建一个 DIY ORM(我怀疑更贴切的名字应该是“Diy Orm Cthulhu Fhtagn”)
第一步是将这些信息存储在某个地方,例如数组。所有可能的 JOIN 的一个条目。您还需要向系统描述您的表格。您还可以让系统查询 MySQL 以检索表和字段名称,可能偶尔从生成 PHP 代码的单独实用程序中获取。
// This just maps what fields are in what tables
$tables = array(
'contacts' => array(
'first_name' => true /* or an array of information such as SQL type, etc. */
);
);
// This maps all the JOINs
$orm = array(
'contacts' => array(
'accounts' => array(
'on' => array( 'account_id', 'account_id' ),
'type' => 'LEFT JOIN', //
)
)
);
因此,您从 $selectFields 列表开始。您将这些字段复制到$unresolvedFields
中,并开始一个接一个地检查它们。您的目标是解析所有字段。
伪代码(实际上不是那么伪):
while (!empty($unresolvedFields)) {
$changes = false;
// Try to resolve one of them.
foreach ($unresolvedFields as $i => $field) {
// Try to resolve it.
list($tableName, $fieldName) = explode('.', $field);
// Did we already select from this table?
if (array_key_exists($tableName, $selectedTables)) {
// Yes, so this field has been resolved for free.
$changes = true;
$resolvedFields[] = $field;
array_push($selectedTables[$tableName], $fieldName);
unset($unresolvedFields[$i];
// On to the next field.
continue;
}
// This is the first time we see this table.
// Is this the VERY FIRST table (assume it's the "lead" table --
// it's not necessary but it simplifies the code)?
if (empty($selectedTables)) {
// Yes. We need do no more.
$selectedTables[$tableName] = array( $fieldName );
$changes = true; //-//
$resolvedFields[] = $field; //-//
unset($unresolvedFields[$i]; //-//
// On to the next field. //--//
continue; //--//
} // We could also put this check before the last. If we did, the
// lines above marked //-// could be left out; those with //--// should.
// And we would need $selectedTables[$tableName] = array(/*EMPTY*/);
// We did not see this table before, and it's not the first.
// So we need a way to join THIS table with SOME of those already used.
// Again we suppose there're no ambiguities here. This table HAS a
// link to some other. So we just need ask, "WHICH other? And do we have it?"
$links = $orm[$tableName];
$useful = array_intersect_keys($orm[$tableName], $selectedTables);
// $useful contains an entry 'someTable' => ( 'on' => ... )
// for each table that we can use to reference $tableName.
// THERE MUST BE ONLY ONE, or there will be an ambiguity.
// Of course most of the time we will find none.
// And go on with the next field...
if (empty($useful)) {
continue;
}
// TODO: check count($useful) is really 1.
$changes = true;
$selectedTables[$tableName] = array( $fieldName );
list($joinWith, $info) = each($useful[0]);
// We write SQL directly in here. We actually shouldn't, but it's faster
// to do it now instead of saving the necessary info.
// $info could actually also contain the JOIN type, additional conditions...
$joins[] = "INNER JOIN {$joinWith} ON ( {$tableName}.{$info['on'][0]}
= {$joinWith}.{$info['on'][1]} )";
unset($unresolvedFields[$i];
}
// If something changed, we need to repeat, because a later field could have
// supplied some table that could have made joinable an earlier field which we
// had given up on, before.
if (!$changes) {
// But if nothing has changed there's no purpose in continuing.
// Either we resolved all the fields or we didn't.
break;
}
}
// Now, if there're still unresolved fields after the situation stabilized,
// we can't make this query. Not enough information. Actually we COULD, but
// it would spew off a Cartesian product of the groups of unjoined tables -
// almost surely not what we wanted. So, unresolveds cause an error.
if (!empty($unresolvedFields)) {
throw new \Exception("SOL");
}
// Else we can build the query: the first table leads the SELECT and all the
// others are joined.
$query = "SELECT " . implode(', ', $selectedFields)
. ' FROM ' . array_shift($selectedTables) . "\n";
// Now for each $selectedTables remaining
foreach ($selectedTables as $table) {
$query .= $joins[$table] . "\n";
// Now we could add any WHEREs, ORDER BY, LIMIT and so on.
...
如果用户选择了姓名、名字、姓氏
您还需要在人类可读的“名称”和“accounts.account_name”之间进行“翻译”。然而,一旦你这样做了,上面的算法就会找到这些记录:
Name ... fields = [ accounts.account_name ], tables = [ accounts ], joins = [ ]
First Name ... fields = [ a.ac_name, co.first ], tables = [ ac, co ], joins = [ co ]
Last Name ... contacts is already in tables, so fields = [ 3 fields ], rest unchanged