Eloquent multiple column selection with operators - laravel

When querying using Eloquent models we use
$modelInsatnce->where([$arrayContainingConditions])
to select rows filtered for multiple columns using = operator.
And for filtering with custom operators on single column we use,
$modelInsatnce->where($column,$operator,$value)
How can we filter for multiple columns using custom operators without chained calling?

The where function can take an array. From the Laravel API:
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
// If the column is an array, we will assume it is an array of key-value pairs
// and can add them each as a where clause. We will maintain the boolean we
// received when the method was called and pass it into the nested where.
if (is_array($column)) {
return $this->addArrayOfWheres($column, $boolean);
}
Then the addArrayOfWheres function:
protected function addArrayOfWheres($column, $boolean)
{
return $this->whereNested(function ($query) use ($column) {
foreach ($column as $key => $value) {
if (is_numeric($key) && is_array($value)) {
call_user_func_array([$query, 'where'], $value);
} else {
$query->where($key, '=', $value);
}
This looks for a numeric key with an array of values, and then calls ->where() with the array of values. If it were an associative array, it assumes = for the operator as you've said. But if you pass a standard array, it will call where on the query for each record, using the three array values as arguments:
$query->where([
['foo','!=',0],
['bar','<',5]
]);
Also note that you can pass and/or as the 4th argument:
$query->where([
['foo','!=',0],
['bar','<',5]
], null, null, 'or');
API link: https://github.com/laravel/framework/blob/5.2/src/Illuminate/Database/Query/Builder.php#L449

Jeff's answer was helpful, but it didn't work in my case.
So I moved 'or' to the fourth argument of inner array.
Tested on Laravel 5.5-5.6.
$query->where([
['foo', 'like', '%something%'],
['bar', '<', 5, 'or'] // <-- HERE
]);
It will result in:
WHERE (`foo` LIKE '%something%' OR `bar` < 5)

Related

Laravel - Query multiple objects to filter records by

I have a filter menu on the front end each which queries records based on a column filter which filters these records based on a column, operator and value entered. What I would like to do is pass an array of objects through to my API and filter by the number of filters applied from the frontend.
Example filter:
"{"fromDate":"","toDate":"","column":"date","operator":"=","value":"Rent"}"
Code to query filters:
return TransactionResource::collection(Transaction::query()
->when($filters->column !== 'date', function ($query) use ($filters) {
$query->where($filters->column, $filters->value);
})
->when($filters->column === 'date', function ($query) use ($filters) {
$query->whereBetween('date', [Carbon::parse($filters->fromDate)->format('Y-m-d'), Carbon::parse($filters->toDate)->format('Y-m-d')]);
})->paginate($pageLimit))->response();
What I would ideally like to do is pass in an array of filters as shown below, and loop through each object querying record by the filters applied.
"[
{"fromDate":"","toDate":"","column":"date","operator":"=","value":"Rent"},
{"fromDate":"2021-01-01","toDate":"2021-01-10","column":"date","operator":"","value":""}
]"
I've tried wrapping the query in a foreach loop without any luck, which doesn't work. I would greatly appreciate any guidance on how I might accomplish this.
Looping through - no records are returned
foreach($filters as $filter) {
TransactionResource::collection(
Transaction::query()
->when($filter->column !== 'date', function ($query) use ($filter) {
$query->where($filter->column, $filter->value);
})
->when($filter->column === 'date', function ($query) use ($filter) {
$query->whereBetween('date', [Carbon::parse($filter->fromDate)->format('Y-m-d'), Carbon::parse($filter->toDate)->format('Y-m-d')]);
})->paginate($pageLimit))->response();
}
You don't need to be using when here since you can easily use if and else statements since you are looping:
$query = Transaction::query();
foreach ($filters as $filter) {
if ($filter->column == 'date') {
$query->whereBetween('date', [Carbon::parse($filter->fromDate)->format('Y-m-d'), Carbon::parse($filter->toDate)->format('Y-m-d')]);
} else {
$query->where($filter->column, $filter->operator ?? '=', $filter->value);
}
}
return TransactionResource::collection($query->paginate(...));
Side note: when using when it can take 2 callbacks. The first is the one that is executed when the condition is true, the second is the one that is executed when the condition is false. So if you just need a 'if/else' you can do it with one when call.
Change response() to get() then above will work.

laravel more than one result from single query

I am trying to get all rows and distinct column from single query. but paginate method is only giving result but not pagination option like total prev next etc..
$offers = Offer::whereHas('users', function ($q) use ($authUser) {
$q->where('user_id', $authUser->parent_id);
$q->where('publisher_id', '=', $authUser->id);
});
and distinct column
$websites = $offers->distinct()->get(['website']);
with pivot columns (just wanted to show my full query)
$offers->orderBy($sortBy, $orderBy)->paginate($perPage)->map(function ($offer) {
if (!empty($offer->users)) {
$manager = $publisher = '';
foreach ($offer->users as $user) {
$manager = $user->pivot->user_id;
$publisher = $user->pivot->publisher_id;
}
$offer->manager = $manager;
$offer->publisher = $publisher;
}
return $offer;
});
Return
return response()->json([
'offers' => $offers,
'websites' => $websites
], 200);
hope my question will make sense.
Thanks.
You should run getCollection() before mapping to get the paginator's underlying collection.
(https://laravel.com/api/7.x/Illuminate/Pagination/LengthAwarePaginator.html#method_getCollection)
$offers->orderBy($sortBy, $orderBy)->paginate($perPage)
->getCollection()
->map(function ($offer) {
// ...
return $offer;
});
I'm answering based on it being $offers:
Your usage of map() is copying the modified results of your paginate() call to a new collection and that collection does not include the pagination information. That's why you no longer have pagination information.
Since there result of paginate() is already a usable collection, you can use each() instead of map() which will alter the objects in-place.

How to remove duplicate value between 2 array, Laravel?

I want to remove duplicated value between 2 arrays. How can I do?
1, 3, 4, 6 are duplicated in a both arrays, I want to unique values.
I using map() to show arrays of item_id contains of quantity but it's duplicate value, I don't want it.
$deliveries = $pickupsGroupByDepartment->first()->map(function ($q) {
return $q->deliveries->groupBy('delivery_date')->map(function($r) {
return $r->mapToGroups(function ($item) {
return [$item['item_id'] => $item['quantity']];
});
});
});
You can use array_unique function then combine it using array_merge function.
$array = array_unique (array_merge ($array1, $array2));
if your data is came from object in Laravel. you can use code below.
$result = $object1->merge($object2)->unique();
If the situation is dynamic data you can do this..
$results = [];
foreach($dynamicArray as $key => $array){
$results = array_unique (array_merge ($results, $array));
}
return $results;

Laravel firstOrCreate without Eloquent

Eloquent has a firstOrCreate method which gets a model based on a condition, or creates it if it doesn't exist.
Is there any equivalent method in Laravel's query builder (i.e. NOT in Eloquent)? For example:
$row = DB::table('users')->where('user_id', 5)->firstOrCreate('name' => 'Peter', 'last_name' => 'Pan');
That would try to get a row from users with 'user_id'==5. If it doesn't exist, it would insert a row with that id number, plus the other mentioned fields.
EDIT: I'm not trying to apply my question with users. I used users as an example to make as clear as possible what I'm looking for.
updateOrInsert function with empty values give me the result like firstOrCreate
Nope, Laravel firstOrCreate is function, that says next:
public function firstOrCreate(array $attributes, array $values = [])
{
if (! is_null($instance = $this->where($attributes)->first())) {
return $instance;
}
return tap($this->newModelInstance($attributes + $values), function ($instance) {
$instance->save();
});
}
But you can add it with query micro:
DB::query()->macro('firstOrCreate', function (array $attributes, array $values = [])
{
if ($record = $this->first()) {
// return model instance
}
// create model instance
});
So than you will be able to call it same way you do with Eloquent.
$record= DB::table('records')->where('alias', $alias)->firstOrFail();
Yeah of course! Just use normal SQL and ->selectRaw( your conditions ) and look for if there is a entry where your specifications are.
https://laravel.com/docs/5.7/queries#raw-expressions

Pluck with multiple columns?

When i use pluck with multiple columns i get this:
{"Kreis 1 \/ Altstadt":"City","Kreis 2":"Enge","Kreis 3":"Sihifeld","Kreis 4":"Hard","Kreis 5 \/ Industriequartier":"Escher Wyss","Kreis 6":"Oberstrass","Kreis 7":"Witikon","Kreis 8 \/ Reisbach":"Weinegg","Kreis 9":"Altstetten","Kreis 10":"Wipkingen","Kreis 11":"Seebach","Kreis 12 \/ Schwamendingen":"Hirzenbach"
But i need this?
["Rathaus","Hochschulen","Lindenhof","City","Wollishofen","Leimbach","Enge","Alt-Wiedikon","Friesenberg","Sihifeld","Werd","Langstrasse","Hard","Gewerbechule","Escher Wyss","Unterstrass","Oberstrass","Fluntern","Hottingen","Hirslanden","Witikon","Seefeld","M\u00fchlebach","Weinegg","Albisrieden","Altstetten","H\u00f6ngg","Wipkingen","Affoltern","Oerlikon","Seebach","Saatlen","Schwamendingen-Mitte","Hirzenbach"]
Any suggestion how can i do that? This is my method:
public function autocomplete_districts(Request $request)
{
$district = $request->input('query');
// $ass = /DB::table('districts')->select(array('district', 'region'))->get();
// dd($ass);
$data = Districts::whereRaw('LOWER(district) like ?', [strtolower('%'.$district . '%')])->orWhereRaw('LOWER(region) like ?', [strtolower('%'.$district . '%')])->pluck('region','district');
return response()->json($data);
}
You should use select() with get() and then later on modify the object as you need.
So instead of: ->pluck('region','district');
use: ->select('region','district')->get();
pluck() is advised when you need value of one column only.
And as far as possible, you should have your models singular form not plural (Districts) - to follow Laravel nomenclature.
Cos that is how pluck works. Instead try this.
$data = Districts::whereRaw('LOWER(district) like ?', [strtolower('%'.$district . '%')])->orWhereRaw('LOWER(region) like ?', [strtolower('%'.$district . '%')])->select('region', 'district')->get();
$data = collect($data->toArray())->flatten()->all();
In my case I wanted to pluck 2 values from an array of Eloquent models and this worked:
$models->map->only(['state', 'note'])->values()
That's shorter version of
$models->map(fn($model) => $model->only(['state', 'note']))->values()
This is an issue I constantly have faced and has led me to create the following solution that can be used on models or arrays.
There is also support for dot syntax that will create a multidimensional array as required.
Register this macro within the AppServiceProvider (or any provider of your choice):
use Illuminate\Support\Arr;
/**
* Similar to pluck, with the exception that it can 'pluck' more than one column.
* This method can be used on either Eloquent models or arrays.
* #param string|array $cols Set the columns to be selected.
* #return Collection A new collection consisting of only the specified columns.
*/
Collection::macro('pick', function ($cols = ['*']) {
$cols = is_array($cols) ? $cols : func_get_args();
$obj = clone $this;
// Just return the entire collection if the asterisk is found.
if (in_array('*', $cols)) {
return $this;
}
return $obj->transform(function ($value) use ($cols) {
$ret = [];
foreach ($cols as $col) {
// This will enable us to treat the column as a if it is a
// database query in order to rename our column.
$name = $col;
if (preg_match('/(.*) as (.*)/i', $col, $matches)) {
$col = $matches[1];
$name = $matches[2];
}
// If we use the asterisk then it will assign that as a key,
// but that is almost certainly **not** what the user
// intends to do.
$name = str_replace('.*.', '.', $name);
// We do it this way so that we can utilise the dot notation
// to set and get the data.
Arr::set($ret, $name, data_get($value, $col));
}
return $ret;
});
});
This can then be used in the following way:
$a = collect([
['first' => 1, 'second' => 2, 'third' => 3],
['first' => 1, 'second' => 2, 'third' => 3]
]);
$b = $a->pick('first', 'third'); // returns [['first' => 1, 'third' => 3], ['first' => 1, 'third' => 3]]
Or additionally, on any models you may have:
$users = User::all();
$new = $users->pick('name', 'username', 'email');
// Might return something like:
// [
// ['name' => 'John Doe', 'username' => 'john', 'email' => 'john#email.com'],
// ['name' => 'Jane Doe', 'username' => 'jane', 'email' => 'jane#email.com'],
// ['name' => 'Joe Bloggs', 'username' => 'joe', 'email' => 'joe#email.com'],
// ]
It is also possible to reference any relationship too using the dot notation, as well as using the as [other name] syntax:
$users = User::all();
$new = $users->pick('name as fullname', 'email', 'posts.comments');
// Might return something like:
// [
// ['fullname' => 'John Doe', 'email' => 'john#email.com', 'posts' => [...]],
// ['fullname' => 'Jane Doe', 'email' => 'jane#email.com', 'posts' => [...]],
// ['fullname' => 'Joe Bloggs', 'email' => 'joe#email.com', 'posts' => [...]],
// ]
My solution in LARAVEL 5.6:
Hi, I've just had the same problem, where I needed 2 columns combined in 1 select list.
My DB has 2 columns for Users: first_name and last_name.
I need a select box, with the users full name visible and the id as value.
This is how I fixed it, using the pluck() method:
In the User model I created a full name accessor function:
public function getNameAttribute() {
return ucwords($this->last_name . ' ' . $this->first_name);
}
After that, to fill the select list with the full name & corresponding database id as value, I used this code in my controller that returns the view (without showing users that are archived, but you can change the begin of the query if you like, most important are get() and pluck() functions:
$users = User::whereNull('archived_at')
->orderBy('last_name')
->get(['id','first_name','last_name'])
->pluck('name','id');
return view('your.view', compact('users'));
Now you can use the $users in your select list!
So first, you GET all the values from DB that you will need,
after that you can use any accessor attribute defined for use in your PLUCK method,
as long as all columns needed for the accessor are in the GET ;-)
As far as now Laravel didn't provide such macro to pick specific columns, but anyway Laravel is out of the box and lets us customize almost everything.
Tested in Laravel 8.x
in AppServiceProvider.php
use Illuminate\Support\Collection;
// Put this inside boot() function
Collection::macro('pick', function (... $columns) {
return $this->map(function ($item, $key) use ($columns) {
$data = [];
foreach ($columns as $column) {
$data[$column] = $item[$column] ?? null;
}
return $data;
});
});
Usage
$users = App\Models\User::all();
$users->pick('id','name');
// Returns: [['id' => 1, 'name' => 'user_one'],['id' => 2, 'name' => 'user_two']]
Important notes:
Do not use this macro for a really HUGE collection (You better do it on Eloquent
select or MySQL query select)
Laravel: To pluck multi-columns in the separate arrays use the following code.
$Ads=Ads::where('status',1);
$Ads=$Ads->where('created_at','>',Carbon::now()->subDays(30));
$activeAdsIds=$Ads->pluck('id'); // array of ads ids
$UserId=$Ads->pluck('user_id'); // array of users ids
I have created the model scope
More about scopes:
https://laravel.com/docs/5.8/eloquent#query-scopes
https://medium.com/#janaksan_/using-scope-with-laravel-7c80dd6a2c3d
Code:
/**
* Scope a query to Pluck The Multiple Columns
*
* This is Used to Pluck the multiple Columns in the table based
* on the existing query builder instance
*
* #author Manojkiran.A <manojkiran10031998#gmail.com>
* #version 0.0.2
* #param \Illuminate\Database\Eloquent\Builder $query
* #param string $keyColumn the columns Which is used to set the key of array
* #param array $extraFields the list of columns that need to plucked in the table
* #return \Illuminate\Support\Collection
* #throws Illuminate\Database\QueryException
**/
public function scopePluckMultiple( $query, string $keyColumn, array $extraFields):\Illuminate\Support\Collection
{
//pluck all the id based on the query builder instance class
$keyColumnPluck = $query->pluck( $keyColumn)->toArray();
//anonymous callback method to iterate over the each fileds of table
$callBcakMethod = function ($eachValue) use ($query)
{
$eachQuery[$eachValue] = $query->pluck( $eachValue)->toArray();
return $eachQuery;
};
//now we are collapsing the array single time to get the propered array
$extraFields = \Illuminate\Support\Arr::collapse( array_map($callBcakMethod, $extraFields));
// //iterating Through All Other Fields and Plucking it each Time
// foreach ((array)$extraFields as $eachField) {
// $extraFields[$eachField] = $query->pluck($eachField)->toArray();
// }
//now we are done with plucking the Required Columns
//we need to map all the values to each key
//get all the keys of extra fields and sets as array key or index
$arrayKeys = array_keys($extraFields);
//get all the extra fields array and mapping it to each key
$arrayValues = array_map(
function ($value) use ($arrayKeys) {
return array_combine($arrayKeys, $value);
},
call_user_func_array('array_map', array_merge(
array(function () {
return func_get_args();
}),
$extraFields
))
);
//now we are done with the array now Convert it to Collection
return collect( array_combine( $keyColumnPluck, $arrayValues));
}
So now the testing part
BASIC EXAMPLE
$basicPluck = Model::pluckMultiple('primaryKeyFiles',['fieldOne', 'FieldTwo']);
ADVANCED EXAMPLE
$advancedPlcuk = Model::whereBetween('column',[10,43])
->orWhere('columnName','LIKE', '%whildCard%')
->Where( 'columnName', 'NOT LIKE', '%whildCard%')
->pluckMultiple('primaryKeyFiles',['fieldOne', 'FieldTwo']);
But it returns the \Illuminate\Support\Collection, so if you need to convert to array
$toArrayColl = $advancedPluck->toArray();
if you need to convert to json
$toJsonColl = $advancedPluck->toJson();
To answer the specific question of "how to return multiple columns using (something like) pluck" we have to remember that Pluck is a Collection member function. So if we're sticking to the question being asked we should stick with a Collection based answer (you may find it more beneficial to develop a model-based solution, but that doesn't help solve the question as posed).
The Collection class offers the "map" member function which can solve the posed question:
$data = Districts::whereRaw('LOWER(district) like ?', [strtolower('%'.$district . '%')])->orWhereRaw('LOWER(region) like ?', [strtolower('%'.$district . '%')])
->map(function ($item, $key, $columns=['region','district']) {
$itemArray = [];
foreach($columns as $column){
$itemArray[$column] = $item->$column;
}
return ($itemArray);
});
dd($data);
This should give you a collection where each element is a 2 element array indexed by 'region' and 'district'.
Laravel 8.x, try to use mapWithKeys method instead of pluck, for example:
$collection->mapWithKeys(function ($item, $key) {
return [$key => $item['firstkey'] . ' ' . $item['secondkey']];
});
Expanding on #Robby_Alvian_Jaya_Mulia from above who gave me the idea. I needed it to also work on a relationship. This is just for a single relationship, but it would probably be easy to nest it more.
This needs to be put into AppServiceProvider.php
use Illuminate\Support\Collection;
// Put this inside boot() function
Collection::macro('pick', function (... $columns) {
return $this->map(function ($item, $key) use ($columns) {
$data = [];
foreach ($columns as $column) {
$collection_pieces = explode('.', $column);
if (count($collection_pieces) == 2) {
$data[$collection_pieces[1]] = $item->{$collection_pieces[0]}->{$collection_pieces[1]} ?? null;
} else {
$data[$column] = $item[$column] ?? null;
}
}
return $data;
});
});
Usage:
$users = App\Models\User::has('role')->with('role')->all();
$users->pick('id','role.name');
// Returns: [['id' => 1, 'name' => 'role_name_one'],['id' => 2, 'name' => 'role_name_two']]
Hope this is helpful to someone. Sorry I didn't add this to under #Robby's answer. I didn't have enough reputation.
Pluck returned only the value of the two columns which wasnt ideal for me, what worked for me was this :
$collection->map->only(['key1', 'key2'])->values()

Resources