在模型和相关模型的字段中使用orWhere进行查询的多个位置

时间:2014-07-10 23:14:54

标签: php laravel-4 eloquent

我有很多表(面向用户,而不是SQL),每列有过滤框。这些专栏不仅包括表格。字段,但相关模型的领域。

我试图添加一个功能,在过滤数据时可以使用逗号作为OR分隔符。我需要这样做,因为我有相当多的表,字段和关系。另外一些表是动态构建的。

为模型自己的字段添加where子句的代码是:

foreach ($filters as $column => $filter) {
    $q->where(function ($q) use ($filter, $model, $column) {
        $first = array_pop($filter);
        $q->where("$model.$column", 'LIKE', "%$first%");
        foreach ($filter as $or) {
            $q->orWhere("$model.$column", 'LIKE', "%$or%");
        }
    });
}

这可以按预期工作。当尝试用关系字段做同样的事情时,唯一的区别是使用whereHas并包括模型:

foreach ($filters as $column => $filter) {
    $q->whereHas($model, function ($q) use ($filter, $model, $column) {
        $first = array_pop($filter);
        $q->where("$model.$column", 'LIKE', "%$first%");
        foreach ($filter as $or) {
            $q->orWhere("$model.$column", 'LIKE', "%$or%");
        }
    });
}

问题在于,如果我过滤关系字段,它就不会按预期工作。我怀疑它与whereHas有一个sub where / orWhere子句。这是我得到的输出

No filters:
|------------+-----------------------------------+--------------------------------------|
| Contact ID | Type (Field in contact SQL table) | Ecozone (Field in Ecozone SQL table) |
|------------+-----------------------------------+--------------------------------------|
|  Filter -> |                                   |                                      |
|------------+-----------------------------------+--------------------------------------|
|          1 | Manager                           | Bush                                 |
|          2 |                                   | Forest                               |
|          3 | Worker                            |                                      |
|------------+-----------------------------------+--------------------------------------|

Single filter on current model (correct):
|------------+-----------------------------------+--------------------------------------|
| Contact ID | Type (Field in contact SQL table) | Ecozone (Field in Ecozone SQL table) |
|------------+-----------------------------------+--------------------------------------|
|  Filter -> | man                               |                                      |
|------------+-----------------------------------+--------------------------------------|
|          1 | Manager                           | Bush                                 |
|------------+-----------------------------------+--------------------------------------|

Multiple filter on current model (correct):
|------------+-----------------------------------+--------------------------------------|
| Contact ID | Type (Field in contact SQL table) | Ecozone (Field in Ecozone SQL table) |
|------------+-----------------------------------+--------------------------------------|
|  Filter -> | man, wor                          |                                      |
|------------+-----------------------------------+--------------------------------------|
|          1 | Manager                           | Bush                                 |
|          3 | Worker                            |                                      |
|------------+-----------------------------------+--------------------------------------|

Single filter on related model field (correct):
|------------+-----------------------------------+--------------------------------------|
| Contact ID | Type (Field in contact SQL table) | Ecozone (Field in Ecozone SQL table) |
|------------+-----------------------------------+--------------------------------------|
|  Filter -> |                                   | bus                                  |
|------------+-----------------------------------+--------------------------------------|
|          1 | Manager                           | Bush                                 |
|------------+-----------------------------------+--------------------------------------|

Multiple filter on related model field (incorrect):
|------------+-----------------------------------+--------------------------------------|
| Contact ID | Type (Field in contact SQL table) | Ecozone (Field in Ecozone SQL table) |
|------------+-----------------------------------+--------------------------------------|
|  Filter -> |                                   | bus,for                              |
|------------+-----------------------------------+--------------------------------------|
|          1 | Manager                           | Bush                                 |
|          2 |                                   | Forest                               |
|          3 | Worker                            |                                      |
|------------+-----------------------------------+--------------------------------------|

整个代码路径如下:

在控制器中:

return Contacts::with(
   'ecozone'
)->where( function ($q) {
    $this->set_filters($q, 'contact');
})->paginate($count);

在BaseController中:

protected function set_filters($q, $current_model) {

    $filters_array = $this->parse_filters();

    if ($filters_array) {
        foreach($filters_array as $model => $filters) {
            if ($model == $current_model) {
                foreach($filters as $column => $filter) {
                    $q->where(function ($q) use ($filter, $model, $column) {
                        $first = array_pop($filter);
                        $q->where("$model.$column", 'LIKE', "%$first%");
                        foreach($filter as $or) {
                            $q->orWhere("$model.$column", 'LIKE', "%$or%");
                        }
                    });
                }
            } else {
                foreach($filters as $column => $filter) {
                    $q->whereHas($model, function ($q) use ($filter, $model, $column) {
                        $first = array_pop($filter);
                        $q->where("$model.$column", 'LIKE', "%$first%");
                        foreach($filter as $or) {
                            $q->orWhere("$model.$column", 'LIKE', "%$or%");
                        }
                    });
                }
            }
        }
    }
}

$ this->过滤器是:

protected function parse_filters() {
    $filters = Input::get('filters');
    $filters = json_decode($filters);

    $filters = array_where($filters, function($key, $value) {
        return !empty($value);
    });
    $filters = (Array) $filters;
    $has_filters = !empty($filters);

    if ($has_filters) {
        $filters_array = [];
        foreach ($filters as $key => $value) {
            $value = explode(',', $value);
            array_set($filters_array, $key, $value);
        }
    } else {
        $filters_array = false;
    }

    return $filters_array;
}

返回,我认为是正确的,以下数组。第一级是模型,第二级是字段,第三级是逗号分隔OR子句

Array (
    [ecozone] => Array
        (
            [ecozone] => Array
                (
                    [0] => bush
                    [1] => forest
                )

        )

    [contact] => Array
        (
            [contact_type] => Array
                (
                    [0] => manager
                    [1] => worker
                )

        )

)

在对当前模型字段进行过滤时使用的SQL转储。这按预期工作:

array (size=4)
  0 => 
    array (size=3)
      'query' => string 'select count(*) as aggregate from `contact` where `contact`.`deleted_at` is null and ((`contact`.`physical_address` LIKE ? or `contact`.`physical_address` LIKE ?))' (length=163)
      'bindings' => 
        array (size=2)
          0 => string '%add%' (length=5)
          1 => string '%nana%' (length=6)
      'time' => float 1.67
  1 => 
    array (size=3)
      'query' => string 'select * from `contact` where `contact`.`deleted_at` is null and ((`contact`.`physical_address` LIKE ? or `contact`.`physical_address` LIKE ?)) limit 10 offset 0' (length=161)
      'bindings' => 
        array (size=2)
          0 => string '%add%' (length=5)
          1 => string '%nana%' (length=6)
      'time' => float 0.91
  2 => 
    array (size=3)
      'query' => string 'select * from `ecozone` where `ecozone`.`deleted_at` is null and `ecozone`.`id` in (?, ?)' (length=89)
      'bindings' => 
        array (size=2)
          0 => string '1' (length=1)
          1 => string '2' (length=1)
      'time' => float 0.8

由相关模型字段过滤的SQL查询转储。这没有按预期工作:

array (size=4)
  0 => 
    array (size=3)
      'query' => string 'select count(*) as aggregate from `contact` where `contact`.`deleted_at` is null and ((select count(*) from `ecozone` where `ecozone`.`deleted_at` is null and `contact`.`ecozone_id` = `ecozone`.`id` and `ecozone`.`ecozone` LIKE ? or `ecozone`.`ecozone` LIKE ? and `ecozone`.`deleted_at` is null) >= 1)' (length=301)
      'bindings' => 
        array (size=2)
          0 => string '%grass%' (length=7)
          1 => string '%bush%' (length=6)
      'time' => float 1.18
  1 => 
    array (size=3)
      'query' => string 'select * from `contact` where `contact`.`deleted_at` is null and ((select count(*) from `ecozone` where `ecozone`.`deleted_at` is null and `contact`.`ecozone_id` = `ecozone`.`id` and `ecozone`.`ecozone` LIKE ? or `ecozone`.`ecozone` LIKE ? and `ecozone`.`deleted_at` is null) >= 1) limit 10 offset 0' (length=299)
      'bindings' => 
        array (size=2)
          0 => string '%grass%' (length=7)
          1 => string '%bush%' (length=6)
      'time' => float 0.89
  2 => 
    array (size=3)
      'query' => string 'select * from `ecozone` where `ecozone`.`deleted_at` is null and `ecozone`.`id` in (?, ?)' (length=89)
      'bindings' => 
        array (size=2)
          0 => string '1' (length=1)
          1 => string '2' (length=1)
      'time' => float 1.38

1 个答案:

答案 0 :(得分:2)

您需要为所有过滤器集使用类似的内容:

`where(function($q){$q->where()->orWhere();})` 

将那些内部wheres包裹在(...)中,因为添加简单orWhere会搞乱整个查询:

// your 2nd query in the incorrect array:
select * from `contact` where `contact`.`deleted_at` is null and
  ((select count(*) from `ecozone` where `ecozone`.`deleted_at` is null 
    and `contact`.`ecozone_id` = `ecozone`.`id` 
    // this is your first where()
    and `ecozone`.`ecozone` LIKE ? 
    // and this is orWhere, which make the query wrong
    or `ecozone`.`ecozone` LIKE ? and `ecozone`.`deleted_at` is null)
  >= 1) limit 10 offset 0

您已针对顶级过滤器执行了此操作,但未在whereHas

中执行此操作

所以看起来应该是这样的:

} else {
    foreach($filters as $column => $filter) {
        $q->whereHas($model, function ($q) use ($filter, $model, $column) {
            $q->where(function ($q) use ($filter, $model, $column) {
               $first = array_pop($filter);
               $q->where("$model.$column", 'LIKE', "%$first%");
               foreach($filter as $or) {
                   $q->orWhere("$model.$column", 'LIKE', "%$or%");
               }
            });
        });
    }
}