如何从字段列表创建动态查询?

时间:2014-10-27 18:01:01

标签: php mysql dynamicquery

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

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

  1. field_name(字段名称“ie.count_id,account_name ....”)
  2. display_label(该字段应如何转换为使用方式“ie。帐户ID,名称”)
  3. table_name(此字段属于“ie。accounts”的表)
  4. related_to(与其他表格的字段关系“如果有的话。”默认值为NULL
  5. related_to_field(指向“如果有的话”字段。默认值为NULL
  6. 以下是示例数据 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. “显示结果”按钮。
    5. 该表单的第一部分将显示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
      
      查询完成后

      将执行。

      或者,如果用户选择了“名称,销售”。然后,用户想要在列daily_sales上应用SUM函数。最后,用户选择了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查询?

      提前感谢您的帮助和时间。

3 个答案:

答案 0 :(得分:0)

将构建sql查询视为一个简单的字符串构建练习。当您引用field_relations表时,从上一页的表单中发布的值将标识您在查询中需要的列和表。

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

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

答案 1 :(得分: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

答案 2 :(得分:0)

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

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

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