5

PHP と MySQL を使用して動的クエリを構築しようとしています。

私が行ったことは、テーブルを作成することです(つまり、field_relationsこのフィールドには5つの列があります

  1. field_name (フィールドの名前「つまり、account_id、account_name ....」)
  2. display_label (フィールドを「アカウント ID、名前」に置き換える方法)
  3. table_name (このフィールドが「ie. accounts」に属するテーブル)
  4. related_to (「もしあれば」別のテーブルへのフィールドの関係。デフォルト値はNULL)
  5. related_to_field (「もしあれば」を指すフィールド。デフォルト値はNULL)

サンプルデータはこちら field_name display_label table_name related_to related_to_field account_id Account ID accounts NULL NULL account_name Name accounts NULL NULL first_name First Name contacts NULL NULL last_name Last Name contacts NULL NULL contact_id Contact ID contacts NULL NULL account_id Account ID contacts accounts account_id task_id Task ID tasks NULL NULL subject Subject tasks NULL NULL owner_id Assigned To contacts contacts contact_id daily_sales Sales transactions accounts account_id sold_on Sold On transactions NULL NULL

したがって、3秒でHTMLフォームを作成すると

  1. 表示する列を選択
  2. 数式を列に追加する (オプション)
  3. Pick Condition 句 (オプション)
  4. 「結果表示」ボタン。

このフォームの最初の部分には、display_label列にリストされているすべての値が表示されます。

ユーザーが選択した場合Name, First Name, Last Name

次に、クエリは次のようになります

SELECT accounts.account_name, contacts.first_name, contacts.last_name
FROM accounts 
INNER JOIN contacts ON contacts.account_id = accounts.account_id

クエリが完了すると、実行されます。

または、ユーザーが「Name, Sales」を選択した場合。次に、ユーザーは列にSUM関数を適用したいと考えていますdaily_sales。そして最後に、ユーザーはフィルターを選択しましたSold On between '2014-01-01 00:00:00' AND '2014-10-01 00:00:00'

次に、クエリは次のようになります

SELECT accounts.account_name, SUM(daily_sales) AS daily_sales
FROM accounts 
LEFT JOIN sales ON sales.account_id = accounts.account_id
WHERE sales.sold_on BETWEEN '2014-01-01 00:00:00' AND '2014-10-01 00:00:00'
GROUP BY accounts.account_name

クエリが完了すると、実行されます。

どうすればそのようなクエリを生成できますか? field_relationsテーブルにさらに列を追加する必要がありますか?

ユーザー仕様を取得するために PHP フォームを作成する方法については心配していませんが、MySQL クエリを正しく生成する方法を理解しようとしていますか?

あなたの助けと時間を前もってありがとう。

4

3 に答える 3

0

SQL クエリの作成は、単純な文字列作成の演習と考えてください。前のページのフォームから投稿された値は、field_relations テーブルを参照すると、クエリに必要な列とテーブルを識別します。

最初に投稿された値は、テーブルの 1 つのフィールドを識別し、FROM 句が必要であることを示します。次に、2 番目のテーブルのフィールドに出会うとすぐに、INNER JOIN の連絡先 (または販売) ON contact.account_id = accounts.account_id 句を追加するように指示されます。後で 3 番目のテーブルのフィールドに遭遇した場合は、別の JOIN 句を追加する必要があります。

field_relations に related_to 列と related_to_field 列はまったく必要ありません。これは、field_relations テーブルを参照すると、フォームから投稿された列名がフィールドの由来を示すためです。

于 2014-10-27T22:08:23.150 に答える
0

まず最初に、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
于 2014-10-30T22:38:10.610 に答える