Laravel 高级搜索查询修复
Laravel advanced search query fix
我有一个包含多个输入和 select 个框的搜索表单,我需要帮助来获取查询中的 if 条件,以便每个部分单独且同时工作。
这是我的 blade 代码:
<form action="{{route('advancesearch')}}" method="post">
{{csrf_field()}}
<div class="sidebar-title">
<span>Advanced Search</span>
<i class="fa fa-caret-down show_sidebar_content" aria-hidden="true"></i>
</div>
<!-- ./sidebar-title -->
<div id="tags-filter-content" class="sidebar-content">
<div class="filter-tag-group">
@foreach($options as $option)
<div class="tag-group">
<p class="title">
<span class="filter-title show_filter_content">{{$option->title}} <span class="pull-right"><i class="fa fa-minus"></i></span></span>
</p>
<div class="filter-content">
<div class="checkbox">
@foreach($option->suboptions as $suboption)
<label for="suboptions">
<input name="suboptions[]" type="checkbox" value="{{$suboption->id}}">
{{ucfirst($suboption->title)}}
</label>
@endforeach
</div>
</div>
</div>
@endforeach
<!-- ./tag-group -->
<div class="tag-group">
<p class="title">
<span class="filter-title show_filter_content">Brand <span class="pull-right"><i class="fa fa-minus"></i></span></span>
</p>
<div class="filter-content">
<div class="checkbox">
@foreach($brands as $brand)
<label for="brands">
<input name="brands[]" type="checkbox" value="{{$brand->id}}">
{{$brand->title}}
</label>
@endforeach
</div>
</div>
</div>
<!-- ./tag-group -->
<div class="tag-group">
<p class="title">
<span class="filter-title show_filter_content">Price Range <span class="pull-right"><i class="fa fa-minus"></i></span></span>
</p>
<div class="row filter-content">
<div class="col-md-6">
<div class="form-group">
<label for="min_price" hidden>Min</label>
<input type="text" name="min_price" class="form-control" placeholder="Rp Min">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="max_price" hidden>Max</label>
<input type="text" name="max_price" class="form-control" placeholder="Rp Max">
</div>
</div>
</div>
</div>
<!-- tag-group -->
<div class="text-center mt-20">
<button type="submit" class="btn btn-danger">TERPAKAN</button>
</div>
</div><!-- ./filter-tag-group -->
</div><!-- ./sidebar-content -->
</form>
这是我的路线:
Route::post('/advanced-search', 'frontend\SearchController@filter')->name('advancesearch');
最后我的函数代码是:
public function advancedsearch(Request $request) {
$brands = Brand::all(); // uses for other part of the page. (not related to search function)
$options = Option::all(); // uses for other part of the page. (not related to search function)
$suboptions = DB::table('product_suboption'); // where my product_id and subopyion_id saves
//search function
$products = Product::where(function($query){
//getting inputs
$suboptions2 = Input::has('suboptions') ? Input::get('suboptions') : [];
$min_price = Input::has('min_price') ? Input::get('min_price') : null;
$max_price = Input::has('max_price') ? Input::get('max_price') : null;
$brands2 = Input::has('brands') ? Input::get('brands') : [];
//returning results
$query->where('price','>=',$min_price)
->where('price','<=',$max_price);
})->get();
return view('front.advancesearch', compact('products', 'brands', 'options'));
}
我的模特关系:
product
型号:
public function options(){
return $this->belongsToMany(Option::class);
}
public function suboptions(){
return $this->belongsToMany(Suboption::class, 'product_suboption', 'product_id', 'suboption_id');
}
public function brand(){
return $this->belongsTo(Brand::class);
}
Option
型号:
public function suboptions(){
return $this->hasMany(Suboption::class, 'option_id');
}
public function products(){
return $this->belongsToMany(Product::class);
}
Suboption
型号:
public function option(){
return $this->belongsTo(Option::class, 'option_id');
}
public function products(){
return $this->belongsToMany(Product::class);
}
Brand
型号:
public function products(){
return $this->hasMany(Product::class);
}
注意
我的 brands
搜索来自产品 table,其中每个产品都有 brand_id
列。
但是
我的 suboptions
来自名为 product_suboption
的第 3 个 table(如您在我的模型代码中所见),我在其中保存了 product_id
和 suboption_id
。
这只是提供一个想法。您可以为查询使用多个 ->where()
和预加载 ->with()
。
看看下面这个查询:
$products = Product::where('price', '>=', $min_price) // you get the max and min price
->where('id', '<=', $max_price)->select('id')
->with([
"brand" => function ($query) {
$query->whereIn('id', $brand_ids); // [1, 2, 3,...]
},
"specifications" => function ($query) {
$query->where('some_column', '=', 'possible-value'); // single condition
},
"specifications.subspecifications" => function ($query) {
$query->where([
'some_column' => 'possible-value',
'another_column' => 'possible-value'
]); // you can also pass arrays of condition
}
])->get(); // This will return the products with the price set by the user
// Since we're just using ->with(), this will also return those products
// that doesn't match the other criteria specifications) so we
// still need to filter it.
最后,您可以过滤符合specifications
的产品,
- 带有空 specifications
的 product
表示此产品不符合标准,因此我们必须将其从 collection.
中删除
$filtered = $products->filter(function ($product, $key) {
return count($product->brand) > 0 && count($product->specifications) > 0;
// add your other boolean conditions here
});
dd($filtered->toArray()); // your filtered products to return
您可以使用 laravel orWhere
和 orWhereHas
分别并同时获得结果,假设您没有 select min_price
和 max_price
但您已经 select 编辑了 brand
那么所有具有此品牌名称的产品都应该是 return,您的查询将如下所示
$products = Product::orWhere('price','>=',$min_price)
->orWhere('price','<=',$max_price)
->orWhereHas('brand',function($query){
$query->whereIn('id', $brand_ids);
})
->orWhereHas('suboptions',function($query){
$query->whereIn('id', $suboptions_ids);
})
->orWhereHas('subspecifications',function($query){
$query->whereIn('id', $subspecifications_ids);
})->get();
$products
如果满足上述查询中所述的任何条件,将有产品集合。
希望对您有所帮助。
这是我的做法。请注意使用 when
来简化可选的 where 条件(也不需要设置变量),以及用于约束 whereHas
和 with
的闭包(如果你想预先加载关系)。
$products = Product::query()
->when($request->min_price, function ($query, $min_price) {
return $query->where('price', '>=', $min_price);
})
->when($request->max_price, function ($query, $max_price) {
return $query->where('price', '<=', $max_price);
})
->when($request->suboptions, function ($query, $suboptions) {
$suboptionsConstraint = function ($q) use ($suboptions) {
return $q->whereIn('id', $suboptions);
};
return $query->whereHas('suboptions', $suboptionsContraint)
->with(['suboptions' => $suboptionsContraint]);
})
->when($request->brands, function ($query, $brands) {
$brandsConstraint = function ($q) use ($brands) {
return $q->whereIn('id', $brands);
};
return $query->whereHas('brands', $brandsConstraint)
->with(['brands' => $brandsConstraint]);
});
我建议采用不同的方法。
在您的控制器上,将其更改为:
public function advancedsearch(Request $request) {
$suboptions2 = request->suboptions ? request->suboptions : null;
$min_price = request->min_price ? request->min_price : null;
$max_price = request->max_price ? request->max_price : null;
$brands2 = request->brands ? request->brands : null;
$query = Product::select('field_1', 'field_2', 'field_3')
->join('brands as b', 'b.id', '=', 'products.brand_id')
...(others joins);
// here we do the search query
if($suboptions2){
$query->where('suboptions_field', '=', $suboptions);
}
if($min_price && $max_price){
$query->where(function($q2) {
$q2->where('price', '>=', $min_price)
->where('price', '<=', $max_price)
});
}
if($brands2){
$query->where('products.brand_id', '=', $brands2);
}
// others queries
// finish it with this
$query->get();
return view('front.advancesearch', compact('products', 'brands', 'options'));
我发现这样做非常有用,因为它可以非常容易地实现额外的查询。
我建议你使用每个分隔符,它可以帮助你轻松地操作代码
作为您的典型条件,您的 sub_option 来自第三个 table 使用最后一个关系船。
if(count($request['suboptions'])) {
$product->whereHas('options',function($options) use ($request) {
$options->whereHas('suboptions',function($suboption)use($request) {
$suboption->whereIn('id',$request['suboptions']);
});
});
}
对于最低价最高价,我假设你的产品价格table
if(! empty($request['min_price'])) {
$product->where('price','>=',$request['min_price']);
}
if(! empty($request['max_price'])) {
$product->where('price','<=',$request['max_price']);
}
如您所说的品牌 brand_id 产品中的列 table 然后
if(count($request['brands'])) {
$product->whereIn('brand_id',$request['brands']);
}
这是我使用 laravel eloquent 进行多输入搜索的方法:
$input = Input::all(); //group all the inputs into single array
$product = Product::with('options','suboptions','brand');
//looping through your input to filter your product result
foreach ($input as $key => $value)
{
if ($value!='') {
if ($key == "max_price")
$product = $product->where('price','<=', $value);
elseif ($key == "min_price")
$product = $product->where('price','>=', $value);
elseif ($key == "brands")
$product = $product->whereIn('brand_id', $value); //assuming that your Input::get('brands') is in array format
elseif ($key == "suboptions")
$product = $product->whereIn('suboption_id', $value);
}
}
$product = $product->get();
如果没有提交输入,上面的方法将 return 所有产品,如果可用,将根据输入过滤结果,最重要的是,在输入之前通过验证清理输入也是一个好习惯继续查询
已解决
经过数周的代码尝试,我终于为自己得出了正确的结果(在我的例子中,它对其他人来说是这样工作的,也许与其他建议的答案一起工作)
public function advancedsearch(Request $request) {
$options = Option::all();
$brands = Brand::all();
$brandss = Input::has('brands') ? Input::get('brands') : [];
$suboption = Input::has('suboptions') ? (int)Input::get('suboptions') : [];
$min_price = Input::has('min_price') ? (int)Input::get('min_price') : null;
$max_price = Input::has('max_price') ? (int)Input::get('max_price') : null;
//codes
if(count($request['suboptions'])){
$products = DB::table('products')
->join('product_suboption', function ($join) {
$suboption = Input::has('suboptions') ? Input::get('suboptions') : [];
$join->on('products.id', '=', 'product_suboption.product_id')
->where('product_suboption.suboption_id', '=', $suboption);
})
->paginate(12);
}
elseif(count($request['brands'])){
$products = DB::table('products')
->whereIn('products.brand_id', $brandss)
->paginate(12);
}
elseif(count($request['min_price']) && count($request['max_price'])){
$products = DB::table('products')
->whereBetween('price', [$min_price, $max_price])
->paginate(12);
}
return view('front.advancesearch', compact('products', 'brands', 'options'));
}
NOTE: most of my pricing issues solved with (int)
as you see in my codes (int)Input::get('min_price')
and
(int)Input::get('max_price')
.
特别感谢 Ravindra Bhanderi 的 count($request['']
建议。
使用 treats 进行动态搜索非常简单,我们可以将其用于所有模型我尽可能地动态化
这是任何模型都可以使用的特征
此函数将删除项目中的代码重复
public function scopeSearch($query, $keyword, $columns = [], $relativeTables = [])
{
if (empty($columns)) {
$columns = array_except(
Schema::getColumnListing($this->table), $this->guarded
);
}
$query->where(function ($query) use ($keyword, $columns) {
foreach ($columns as $key => $column) {
$clause = $key == 0 ? 'where' : 'orWhere';
$query->$clause($column, "LIKE", "%$keyword%");
if (!empty($relativeTables)) {
$this->filterByRelationship($query, $keyword, $relativeTables);
}
}
});
return $query;
}
也过滤关系
private function filterByRelationship($query, $keyword, $relativeTables)
{
foreach ($relativeTables as $relationship => $relativeColumns) {
$query->orWhereHas($relationship, function($relationQuery) use ($keyword, $relativeColumns) {
foreach ($relativeColumns as $key => $column) {
$clause = $key == 0 ? 'where' : 'orWhere';
$relationQuery->$clause($column, "LIKE", "%$keyword%");
}
});
}
return $query;
}
我有一个包含多个输入和 select 个框的搜索表单,我需要帮助来获取查询中的 if 条件,以便每个部分单独且同时工作。
这是我的 blade 代码:
<form action="{{route('advancesearch')}}" method="post">
{{csrf_field()}}
<div class="sidebar-title">
<span>Advanced Search</span>
<i class="fa fa-caret-down show_sidebar_content" aria-hidden="true"></i>
</div>
<!-- ./sidebar-title -->
<div id="tags-filter-content" class="sidebar-content">
<div class="filter-tag-group">
@foreach($options as $option)
<div class="tag-group">
<p class="title">
<span class="filter-title show_filter_content">{{$option->title}} <span class="pull-right"><i class="fa fa-minus"></i></span></span>
</p>
<div class="filter-content">
<div class="checkbox">
@foreach($option->suboptions as $suboption)
<label for="suboptions">
<input name="suboptions[]" type="checkbox" value="{{$suboption->id}}">
{{ucfirst($suboption->title)}}
</label>
@endforeach
</div>
</div>
</div>
@endforeach
<!-- ./tag-group -->
<div class="tag-group">
<p class="title">
<span class="filter-title show_filter_content">Brand <span class="pull-right"><i class="fa fa-minus"></i></span></span>
</p>
<div class="filter-content">
<div class="checkbox">
@foreach($brands as $brand)
<label for="brands">
<input name="brands[]" type="checkbox" value="{{$brand->id}}">
{{$brand->title}}
</label>
@endforeach
</div>
</div>
</div>
<!-- ./tag-group -->
<div class="tag-group">
<p class="title">
<span class="filter-title show_filter_content">Price Range <span class="pull-right"><i class="fa fa-minus"></i></span></span>
</p>
<div class="row filter-content">
<div class="col-md-6">
<div class="form-group">
<label for="min_price" hidden>Min</label>
<input type="text" name="min_price" class="form-control" placeholder="Rp Min">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="max_price" hidden>Max</label>
<input type="text" name="max_price" class="form-control" placeholder="Rp Max">
</div>
</div>
</div>
</div>
<!-- tag-group -->
<div class="text-center mt-20">
<button type="submit" class="btn btn-danger">TERPAKAN</button>
</div>
</div><!-- ./filter-tag-group -->
</div><!-- ./sidebar-content -->
</form>
这是我的路线:
Route::post('/advanced-search', 'frontend\SearchController@filter')->name('advancesearch');
最后我的函数代码是:
public function advancedsearch(Request $request) {
$brands = Brand::all(); // uses for other part of the page. (not related to search function)
$options = Option::all(); // uses for other part of the page. (not related to search function)
$suboptions = DB::table('product_suboption'); // where my product_id and subopyion_id saves
//search function
$products = Product::where(function($query){
//getting inputs
$suboptions2 = Input::has('suboptions') ? Input::get('suboptions') : [];
$min_price = Input::has('min_price') ? Input::get('min_price') : null;
$max_price = Input::has('max_price') ? Input::get('max_price') : null;
$brands2 = Input::has('brands') ? Input::get('brands') : [];
//returning results
$query->where('price','>=',$min_price)
->where('price','<=',$max_price);
})->get();
return view('front.advancesearch', compact('products', 'brands', 'options'));
}
我的模特关系:
product
型号:
public function options(){
return $this->belongsToMany(Option::class);
}
public function suboptions(){
return $this->belongsToMany(Suboption::class, 'product_suboption', 'product_id', 'suboption_id');
}
public function brand(){
return $this->belongsTo(Brand::class);
}
Option
型号:
public function suboptions(){
return $this->hasMany(Suboption::class, 'option_id');
}
public function products(){
return $this->belongsToMany(Product::class);
}
Suboption
型号:
public function option(){
return $this->belongsTo(Option::class, 'option_id');
}
public function products(){
return $this->belongsToMany(Product::class);
}
Brand
型号:
public function products(){
return $this->hasMany(Product::class);
}
注意
我的 brands
搜索来自产品 table,其中每个产品都有 brand_id
列。
但是
我的 suboptions
来自名为 product_suboption
的第 3 个 table(如您在我的模型代码中所见),我在其中保存了 product_id
和 suboption_id
。
这只是提供一个想法。您可以为查询使用多个 ->where()
和预加载 ->with()
。
看看下面这个查询:
$products = Product::where('price', '>=', $min_price) // you get the max and min price
->where('id', '<=', $max_price)->select('id')
->with([
"brand" => function ($query) {
$query->whereIn('id', $brand_ids); // [1, 2, 3,...]
},
"specifications" => function ($query) {
$query->where('some_column', '=', 'possible-value'); // single condition
},
"specifications.subspecifications" => function ($query) {
$query->where([
'some_column' => 'possible-value',
'another_column' => 'possible-value'
]); // you can also pass arrays of condition
}
])->get(); // This will return the products with the price set by the user
// Since we're just using ->with(), this will also return those products
// that doesn't match the other criteria specifications) so we
// still need to filter it.
最后,您可以过滤符合specifications
的产品,
- 带有空 specifications
的 product
表示此产品不符合标准,因此我们必须将其从 collection.
$filtered = $products->filter(function ($product, $key) {
return count($product->brand) > 0 && count($product->specifications) > 0;
// add your other boolean conditions here
});
dd($filtered->toArray()); // your filtered products to return
您可以使用 laravel orWhere
和 orWhereHas
分别并同时获得结果,假设您没有 select min_price
和 max_price
但您已经 select 编辑了 brand
那么所有具有此品牌名称的产品都应该是 return,您的查询将如下所示
$products = Product::orWhere('price','>=',$min_price)
->orWhere('price','<=',$max_price)
->orWhereHas('brand',function($query){
$query->whereIn('id', $brand_ids);
})
->orWhereHas('suboptions',function($query){
$query->whereIn('id', $suboptions_ids);
})
->orWhereHas('subspecifications',function($query){
$query->whereIn('id', $subspecifications_ids);
})->get();
$products
如果满足上述查询中所述的任何条件,将有产品集合。
希望对您有所帮助。
这是我的做法。请注意使用 when
来简化可选的 where 条件(也不需要设置变量),以及用于约束 whereHas
和 with
的闭包(如果你想预先加载关系)。
$products = Product::query()
->when($request->min_price, function ($query, $min_price) {
return $query->where('price', '>=', $min_price);
})
->when($request->max_price, function ($query, $max_price) {
return $query->where('price', '<=', $max_price);
})
->when($request->suboptions, function ($query, $suboptions) {
$suboptionsConstraint = function ($q) use ($suboptions) {
return $q->whereIn('id', $suboptions);
};
return $query->whereHas('suboptions', $suboptionsContraint)
->with(['suboptions' => $suboptionsContraint]);
})
->when($request->brands, function ($query, $brands) {
$brandsConstraint = function ($q) use ($brands) {
return $q->whereIn('id', $brands);
};
return $query->whereHas('brands', $brandsConstraint)
->with(['brands' => $brandsConstraint]);
});
我建议采用不同的方法。
在您的控制器上,将其更改为:
public function advancedsearch(Request $request) {
$suboptions2 = request->suboptions ? request->suboptions : null;
$min_price = request->min_price ? request->min_price : null;
$max_price = request->max_price ? request->max_price : null;
$brands2 = request->brands ? request->brands : null;
$query = Product::select('field_1', 'field_2', 'field_3')
->join('brands as b', 'b.id', '=', 'products.brand_id')
...(others joins);
// here we do the search query
if($suboptions2){
$query->where('suboptions_field', '=', $suboptions);
}
if($min_price && $max_price){
$query->where(function($q2) {
$q2->where('price', '>=', $min_price)
->where('price', '<=', $max_price)
});
}
if($brands2){
$query->where('products.brand_id', '=', $brands2);
}
// others queries
// finish it with this
$query->get();
return view('front.advancesearch', compact('products', 'brands', 'options'));
我发现这样做非常有用,因为它可以非常容易地实现额外的查询。
我建议你使用每个分隔符,它可以帮助你轻松地操作代码
作为您的典型条件,您的 sub_option 来自第三个 table 使用最后一个关系船。
if(count($request['suboptions'])) {
$product->whereHas('options',function($options) use ($request) {
$options->whereHas('suboptions',function($suboption)use($request) {
$suboption->whereIn('id',$request['suboptions']);
});
});
}
对于最低价最高价,我假设你的产品价格table
if(! empty($request['min_price'])) {
$product->where('price','>=',$request['min_price']);
}
if(! empty($request['max_price'])) {
$product->where('price','<=',$request['max_price']);
}
如您所说的品牌 brand_id 产品中的列 table 然后
if(count($request['brands'])) {
$product->whereIn('brand_id',$request['brands']);
}
这是我使用 laravel eloquent 进行多输入搜索的方法:
$input = Input::all(); //group all the inputs into single array
$product = Product::with('options','suboptions','brand');
//looping through your input to filter your product result
foreach ($input as $key => $value)
{
if ($value!='') {
if ($key == "max_price")
$product = $product->where('price','<=', $value);
elseif ($key == "min_price")
$product = $product->where('price','>=', $value);
elseif ($key == "brands")
$product = $product->whereIn('brand_id', $value); //assuming that your Input::get('brands') is in array format
elseif ($key == "suboptions")
$product = $product->whereIn('suboption_id', $value);
}
}
$product = $product->get();
如果没有提交输入,上面的方法将 return 所有产品,如果可用,将根据输入过滤结果,最重要的是,在输入之前通过验证清理输入也是一个好习惯继续查询
已解决
经过数周的代码尝试,我终于为自己得出了正确的结果(在我的例子中,它对其他人来说是这样工作的,也许与其他建议的答案一起工作)
public function advancedsearch(Request $request) {
$options = Option::all();
$brands = Brand::all();
$brandss = Input::has('brands') ? Input::get('brands') : [];
$suboption = Input::has('suboptions') ? (int)Input::get('suboptions') : [];
$min_price = Input::has('min_price') ? (int)Input::get('min_price') : null;
$max_price = Input::has('max_price') ? (int)Input::get('max_price') : null;
//codes
if(count($request['suboptions'])){
$products = DB::table('products')
->join('product_suboption', function ($join) {
$suboption = Input::has('suboptions') ? Input::get('suboptions') : [];
$join->on('products.id', '=', 'product_suboption.product_id')
->where('product_suboption.suboption_id', '=', $suboption);
})
->paginate(12);
}
elseif(count($request['brands'])){
$products = DB::table('products')
->whereIn('products.brand_id', $brandss)
->paginate(12);
}
elseif(count($request['min_price']) && count($request['max_price'])){
$products = DB::table('products')
->whereBetween('price', [$min_price, $max_price])
->paginate(12);
}
return view('front.advancesearch', compact('products', 'brands', 'options'));
}
NOTE: most of my pricing issues solved with
(int)
as you see in my codes(int)Input::get('min_price')
and(int)Input::get('max_price')
.
特别感谢 Ravindra Bhanderi 的 count($request['']
建议。
使用 treats 进行动态搜索非常简单,我们可以将其用于所有模型我尽可能地动态化
这是任何模型都可以使用的特征
此函数将删除项目中的代码重复
public function scopeSearch($query, $keyword, $columns = [], $relativeTables = [])
{
if (empty($columns)) {
$columns = array_except(
Schema::getColumnListing($this->table), $this->guarded
);
}
$query->where(function ($query) use ($keyword, $columns) {
foreach ($columns as $key => $column) {
$clause = $key == 0 ? 'where' : 'orWhere';
$query->$clause($column, "LIKE", "%$keyword%");
if (!empty($relativeTables)) {
$this->filterByRelationship($query, $keyword, $relativeTables);
}
}
});
return $query;
}
也过滤关系
private function filterByRelationship($query, $keyword, $relativeTables)
{
foreach ($relativeTables as $relationship => $relativeColumns) {
$query->orWhereHas($relationship, function($relationQuery) use ($keyword, $relativeColumns) {
foreach ($relativeColumns as $key => $column) {
$clause = $key == 0 ? 'where' : 'orWhere';
$relationQuery->$clause($column, "LIKE", "%$keyword%");
}
});
}
return $query;
}