5

我正在尝试使用 PHP 和 MySQL 构建动态查询。

我所做的是创建了一个表(即。field_relations)这个字段有 5 列

  1. field_name(字段名称“ie. account_id, account_name ....”)
  2. display_label(字段应该如何被替换为使用“ie.Account Id, Name”)
  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. 选择条件子句(可选)
  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

查询完成后将执行。

或者,如果用户选择“名称,销售”。然后用户想在列上应用 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 表时,从上一页的表单中发布的值将确定您想要在查询中使用哪些列和表。

第一个发布的值将标识其中一个表中的一个字段,告诉您需要一个 FROM 子句。然后,一旦您遇到第二个表中的字段,它就会告诉您在 contacts.account_id = accounts.account_id 子句中添加 INNER JOIN 联系人(或销售)。如果稍后遇到第三个表中的字段,则必须添加另一个 JOIN 子句。

您根本不需要 field_relations 中的 related_to 和 related_to_field 列,因为从表单发布的列名在引用 field_relations 表时会告诉您字段来自哪个表。

于 2014-10-27T22:08:23.150 回答
0

首先,也许您最好看一下可用于 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
于 2014-10-30T22:38:10.610 回答
0

为什么不使用 ORM?在这种情况下,教义非常有用:

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html

您可以动态添加实体和字段并将它们连接在一起:易于学习和实施。

于 2014-11-05T15:50:58.630 回答