I have a fairly complex query as follows:
return $this->createQueryBuilder('s')
->select('s')
->addSelect('COUNT(p.id) as HIDDEN c_id')
->leftJoin('s.owner', 'o')
->leftJoin('s.userPictures', 'p')
->leftJoin('o.transactions', 't')
->leftJoin('t.packType', 'pt')
->where('pt.id =:packId')
->setParameter('packId', $packId)
->andWhere('s.expirydate >=:expiryDate')
->setParameter('expiryDate', new \DateTime('now'))
->andWhere('c_id <:numberOfPictures')
->setParameter('numberOfPictures', $numberOfPictures)
->orderBy("c_id", 'DESC')
->groupBy('p.id')
->getQuery()
;
the problem is that the query is leftJoined with all of it's transactions. I wanted such that it is left joined with the most recent transaction only. How can I do this? Is there an alternative way other than having to find the transaction id of the most recent transaction and put it into the where clause?
The Transaction entity has a created column and the entity looks like this:
class Transaction
{
/**
* #var datetime $created
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
protected $created;
}
Related
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` = ?
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()
My problem is simple. I have two tables
transaction_bodies
------------------
body_id
full_name
and the other one is
transaction_accounts
--------------------
account_id
body_id
account_name
Relation is one to many. One body can have multiple accounts. I am trying to create a query that counts the accounts that bodies have.
I tried this
SELECT *
FROM
(
SELECT count(*) as trans, tb.full_name
FROM transaction_accounts ta
LEFT JOIN transaction_bodies tb
ON tb.body_id = ta.body_id
) as row;
But this doesn't give the right result. Can anyone help me out with this?
And if can provide how to write sub-queries in Laravel that would be a appreciated much.
Try this :
$result = DB::table('transaction_bodies')
->leftJoin('transaction_accounts as
ta','transaction_bodies.body_id','ta.body_id')
->select(DB::raw('count(ta.account_id) AS trans'),'transaction_bodies.full_name')
->groupBy('transaction_bodies.body_id')
->get();
You can do it with LEFT JOIN, e.g.:
SELECT tb.body_id, COUNT(ta.*)
FROM transaction_bodies LEFT JOIN transaction_accounts ta
ON tb.body_id = ta.body_id
GROUP BY tb.body_id;
With a simple LEFT JOIN you can achieve it like
SELECT tb.full_name, COUNT(account_id) as accounts
FROM transaction_bodies tb LEFT JOIN transaction_accounts ta
ON tb.body_id = ta.body_id
GROUP BY tb.body_id;
In Laravel you can do it like with model
$accounts = Transaction_body::leftJoin('transaction_accounts as ta','transaction_bodies.body_id','ta.body_id')->groupBy('transaction_bodies.body_id')->get();
without model
$accounts = DB::table('transaction_bodies')->leftJoin('transaction_accounts as ta','transaction_bodies.body_id','ta.body_id')->groupBy('transaction_bodies.body_id')->get();
/**
* Class Body
*/
class Body extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'transaction_bodies';
/**
* Get the accounts for the Transaction Body.
*/
public function accounts()
{
return $this->hasMany(Account::class);
}
}
/**
* Class Account
*/
class Account extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'transaction_accounts';
/**
* Get the body that owns the account.
*/
public function body()
{
return $this->belongsTo(Body::class);
}
}
//usage
$accounts = Body::find(1)->accounts;
https://laravel.com/docs/5.4/eloquent-relationships#one-to-many
In my Padel project on Symfony2 I have a Competition - Registration relationship
On the "Competition" entity:
/**
* #ORM\OneToMany(targetEntity="Registration", mappedBy="competition")
*/
protected $registrations;
On the "Registration" entity:
/**
* #ORM\ManyToOne(targetEntity="PadelSchedule\UserBundle\Entity\User", inversedBy="registrations")
* #ORM\JoinColumn(name="idPlayer", referencedColumnName="id")
*/
private $player;
/**
* #ORM\ManyToOne(targetEntity="Competition", inversedBy="registrations")
* #ORM\JoinColumn(name="idCompetition", referencedColumnName="id")
*/
private $competition;
Something like this. What I need to do is, having the id of the player, get a list of the competitions on which this player is registered.
The close thing that I have get is using a join with this query:
$qb = $em->createQueryBuilder()
->select('r, c')
->from('PadelScheduleMainBundle:Registration', 'r')
->leftJoin('r.competition', 'c')
->where('r.player = :idPlayer')
->setParameter('idPlayer', $idPlayer);
But as it seems by the error I get, what I think I get through this is a list of Registrations with the competition joined, but I need for my view is a list of "Competition" objects.
Any help? Thanks!
Since you select from your Registration entity you get a result of Registrations. Maybe you have better luck using a query like this:
$em->createQueryBuilder()
->select('c')
->from('PadelScheduleMainBundle:Competition', 'c')
->innerJoin('c.registrations', 'r')
->where('r.player = :player')
->setParameter('player', $playerId)
According to your query question.. I have write this query ..Please Try with this query:
$query = $em->createQuery(
'SELECT c
FROM PadelScheduleMainBundle:Competition c
INNER JOIN c.registrations r
WHERE r.player = :idPlayer')
->setParameter('idPlayer', $idPlayer);
Let me first introduce the Entities used in this example:
Order (_order in mysql)
$id (primary key, auto increment)
OrderStatus (order_status in mysql)
$id (primary key, auto increment)
$order (storing the order object it is related to, named order_id in mysql)
$statuscode (Storing the integer code)
$created_at (Storing the datetime of creation)
The relationship is Order n:1 OrderStatus. For every status change, I create a new OrderStatus with the new statuscode. So for one Order there can be many OrderStatus. The actual OrderStatus can be figured out by looking at the OrderStatus with the latest created_at.
I now want to get all objects which have the status 0 right now. In SQL, my query would look like this:
SELECT o.id,os.statuscode,os.created_at
FROM `_order` o
LEFT JOIN `order_status` os ON o.id = os.order_id
WHERE os.created_at = (SELECT MAX(created_at)
FROM order_status
WHERE order_id = os.order_id);
Can I do such a query in DQL or do I have to work with objects? If so, would I need to read all OrderStatus objects and manually figure out which is the most current one or can I somehow preselect?
I figured out a way which is not quite what I was searching for, but it doesn't require any changes in my application, so it's ok as long as I don't find a better solution.
What I'll do is I'll store the actual status directly within the Order. Through LifecycleCallbacks I'll update the order as soon as a OrderStatus related to it is created.
So my Order-Object looks like this:
/**
* #ORM\Entity
* #ORM\Table(name="_order")
*/
class Order{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
//..
/**
* #ORM\Column(type="smallint", nullable=false)
*/
protected $status = -1;
// Getter and Setter!
}
My Order-Status will look like this:
/**
* #ORM\Entity
* #ORM\Table(name="order_status")
* #ORM\HasLifecycleCallbacks
*/
class OrderStatus{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="order_status", cascade={"persist"})
* #ORM\JoinColumn(name="order_id", referencedColumnName="id", nullable=false)
*/
protected $order;
/**
* #ORM\Column(type="smallint", nullable=false)
*/
protected $statuscode;
// ..
/**
* #ORM\prePersist
*/
public function prePersist(){
$this->order->setStatus($this->statuscode);
}
// Getter and Setter!
}
Mind the , cascade={"persist"} within the OrderStatus so the Order will also be persisted when the OrderStatus is persisted, otherwise the status will be set, but not stored!
On the plus side of this solution is that I now get the OrderStatus by calling $order->getStatus() instead of having to query the database for the status and that I'm able to do $repository->findByStatus(0). On the other hand, the moment the status field gets changed due to some error, the data will be inconsistent.
As said, if you got a solution where I don't need an extra field to store the status, post it! I'm very interested in a better solution!