まず最初に、PHP/MySQL で使用できるいくつかの ORM (オブジェクト関係管理) システムのいずれかを調べた方がよいでしょう。
ただし、ホイールを小さくしておく限り、車輪を再発明するのは比較的簡単です (つまり、非常に単純なクエリしか解決できないことを意味します)。
その場合、内部結合のみが必要であり、それらはすべて 1 対多のタイプであるとしましょう (実際には、後で説明するように、これは厳密な要件ではありません)。DIY ORM を構築できます (より適切な名前は「Diy Orm Cthulhu Fhtagn」のようなものになると思います) 。
最初のステップは、この情報を配列などのどこかに保存することです。可能なすべての JOIN に対して 1 つのエントリ。また、テーブルをシステムに説明する必要があります。システム クエリ 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
、1 つずつ調べ始めます。あなたの目標は、すべてのフィールドを解決することです。
疑似コード (実際にはそれほど疑似ではありません):
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