Eloquent - where not equal to - laravel

I'm currently using the latest Laravel version.
I've tried the following queries:
Code::where('to_be_used_by_user_id', '<>' , 2)->get()
Code::whereNotIn('to_be_used_by_user_id', [2])->get()
Code::where('to_be_used_by_user_id', 'NOT IN', 2)->get()
Ideally, it should return all records except user_id = 2, but it returns blank array. How do I tackle this?
Code::all()
This returns all 4 records.
Code model:
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class Code extends Model
{
protected $fillable = ['value', 'registration_id', 'generated_for_user_id', 'to_be_used_by_user_id', 'code_type_id', 'is_used'];
public function code_type()
{
return $this->belongsTo('App\CodeType');
}
}

Use where with a != operator in combination with whereNull
Code::where('to_be_used_by_user_id', '!=' , 2)->orWhereNull('to_be_used_by_user_id')->get()

For where field not empty this worked for me:
->where('table_name.field_name', '<>', '')

While this seems to work
Code::query()
->where('to_be_used_by_user_id', '!=' , 2)
->orWhereNull('to_be_used_by_user_id')
->get();
you should not use it for big tables, because as a general rule "or" in your where clause is stopping query to use index. You are going from "Key lookup" to "full table scan"
Instead, try Union
$first = Code::whereNull('to_be_used_by_user_id');
$code = Code::where('to_be_used_by_user_id', '!=' , 2)
->union($first)
->get();

Or like this:
Code::whereNotIn('to_be_used_by_user_id', [2])->get();

Fetching data with either null and value on where conditions are very tricky. Even if you are using straight Where and OrWhereNotNull condition then for every rows you will fetch both items ignoring other where conditions if applied. For example if you have more where conditions it will mask out those and still return with either null or value items because you used orWhere condition
The best way so far I found is as follows. This works as where (whereIn Or WhereNotNull)
Code::where(function ($query) {
$query->where('to_be_used_by_user_id', '!=' , 2)->orWhereNull('to_be_used_by_user_id');
})->get();

Here's a useful scope method that you can add to your model and use it for different columns.
/**
* #param Builder $query
* #param string $field
* #param $value
* #return Builder
*/
public function scopeWhereNot(Builder $query, string $field, $value): Builder
{
return $value === null
? $query->whereNotNull($field)
: $query->where(function ($q) use ($field, $value) {
return $q->where($field, '<>', $value)
->orWhereNull($field);
});
}
and use it as follows
Code::whereNot('to_be_used_by_user_id', 2)->get()

Related

Laravel: pass an argument to the relationship function (not the query callback function) when using whereHas

I would like to know how to pass an argument to a model relationship function. Just to be clear, I'm NOT talking about the query callback.
Consider a model like so:
class UserRelationships extends Model
{
// other stuff
// dynamic scope:
/**
* Scope a query to only include users of a given type.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param mixed $type
* #return \Illuminate\Database\Eloquent\Builder
*/
// $relationships = UserRelationships::at( Carbon::parse('2022-10-10') )->get();
public function scopeAt($query, Carbon $date)
{
return $query->where('superseded_at', '>', $date )
->where('created_at', '<=', $date );
}
}
And a related model featuring the following relationships:
class User extends Authenticatable
{
public function progenial_relation(Carbon $date=null) // returns this user record in the userRelationships table, which can be used to retrieve this users parent (directly lookup the sponsor_id)
// when eager loading, this is useful for getting all users whose parent is x, hence the name
{
return $this->hasOne(UserRelationships::class, 'user_id', 'id')
->at( #$date ?: Carbon::now() ) // local dynamic scope in userRelationships
->orderByDesc('created_at')
->limit(1);
}
public function parental_relation(Carbon $date=null) // returns records from the userRelationships table, of all the users which refer to this user as their sponsor
// when eager loading, this is useful for getting the user whose child is x, hence the name
{
return $this->hasMany(UserRelationships::class, 'sponsor_id', 'id')
->at( #$date ?: Carbon::now() ); // local dynamic scope in userRelationships
}
}
As you can see my relationships accept an argument (the date).
Now, if you wanted to use those relationships straightforwardly like so, there's no issues:
$date = Carbon\Carbon::parse('2022-06-01');
$relations_at_date = User::find(1)->parental_relation( $date )->get();
But what happens if you need to use eager-loading methods such as has(), whereHas(), doesntHave(), whereDoesntHave()?
How do you pass an argument to the relationship? For example, I wanted to add other relationships to my User model.
public function children(Carbon $date=null)
{
$date = #$date ?: Carbon::now();
return self::whereHas('progenial_relation', function($q) {
$q->where('sponsor_id', $this->id);
}, $date); // not working
}
I tried with these syntax, but it doesn't work:
whereHas( 'relationship_name', $callback, $argument )
whereHas( 'relationship_name', $argument, $callback )
whereHas( 'relationship_name', [$argument], $callback )
whereHas( 'relationship_name', $callback, [$argument] )
Is it somehow possible?
Are there any alternatives?
For completeness I'm going to add what happens if I use a normal closure:
public function children(Carbon $date=null)
{
$date = #$date ?: Carbon::now();
return self::whereHas('progenial_relation', function($q) use ($date) {
$q->at($date)->where('sponsor_id', $this->id);
});
}
This is the resulting SQL. As you can see the constraints are applied twice. Once by the query callback and once by the relationship. But since I cannot pass the correct argument to the relationship, it gets the default one. The 2 constraints collide and the query does not work.
"select * from `users`
where exists (
select *
from `user_relationships`
where `users`.`id` = `user_relationships`.`user_id`
and `user_relationships`.`superseded_at` > ?
and `user_relationships`.`created_at` <= ?
and `sponsor_id` = ?
and `user_relationships`.`superseded_at` > ?
and `user_relationships`.`created_at` <= ?
)
and `users`.`deleted_at` is null"
I don't think that its possible to pass variables to relationship methods when eager-loading like this.
But you can apply a sub-query to the wherehas:
$date = #$date ?: Carbon::now();
return self::whereHas('progenial_relation', function($q) use ($date) {
$q
->where('sponsor_id', $this->id)
->at( #$date ?: Carbon::now() );
}, $date);
Although I'm not sure what the ->at method/scope you added does.

Laravel eloquent whereIn with one query

In laravel, I can use many where rules as an array a run one query to the database using laravel eloquent where method to get needed result.
Code:
$where = [];
$where[] = ['user_id', '!=', null];
$where[] = ['updated_at', '>=', date('Y-m-d H:i:s')];
if($request->searchTerm) {
$where[] = ['title', 'like', '%' . $request->searchTerm . '%'];
}
Model::where($where)->get();
Question part:
Now I need to use Laravel Eloquent method whereIn with array params to get needed result with one query.
I tried by looping method but with many queries to the database.
Code:
$model = new Model;
$whereIn = [];
$whereIn[] = ['date', '>=', Carbon::now()->subDays(10)];
$whereIn[] = ['user_role', 'candidate'];
if (!empty($scope) && is_array($scope)) {
$whereIn[] = ['user_id', $scope];
}
if(is_array($employment) && !empty($employment)) {
$whereIn[] = ['employment', $employment];
}
if(is_array($experience) && !empty($experience)) {
$whereIn[] = ['experience', $experience];
}
foreach ($whereIn as $v) {
$model = $model->whereIn($v[0], $v[1]);
}
dump($model->get());
First I tired $model->whereIn($whereIn)->get() but it's return error. It's possible get results with one query using whereIn without looping?
Note: My $whereIn array will be dynamic array!
whereIn is a query builder function so you can't use it on the model directly. Instead you should create a query builder instance. I also suggest you use when instead of the if statements:
$models = Model::when(!empty($scope) && is_array($scope), function ($query) use ($scope) {
$query->whereIn('user_id', $scope);
})->when(!empty($employment) && is_array($employment), function ($query) use ($employment) {
$query->whereIn('employment', $employment);
})->when(!empty($experience) && is_array($experience), function ($query) use ($experience) {
$query->whereIn('experience', $experience);
})->get();
dump($models);
when essentially runs the function when the first parameter is true. There's more detail in the documentation under conditional clauses.
Since your $whereIn variable is an array of arrays it will work like :
$model->whereIn($whereIn[0][0], $whereIn[0][1])->get();
If it just a simple array then you can use :
$model->whereIn($whereIn[0], $whereIn[1])->get();
Do in Eloquent
$model = Model::whereIn('id', array(1, 2, 3))->get();
Or using Query builder then :
$model = DB::table('table')->whereIn('id', array(1, 2, 3))->get();

Use more complex where clauses in Scout

I am a happy user of Laravel Scout.
Now I'd like to extend my search:
$data
= new UserOverviewResourceCollection(User::search($searchphrase)
->currentStatus('active')->orderBy('lastname', 'asc')
->orderBy('firstname', 'asc')
->paginate(config('pagination.length')));
currentStatus comes from https://github.com/spatie/laravel-model-status .
Now I am getting a response, that currentStatus is not supported. I thought I could be a good idea to filter the result of User::search after it has been returned from scout?
Another idea: I'd like to add more complex where clauses:
->where([
[
'starts_on',
'<',
Carbon::now()->toDateTimeString(),
],
[
'ends_on',
'>',
Carbon::now()->toDateTimeString(),
],
])
Maybe you got a better idea?
I really need similar functionality as well but I'm afraid it's not possible at the moment (Laravel 8)
According to the docs; Laravel\Scout\Builder (which is what search() returns) only has a really basic implementation of the where method an can only handle exactly 2 arguments.
Here is the code from the scout package:
/**
* Add a constraint to the search query.
*
* #param string $field
* #param mixed $value
* #return $this
*/
public function where($field, $value)
{
$this->wheres[$field] = $value;
return $this;
}
See https://laravel.com/docs/8.x/scout#where-clauses
Putting the search() behind all regular eloquent builder methods won't work either since search will only be a static function of the model.
Hopefully this will be improved in the future.
You can extend Builder and add currentStatus method to store required status in builder. See example
https://github.com/matchish/laravel-scout-elasticsearch/issues/47#issuecomment-526287359
Then you need implement your own engine and build query using builder there.
Here is example for ElasticSearch engine
https://github.com/matchish/laravel-scout-elasticsearch/blob/06c90914668942b23ffaff7d58af5b9db1901fb1/src/Engines/ElasticSearchEngine.php#L135
Well rewriting it as this should work.
$data
= new UserOverviewResourceCollection(User::search($searchphrase)
->where('starts_on','<',now())
->orderBy('lastname', 'asc')
->orderBy('firstname', 'asc')
->paginate(config('pagination.length')));
Notice that now() is just a global helper function returning current moment Carbon instance. It's just shorter to write, no other reason for using it.
And if you want grouped where queries you do it like this:
$data
= new UserOverviewResourceCollection(User::search($searchphrase)
->where(function($q) {
$q->where('starts_on','<',now())->where('ends_on','>',now())
})
->orderBy('lastname', 'asc')
->orderBy('firstname', 'asc')
->paginate(config('pagination.length')));
And then you should be able to export this to the UserOverviewResourceCollection as a local scope, for example:
public function scopeActive($q) {
return $q->where('starts_on','<',now())->where('ends_on','>',now())
}
and then you can use something like this:
$data
= new UserOverviewResourceCollection(User::search($searchphrase)
->active()
->orderBy('lastname', 'asc')
->orderBy('firstname', 'asc')
I've written this from my head so there might be typos.
$data
= new UserOverviewResourceCollection(User::search($searchphrase)
->currentStatus('active')->orderBy('lastname', 'asc')
->where(function($q){
$query->where('starts_on', '<',Carbon::now()->toDateTimeString());
$query->where('ends_on', '>',Carbon::now()->toDateTimeString());
})
->orderBy('firstname', 'asc')
->paginate(config('pagination.length')));
Try this code. This will satisfy your complex where condition

Laravel Eloquent - text field with leading number getting interpreted as integer

I have the following code:
$event = Event::where('slug', '=', $param)
->orWhere('eventID', '=', $param)
->firstOrFail();
The purpose is to enable $event to populate either with its id or a short text string in the slug field.
I'm experiencing an issue where text beginning with a number is causes the $event to get the value where eventID = the numeric part of the text.
This is not the desired behavior.
Ideas? Thanks!
You need check $param for integer value and depends on that make a query. It easy to do with when method for laravel versions > 5.3. For older version logic will be the same but don't use when method because it has not third parameter.
$event = Event::when(filter_var($param, FILTER_VALIDATE_INT) !== false,
function($query) use ($param){
return $query->where('eventID', $param);
},
function($query) use ($param){
return $query->where('slug', $param);
}
)->firstOrFail();
Other way:
$event = Event::query();
$event = filter_var($param, FILTER_VALIDATE_INT) !== false ? $event->where('eventID', $param) : $event->where('slug', $param);
$event = $event->firstOrFail();

Why soft deleted entities appear in query results?

I am trying to implement soft deleting concept.
Here is my object:
class Post extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'posts';
protected $softDelete = true;
...
Soft delete is on.
Now, if I 'delete' a post, it gets a 'deleted_at' timestamp:
The problem is, when I search or just use all() to display the posts, the soft deleted items appears there. What is wrong?
Sometimes, you will get the soft deleted table entries with get() even with eloquent and protected $softDelete = true;.
So to avoid this problem, use
...->whereNull('deleted_at')->get();
For example, this query will fetch all rows including soft deleted.
DB::table('pages')->select('id','title', 'slug')
->where('is_navigation','=','yes')
->where('parent_id','=',$parent_id)
->orderBy('page_order')
->get();
So the proper method is,
DB::table('pages')->select('id','title', 'slug')
->where('is_navigation','=','yes')
->where('parent_id','=',$parent_id)
->whereNull('deleted_at')
->orderBy('page_order')
->get();
The soft deleting feature works when using Eloquent. If you are querying the results with query builder you will eventually see all the records trashed and not trashed.
It is not clear in the current docs of Laravel 4, but seeing that the concept of soft deleting just appears under Eloquent ORM - Soft Deleting and not under Query Builder, we can only assume that: soft delete only works with Eloquent ORM.
There is a little trick using soft delete tables and queries in laravel:
When we create something like
$objCars = Car::where("color","blue");
The system executes something like that:
SELECT
*
FROM
cars
WHERE
deleted_at IS NULL
AND
"color" = 'blue'
So far, so good. But, when we apply the "orWhere" method, something funny happens
$objCars = Car::where("color","blue")->orWhere("color","red");
The system will execute something like that:
SELECT
*
FROM
cars
WHERE
deleted_at IS NULL
AND
"color" = 'blue'
OR
"color" = 'red'
This new query will return all the car where deleted_at is null and the color is blue OR if the color is red, even if the deleted_at is not null. It is the same behavior of this other query, what show the problem more explicitly:
SELECT
*
FROM
cars
WHERE
(
deleted_at IS NULL
AND
"color" = 'blue'
)
OR
"color" = 'red'
To escape from this problem, you should change the "where" method passing a Closure. Like that:
$objCars = Car::where(
function ( $query ) {
$query->where("color","blue");
$query->orWhere("color","red");
}
);
Then, the system will execute something like that:
SELECT
*
FROM
cars
WHERE
deleted_at IS NULL
AND
(
"color" = 'blue'
OR
"color" = 'red'
)
This last query, searches for all cars where deleted_at is null and where the color can be or red or blue, as we was want it to do.
I had the same problem and nothing here helped me.
My problem was in my construct, I forgot to call the parent constructor:
public function __construct()
{
parent::__construct();
//Rest of my code
}
Hope that help someone!
Laravel 5.2.44 added withoutTrashed() method to SoftDeletingScope. For example you can use something like this:
Post::withoutTrashed()->get();
Tested it in Laravel 5.6, You need to use SoftDeletes Trait in your model
use Illuminate\Database\Eloquent\SoftDeletes;
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Banners extends Model
{
use SoftDeletes;
//no need of this below line
//protected $softDelete = true;
}
and when you query
$banners = Banners::where('status', 1)->get();
it will not return soft deleted data.
me solved join table with eloquent on softdelete - laravel 8
$query = Model_table_1::join('user', 'user.id', '=', 'table_1.user_id')
->join('table_2', 'table_2.id', '=', 'table_1.table2_id')
->join('table_3', 'table_3.id', '=', 'table_1.table3_id')
->join('table_4', 'table_4.id', '=', 'table_3.table4_id')
->select('table_1.id','table_1.name','table_1.created_at',
'user.name','table_2.name2','table_3.name3','table_4.name4')
->get();
use where in datables
$query = Model_table_1::join('user', 'user.id', '=', 'table_1.user_id')
->join('table_2', 'table_2.id', '=', 'table_1.table2_id')
->join('table_3', 'table_3.id', '=', 'table_1.table3_id')
->join('table_4', 'table_4.id', '=', 'table_3.table4_id')
->select('table_1.id','table_1.name','table_1.created_at','user.name','table_2.name2','table_3.name3','table_4.name4')
->where('table_1.user_id', '=', Auth::user()->id)
->get();
use where in view
$query = Model_table_1::join('user', 'user.id', '=', 'table_1.user_id')
->join('table_2', 'table_2.id', '=', 'table_1.table2_id')
->join('table_3', 'table_3.id', '=', 'table_1.table3_id')
->join('table_4', 'table_4.id', '=', 'table_3.table4_id')
->select('table_1.id','table_1.name','table_1.created_at','user.name','table_2.name2','table_3.name3','table_4.name4')
->where('table_1.user_id', '=', Auth::user()->id)
->first();

Resources