laravel eloquent subquery same table different columns - mysql

i am trying to re-construct a working mysql query (or something better with the same result) in laravel eloquent. My db looks like this:
I have a words table with columns id and name.
I have a pivot table called synonyms with columns id, word_id and synonym_id.
The synonyms table links words together that are synonyms. It links for example a bike and cycle together. But also bike to cycle etc.
I did manage to build this query in my phpstorm editor and it works:
select * from words
where id in (
select synonym_id from synonyms
where word_id in (select id from words where name = 'bike')
)
or id in (
select word_id from synonyms
where synonym_id in (select id from words where name = 'bike')
)
It returns a result set containing cycle, tandem, velocipede etc.
I did create a "word" eloquent model that has these methods:
/**
* Synonyms for this word
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function synonyms() {
return $this->belongsToMany(Word::class, 'synonyms', 'synonym_id', 'word_id');
}
/**
* Words that have this word as their synonym
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function isSynonymFor() {
return $this->belongsToMany(Word::class, 'synonyms', 'word_id', 'synonym_id');
}
And now I am trying to rebuild that query in eloquent like this:
$word = 'bike'; //Just for debugging purposes I hardcoded it.
$query = Word::whereHas('isSynonymFor', function(Builder $query) use($word) {
$query->where('name', '=', $word);
})->orWhereHas('synonyms', function(Builder $query) use($word) {
$query->where('name', '=', $word);
})
return $query->get();
This does not work like I want it too. It just returns a collection with "bike" in it and not the rest.
How can I fix it?

If you only want to reproduce that exact query (no eloquent relationships used), you should be able to do it just fine with whereIn(column, Closure) for the subqueries.
$query = DB::table('words')
->whereIn('id', function ($sub) {
$sub->select('synonym_id')
->from('synonyms')
->whereIn('word_id', function ($sub2) {
$sub2->select('id')
->from('words')
->where('name', 'bike');
});
})
->orWhereIn('id', function ($sub) {
$sub->select('word_id')
->from('synonyms')
->whereIn('synonym_id', function ($sub2) {
$sub2->select('id')
->from('words')
->where('name', 'bike');
});
})
// ->toSql();
->get();

Related

how to to make eloquent scope with whereHas like sql query below?

how to to make eloquent scope with whereHas like sql query below
table Property(id, title, slug, category_id, location_id,image)
table Category(id, name, slug)
table City ( id, name, slug)
The simple sql query that i need
Select * from property
join category on property.category_id=category.id
join city on property.location_id = city.id
where category.name = $query and city.name=$query
I want to make the eloquent scope in the Property Model
This is easy with relationships.
Based on your query, let's say this is your Property model:
class Property extends Model
{
public function category()
{
return $this->belongsTo(Category::class);
}
public function city()
{
return $this->belongsTo(City::class, 'location_id');
}
Now I can write my Eloquent query like this:
$query = 'Example';
$properties = Property::where('name', $query)
->whereHas('category', function (Builder $builder) use ($query) {
$builder->where('name', $query);
})->get();
Please note that Builder is imported from Illuminate\Database\Eloquent\Builder. You can also add with('category', 'city') to the query above in order to eager load those relationships.

Laravel: get models if count of relation has some condition

I have User and UserComplains Models.
I like to retrieve users that have UserComplains more than 2 times in the last 24 hours.
users:
id
user_complains:
complained_id ->ref-> users.id
created_at
this is what I tried and it is working:
$users = User::select('users.*')->join('user_complains' , 'users.id' , '=' , 'user_complains.complained_id')
->whereRaw("(
select count(*) from `user_complains`
where `user_complains`.`complained_id` = `users`.`id`
and `user_complains`.`created_at` > ?) >= ?" , [now()->subHours(24), 2])
->groupBy("users.id")
->get();
the above code is fine and is working, but I wonder is there a better way to do that?!
For something like this you can use whereHas(). :
$users = User::whereHas('*relationship*', function ($query) {
$query->where('created_at', '>=', now()->subDay(1));
}, '>', 2)->get();
As mentioned in the documentation, you can pass additional checks as the 3rd and 4th param so in this case you want to say where the user has more that 2 user_complains.
NB You will need to replace *relationship* with the actual name of the relationship.
You can do the following:
User::whereHas('complaints', function($query) {
$query->where('created_at', '>=', '2020-04-26');
}, '>', 2)->get();
In order for this to work though you need to have set up a relationship between your User and UserComplaint models.
class User extends Model
{
public function complaints()
{
return $this->hasMany(UserComplaint:class);
}
}

Laravel Many-to-Many (on the same users table/Model): Query scopes to include related for the specified user

Users can block each other. One user can block many (other) users, and one user can be blocked by many (other) users.
In User model I have these many-to-many relationships:
/**
* Get the users that are blocked by $this user.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function blockedUsers()
{
return $this->belongsToMany(User::class, 'ignore_lists', 'user_id', 'blocked_user_id');
}
/**
* Get the users that blocked $this user.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function blockedByUsers()
{
return $this->belongsToMany(User::class, 'ignore_lists', 'blocked_user_id', 'user_id');
}
(ignore_lists is the pivot table and it has id, user_id, 'blocked_user_id' columns)
I want to create the following Query Scopes:
1) To include users that are blocked by the specified user ($id):
/**
* Scope a query to only include users that are blocked by the specified user.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param $id
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeAreBlockedBy($query, $id)
{
// How to do this? :)
}
Example of usage: User::areBlockedBy(auth()->id())->where('verified', 1)->get();
2) To include users that are not blocked by the specified user ($id):
/**
* Scope a query to only include users that are not blocked by the specified user.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param $id
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeAreNotBlockedBy($query, $id)
{
// How to do this? :)
}
Example of usage: User::areNotBlockedBy(auth()->id())->where('verified', 1)->get();
3) To include users that blocked the specified user ($id):
/**
* Scope a query to only include users that blocked the specified user.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param $id
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWhoBlocked($query, $id)
{
// How to do this? :)
}
Example of usage: User::whoBlocked(auth()->id())->where('verified', 1)->get();
4) To include users that did not block the specified user ($id):
/**
* Scope a query to only include users that did not block the specified user.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param $id
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWhoDidNotBlock($query, $id)
{
// How to do this? :)
}
Example of usage: User::whoDidNotBlock(auth()->id())->where('verified', 1)->get();
How would you do this?
I didn't find anything in the Laravel docs about this (maybe I missed it).
(I'm using Laravel 6.x)
I'm not sure, but I think this could be done in two ways: Using Left Join or using raw queries in whereIn... I may be wrong, but I think the "left join" solution would be better as far as performance is concerned, right? (not sure about this, maybe I'm totally wrong).
Use join(inner join) performance is better than whereIn subquery.
In MySQL, subselects within the IN clause are re-executed for every row in the outer query, thus creating O(n^2).
I think use whereHas and whereDoesntHave for query will be more readable.
1) The relationship method blockedUsers() has already include users that are blocked by the specified user ($id), you can use this method directly:
User::where('id', $id)->first()->blockedUsers();
Considerate about applying the where('verified', 1) at first, so you can use query like User::where('verified', 1)->areBlockedBy(auth()->id()), the scope can be like this:
public function scopeAreBlockedBy($query, $id)
{
return $query->whereHas('blockedByUsers', function($users) use($id) {
$users->where('ignore_lists.user_id', $id);
});
}
// better performance: however, when you apply another where condition, you need to specify the table name ->where('users.verified', 1)
public function scopeAreBlockedBy($query, $id)
{
return $query->join('ignore_lists', function($q) use ($id) {
$q->on('ignore_lists.blocked_user_id', '=', 'users.id')
->where('ignore_lists.user_id', $id);
})->select('users.*')->distinct();
}
We use join for the second query that will improve the performance because it doesn't need to use where exists.
Example for 300,000+ records in users table:
Explain the first query whereHas which scan 301119+1+1 rows and takes 575ms:
Explain the second query join which scan 3+1 rows and takes 10.1ms:
2) To include users that are not blocked by the specified user ($id), you can use whereDoesntHave closure like this one:
public function scopeNotBlockedUsers($query, $id)
{
return $query->whereDoesntHave('blockedByUsers', function($users) use ($id){
$users->where('ignore_lists.user_id', $id);
});
}
I prefer to use whereDoesntHave instead of leftJoin here. Because when you use leftjoin like this below:
User::leftjoin('ignore_lists', function($q) use ($id) {
$q->on('ignore_lists.blocked_user_id', '=', 'users.id')
->where('ignore_lists.user_id', $id);
})->whereNull('ignore_lists.id')->select('users.*')->distinct()->get();
Mysql need to create an temporary table for storing all the users' records and combine some ignore_lists.And then scan these records and find out the records which without ignore_lists. whereDosentHave will scan all users too. For my mysql server, where not exists is a little faster than left join. Its execution plan seems good. The performance of these two queries are not much different.
For whereDoesntHave is more readable. I will choose whereDoesntHave.
3) To include users that blocked the specified user ($id), to use whereHas blockedUsers like this:
public function scopeWhoBlocked($query, $id)
{
return $query->whereHas('blockedUsers', function($q) use ($id) {
$q->where('ignore_lists.blocked_user_id', $id);
});
}
// better performance: however, when you apply another where condition, you need to specify the table name ->where('users.verified', 1)
public function scopeWhoBlocked($query, $id)
{
return $query->join('ignore_lists', function($q) use ($id) {
$q->on('ignore_lists.user_id', '=', 'users.id')
->where('ignore_lists.blocked_user_id', $id);
})->select('users.*')->distinct();
}
4) To include users that did not block the specified user ($id), use whereDoesntHave for blockedByUsers:
public function scopeWhoDidNotBlock($query, $id)
{
return $query->whereDoesntHave('blockedUsers', function($q) use ($id) {
$q->where('ignore_lists.blocked_user_id', $id);
});
}
PS: Remember to add index on foreign_key for ignore_lists table.
You can use Querying Relationship Existence whereHas and Querying Relationship Absence whereDoesntHave query builder functions to build your result queries.
I have included each query generated SQL code and query time in milliseconds tested on a dual Xeon dedicated server on a table that has 1000 users.
We don't want to get current user in the results when querying with areNotBlockedBy and whoDidNotBlock, so these functions will exclude the user with $id.
To include users that are blocked by the specified user ($id):
/**
* Scope a query to only include users that are blocked by the specified user.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param $id
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeAreBlockedBy($query, $id)
{
return User::whereHas('blockedByUsers', function($q) use($id) {
$q->where('user_id', $id);
});
}
Executing:
User::areBlockedBy(auth()->id())->where('verified', 1)->get();
Will generate the following SQL:
-- Showing rows 0 - 3 (4 total, Query took 0.0006 seconds.)
select * from `users` where exists (select * from `users` as `laravel_reserved_9` inner join `ignore_lists` on `laravel_reserved_9`.`id` = `ignore_lists`.`user_id` where `users`.`id` = `ignore_lists`.`blocked_user_id` and `user_id` = ?) and `verified` = ?
To include users that are not blocked by the specified user ($id):
/**
* Scope a query to only include users that are not blocked by the specified user.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param $id
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeAreNotBlockedBy($query, $id)
{
// It will exclude the user with $id
return User::where('id', '!=', $id)
->whereDoesntHave('blockedByUsers', function($q) use($id) {
$q->where('user_id', $id);
});
}
Executing:
User::areNotBlockedBy(auth()->id())->where('verified', 1)->get();
Will generate the following SQL:
-- Showing rows 0 - 24 (990 total, Query took 0.0005 seconds.)
select * from `users` where `id` != ? and not exists (select * from `users` as `laravel_reserved_0` inner join `ignore_lists` on `laravel_reserved_0`.`id` = `ignore_lists`.`user_id` where `users`.`id` = `ignore_lists`.`blocked_user_id` and `user_id` = ?) and `verified` = ?
To include users that blocked the specified user ($id):
/**
* Scope a query to only include users that blocked the specified user.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param $id
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWhoBlocked($query, $id)
{
return User::whereHas('blockedUsers', function($q) use($id) {
$q->where('blocked_user_id', $id);
});
}
Executing:
User::whoBlocked(auth()->id())->where('verified', 1)->get();
Will generate the following SQL:
-- Showing rows 0 - 1 (2 total, Query took 0.0004 seconds.)
select * from `users` where exists (select * from `users` as `laravel_reserved_12` inner join `ignore_lists` on `laravel_reserved_12`.`id` = `ignore_lists`.`blocked_user_id` where `users`.`id` = `ignore_lists`.`user_id` and `blocked_user_id` = ?) and `verified` = ?
To include users that did not block the specified user ($id):
/**
* Scope a query to only include users that did not block the specified user.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param $id
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWhoDidNotBlock($query, $id)
{
// It will exclude the user with $id
return User::where('id', '!=', $id)
->whereDoesntHave('blockedUsers', function($q) use($id) {
$q->where('blocked_user_id', $id);
});
}
Executing:
User::whoDidNotBlock(auth()->id())->where('verified', 1)->get();
Will generate the following SQL:
-- Showing rows 0 - 24 (992 total, Query took 0.0004 seconds.)
select * from `users` where `id` != ? and not exists (select * from `users` as `laravel_reserved_1` inner join `ignore_lists` on `laravel_reserved_1`.`id` = `ignore_lists`.`blocked_user_id` where `users`.`id` = `ignore_lists`.`user_id` and `blocked_user_id` = ?) and `verified` = ?

Laravel Query builder - select top 1 row from another table

I have this SQL query for MySQL which works fine. But I need to rewrite it using query builder and need to avoid DB::raw() completely because development database is different from production. I know far from ideal, but unfortunately it is what it is.
SELECT athletes.*,
(
SELECT performance
FROM performances
WHERE athletes.id = performances.athlete_id AND performances.event_id = 1
ORDER BY performance DESC
LIMIT 0,1
) AS personal_best
FROM athletes
ORDER BY personal_best DESC
Limit 0, 100
And I'm struggling how to rewrite the personal_best part. I have table of performances for athletes and I need to select only the best performance for each athletes as his personal best.
I was trying to search for answer but all of the answers I found included raw adding raw SQL.
Any ideas or hint would be much appreciated.
Thank you in advance!
So I accepted I might have to use Eloquent for this, but still having trouble to progress. Heres my code:
class Athlete extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'athletes';
/**
* The primary key associated with the table.
*
* #var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
/**
* Get the performances for the Athelete post.
*
* #return HasMany
*/
public function performances()
{
return $this->hasMany('App\EloquentModels\Performance', 'athlete_id');
}
}
class Performance extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'performances';
/**
* The primary key associated with the table.
*
* #var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
}
Create a new connection at database.php like mysql_dev for development parameters.
DB::connection('mysql_dev')->table('athletes')
->leftJoin('performances','athletes.id','performances.athlete_id')
->where('performances.event_id',1)
->groupBy('athletes.id')
->orderByDesc('personal_best')
->select('athletes.*',DB::raw('MAX(performances.performance) AS personal_best')
->paginate(100);
try like this without raw,
DB::connection('mysql_dev')->table('athletes')
->leftJoin('performances','athletes.id','performances.athlete_id')
->where('performances.event_id',1)
->groupBy('athletes.id')
->orderByDesc('performances.performance')
->select('athletes.*','performances.performance'
->paginate(100);
If you are using raw SQL just do MAX for performance for each athlete using GROUP BY.
SELECT athletes.*, MAX(performance) AS personal_best
FROM athletes
INNER JOIN performances ON athletes.id = performances.athlete_id AND performances.event_id = 1
GROUP BY athletes.id
ORDER BY personal_best DESC
LIMIT 0, 100
Laravel Query Builder:
DB::table('athletes')
->join('performances', 'athletes.id', '=', 'performances.athlete_id')
->where('performances.event_id', '=', 1)
->groupBy('athletes.id')
->orderBy('personal_best', 'desc')
->select('athletes.*',DB::raw('MAX(performance) AS personal_best')
->limit(100);
Doc says that we can do max(personal_best) but not sure how to use it with group by.
I'm afraid you can't avoid DB::raw in Query Builder but you can use eloquent model for the same, as answered by Shaielndra Gupta.
For that you can create model and relationship.
1. Create Model:
php artisan make:model Athelete
php artisan make:model Performance
2. Create relationship between Athelete and Perforamnce.
Update Athelete.php
/**
* Get the performances for the Athelete post.
*/
public function performances()
{
return $this->hasMany('App\Performance');
}
3. Get data(didn't verify by myself)
$data = Athelete::with('performances',function ($query) use ($eventId){
$query->max('performance')
$query->where('event_id',$eventId)
$query->orderBy('performance');
})->get();
Reference:
Laravel Model
Laravel Relationship
You can use like below.
$sql1 = "(
SELECT performance
FROM performances
WHERE athletes.id = performances.athlete_id AND performances.event_id = 1
ORDER BY performance DESC
LIMIT 0,1
) AS personal_best";
$sql2 = "SELECT athletes.*,$sql1
FROM athletes
ORDER BY personal_best DESC
Limit 0, 100";
$result = DB::select($sql2);
you can user Eloquent ORM like this
$data = Athelete::with('performances',function ($query) use ($eventId){
$query->max('performance')
$query->where('event_id',$eventId)
$query->orderBy('performance');
})->get()

Where not Exists en Laravel

Could anybody tell me what error I might have in my laravel query, basically what I want is to list the records of a table whose id is not related in another table. I did it in Mysql with this query: SELECT * FROM item WHERE NOT EXISTS (SELECT null FROM qualifications WHERE grades.item_id = item.id AND qualifications.user_id = 2);
but now I need to do this same query in laravel, I tried it this way:
codigo
and what I get is this syntax error that I do not know how to solve anymore:
error
I am very grateful to anyone who can tell me what I am doing wrong, or in what form I make that query in Laravel.
You can also rewrite your query as left join like
SELECT i.*
FROM item i
LEFT JOIN qualifications q ON q.item_id = i.id AND q.user_id = 2
WHERE q.item_id IS NULL
In query builder you can write it as
DB::table('item as i')
->select('i.*')
->leftJoin('qualifications as q', function ($join) use($user_id) {
$join->on('q.item_id', '=', 'i.id')
->on('q.user_id', '=', $user_id);
})
->whereNull('q.item_id')
->get();
Another approach which i suggest you to go with, is setup your relations and models and do it with eloquent way
class Item extends Model
{
public function qualifications()
{
return $this->hasMany(\App\Models\Qualification::class, 'item_id');
}
}
class Qualification extends Model
{
public function qualifications()
{
return $this->belongsTo(Item::class, 'item_id');
}
}
And then you can use Querying Relationship Absence
Item::whereDoesntHave('qualifications', function ($query) use($user_id) {
$query->where('user_id', '=', $user_id);
})->get();