How to use SQL_CALC_FOUND_ROWS with Zend\Db\TableGateway - mysql

How to get SQL_CALC_FOUND_ROWS with Zend\Db\TableGateway without using direct low level queries with raw SQL?
class ProductTable {
protected $tableGateway;
/**
* Set database gateway
*
* #param TableGateway $tableGateway - database connection
* #return void
*/
public function __construct(TableGateway $tableGateway) {
$this->tableGateway = $tableGateway;
}
/**
* Fetch all products
*
* #param integer $page - page of records
* #param integer $perpage - records per page
* #return void
*/
public function fetchAll($page = 1, $perpage = 18) {
return $this->tableGateway->select(function (Select $select) use ($page, $perpage) {
$select
->limit($perpage)
->offset(($page - 1) * $perpage);
});
}
}
I wish to get total number of records in a same query used in fetchAll.

Looks like Zend Framework 2.1.4 has support to specify a quantifier. This enables you to use the SQL_CALC_FOUND_ROWS in a select object. One thing I did find tricky to work around is that Zend's Zend\Db\Sql\Select class will not generate the correct SQL for you if you did not specify a table. This becomes and issue when executing the subsequent select to retrieve the FOUND_ROWS(). I've updated your code below to include what I would use. I've merge my project implementation into your code, so if something does not work, its probably because I mistype something, but overall it works for me (not as desirable as I would want).
use Zend\Db\Sql\Expression;
use Zend\Db\Sql\Select;
class ProductTable {
protected $tableGateway;
/**
* Set database gateway
*
* #param TableGateway $tableGateway - database connection
* #return void
*/
public function __construct(TableGateway $tableGateway) {
$this->tableGateway = $tableGateway;
}
/**
* Fetch all products
*
* #param integer $page - page of records
* #param integer $perpage - records per page
* #return void
*/
public function fetchAll($page = 1, $perpage = 18) {
$result = $this->tableGateway->select(function (Select $select) use ($page, $perpage) {
$select
->quantifier(new Expression('SQL_CALC_FOUND_ROWS'))
->limit($perpage)
->offset(($page - 1) * $perpage);
});
/* retrieve the sql object from the table gateway */
$sql = $this->tableGateway->getSql();
/* create an empty select statement passing in some random non-empty string as the table. need this because Zend select statement will
generate an empty SQL if the table is empty. */
$select = new Select(' ');
/* update the select statement specification so that we don't incorporate the FROM clause */
$select->setSpecification(Select::SELECT, array(
'SELECT %1$s' => array(
array(1 => '%1$s', 2 => '%1$s AS %2$s', 'combinedby' => ', '),
null
)
));
/* specify the column */
$select->columns(array(
'total' => new Expression("FOUND_ROWS()")
));
/* execute the select and extract the total */
$statement = $sql->prepareStatementForSqlObject($select);
$result2 = $statement->execute();
$row = $result2->current();
$total = $row['total']';
/* TODO: need to do something with the total? */
return $result;
}
}

Related

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()

Laravel: Have an existing query but need to apply it to only the last 30 days

I have a query:
User::selectRaw('users.facebook_id')
->join('orders', 'orders.customer_id', 'shop_users.id')
->groupBy('shop_users.id')
->havingRaw('SUM(orders.total) >= 0')
->get()->pluck('facebook_id')->all();
but I want it only for orders from the last 30 days from now.
I assume I could use something such as ->whereDate('created_at', '>', Carbon::now()->subDays(30)) but not sure how to apply this to orders.
I think where between is what you need
Here try this
User::selectRaw('users.facebook_id')
->join('orders', 'orders.customer_id', 'shop_users.id')
->groupBy('shop_users.id')
->havingRaw('SUM(orders.total) >= 0')
->whereBetween('orders.created_at', [Carbon::now()->addDays(-30), Carbon::now()])
->get()->pluck('facebook_id')->all();
This will work if your datatype is dateTime.
now if you want to date only you can use this ->toDateString() just add this on your carbon
Hope it helps.
Here is the Join function from Laravel Code base,
/**
* Add a join clause to the query.
*
* #param string $table
* #param \Closure|string $first
* #param string|null $operator
* #param string|null $second
* #param string $type
* #param bool $where
* #return $this
*/
public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false)
{
$join = $this->newJoinClause($this, $type, $table);
// If the first "column" of the join is really a Closure instance the developer
// is trying to build a join with a complex "on" clause containing more than
// one condition, so we will add the join and call a Closure with the query.
if ($first instanceof Closure) {
call_user_func($first, $join);
$this->joins[] = $join;
$this->addBinding($join->getBindings(), 'join');
}
// If the column is simply a string, we can assume the join simply has a basic
// "on" clause with a single condition. So we will just build the join with
// this simple join clauses attached to it. There is not a join callback.
else {
$method = $where ? 'where' : 'on';
$this->joins[] = $join->$method($first, $operator, $second);
$this->addBinding($join->getBindings(), 'join');
}
return $this;
}
That means, you can do may be something like below
User::selectRaw('users.facebook_id')
->join('orders',function ($j) {
// Now $j is instance of builder e.g.
// $j->on('users.id', '=', 'orders.id')->orOn('users.name', '=', 'orders.name')->->whereDate('created_at', '>', Carbon::now()->subDays(30));
// Not super clear on the tables you are using
})
->groupBy('shop_users.id')
->havingRaw('SUM(orders.total) >= 0')
->get()->pluck('facebook_id')->all();

Doctrine does not automatically load joined relation using GROUP BY

I have two Doctrine entity classes: Vertriebsschiene and Filiale:
/**
* Vertriebsschiene
*
* #ORM\Table(name="vertriebsschiene", indexes={
* #ORM\Index(columns={"name"}, flags={"fulltext"})
* }))
* #ORM\Entity(repositoryClass="CRMBundle\Repository\VertriebsschieneRepository")
*/
class Vertriebsschiene
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=100)
*/
private $name;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Filiale", mappedBy="vertriebsschiene", fetch="EAGER")
*/
private $filialen;
...
}
/**
* Filiale
*
* #ORM\Table(name="filiale")
* #ORM\Entity(repositoryClass="CRMBundle\Repository\FilialeRepository")
*/
class Filiale extends Lieferant
{
/**
* #var Vertriebsschiene
*
* #ORM\ManyToOne(targetEntity="CRMBundle\Entity\Vertriebsschiene", inversedBy="filialen", fetch="EAGER")
*/
private $vertriebsschiene;
...
}
The Vertriebsschine objects have a non-uniqe name. Now I try to display a list of Vertriebsschiene objects with their Filiale objects.
My findAllQuery method looks like this:
/**
* #param User $user
* #return \Doctrine\ORM\Query
*/
public function findAllQuery(User $user){
$qb = $this->createQueryBuilder('v')
->select('v as vertriebsschiene')
->addSelect('COUNT(f) as filial_num')
->leftJoin('v.filialen', 'f')
->groupBy('v.name');
$this->restrictAccess($user, $qb);
return $qb->getQuery();
}
/**
* #param User $user
* #param $qb
*/
protected function restrictAccess(User $user, QueryBuilder &$qb)
{
if ($user->hasRole(RoleVoter::AUSSENDIENST)) {
$qb ->leftJoin('f.vertreter', 'u')
->leftJoin('u.vertretungen', 'vx')
->andWhere($qb->expr()->orX(
'f.vertreter = :userid',
'f.vertreter IS NULL',
$qb->expr()->andX(
'vx.proxy = :userid',
$qb->expr()->between(':currentDate', 'vx.start', 'vx.end')
)
))
->setParameter('userid', $user->getId())
->setParameter('currentDate', new \DateTime(), \Doctrine\DBAL\Types\Type::DATETIME);
}
}
My problem is, that the Vertriebsschiene::$filiale array collection is not automatically loaded, but is loaded for every Vertriebsschiene resulting in many DB connections.
This also has the problem, that the WHERE statement is ignored when the Vertriebsschiene::$filiale is fetched.
The COUNT(f) returns the correct amount of Filiale objects.
I suspect this is an issue with the GROUP BY statement.
I think the problem is that you do not tell doctrine to select filiale fields.
Try to add the filiale alias in your select :
public function findAllQuery(User $user){
$qb = $this->createQueryBuilder('v')
->select('v as vertriebsschiene', 'f')
->addSelect('COUNT(f) as filial_num')
->leftJoin('v.filialen', 'f')
->groupBy('v.name');
$this->restrictAccess($user, $qb);
return $qb->getQuery();
}
If you check your query in the profiler, i think you'll see that doctrine add a LEFT JOIN fialiale f0_ ON v0_.id = f0_.vertriebsschiene_id (or something like this but does not add SELECT ... f0_.id, f0_.xxxx.
So every time you'll call $vertriebsschiene->getFieliale()->getXXX() doctrine will have to execute the corresponding query to get the filiale data.

SQLSTATE[42000]: Syntax error or access violation: 1066 Not unique table/alias on relationship

So, I'm receiving the following error from Laravel framework; but I couldn't find why this framework is producing this error:
SQLSTATE[42000]: Syntax error or access violation: 1066 Not unique table/alias: 'participants' (SQL: select `participants`.*, `participants`.`message_id` as `pivot_message_id`, `participants`.`user_id` as `pivot_user_id`, `participants`.`created_at` as `pivot_created_at`, `participants`.`updated_at` as `pivot_updated_at` from `participants` inner join `participants` on `participants`.`id` = `participants`.`user_id` where `participants`.`deleted_at` is null and `participants`.`message_id` in (2))
My message/participants relationship looks like this:
public function participants()
{
return $this->belongsToMany('Namespace\Modules\Email\Models\Participant', 'participants', 'message_id', 'user_id')->withTimestamps();
}
and I'm trying to call it like this:
public function getAllMessages()
{
return Message::with('user')->with('participants')->get();
}
Why am I getting this error? What's going on?
Edit: Included full models
Message
class Message extends Eloquent
{
use PublishedTrait;
use SoftDeletingTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'messages';
/**
* The attributes that can be set with Mass Assignment.
*
* #var array
*/
protected $fillable = ['subject', 'user_id', 'body', 'status'];
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
/**
* Validation rules.
*
* #var array
*/
protected $rules = [
'subject' => 'required|max:255',
'body' => 'required',
];
/**
* User relationship
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(Config::get('email.user_model'));
}
public function assets()
{
return $this->belongsToMany('Namespace\Modules\Assets\Models\Asset', 'message_assets');
}
/**
* Participants relationship
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function participants()
{
return $this->belongsToMany('Namespace\Modules\Email\Models\Participant', 'participants', 'message_id', 'user_id')->withTimestamps();
}
/**
* Recipients of this message
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function recipients()
{
return $this->participants()->where('user_id', '!=', $this->user_id);
}
/**
* Returns the latest message from a thread
*
* #return Namespace\Modules\Email\Models\Message
*/
public function getLatestMessageAttribute()
{
return $this->messages()->latest()->first();
}
/**
* Returns threads that the user is associated with
* #param $query
* #param $userId
* #return mixed
*/
public function scopeForUser($query, $userId)
{
return $query->join('participants', 'messages.id', '=', 'participants.message_id')
->where('participants.user_id', $userId)
->where('participants.deleted_at', null)
->select('messages.*');
}
/**
* Returns threads that the user is associated with
* #param $query
* #param $userId
* #return mixed
*/
public function scopeForUserWithDeleted($query, $userId)
{
return $query->join('participants', 'messages.id', '=', 'participants.message_id')
->where('participants.user_id', $userId)
->select('messages.*');
}
/**
* Returns messages that the user has sent
* #param $query
* #param $userId
* #return mixed
*/
public function scopeByUser($query, $userId)
{
return $query->where('user_id', $userId);
}
/**
* Returns threads with new messages that the user is associated with
* #param $query
* #param $userId
* #return mixed
*/
public function scopeForUserWithNewMessages($query, $userId)
{
return $query->join('participants', 'messages.id', '=', 'participants.message_id')
->where('participants.user_id', $userId)
->whereNull('participants.deleted_at')
->where(function ($query) {
$query->where('messages.updated_at', '>', $this->getConnection()->raw($this->getConnection()->getTablePrefix() . 'participants.last_read'))
->orWhereNull('participants.last_read');
})
->select('messages.*');
}
}
Participant
class Participant extends Eloquent
{
use SoftDeletingTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'participants';
/**
* The attributes that can be set with Mass Assignment.
*
* #var array
*/
protected $fillable = ['message_id', 'user_id', 'last_read'];
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = ['created_at', 'updated_at', 'deleted_at', 'last_read'];
/**
* Thread relationship
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function message()
{
return $this->hasMany('Namespace\Modules\Email\Models\Message');
}
/**
* User relationship
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(Config::get('email.user_model'));
}
}
Answered via the Larachat official Slack:
The relationship is missing a pivot table for this to work. The second argument in the participants method is the pivot table to use:
public function participants()
{
return $this->belongsToMany('Namespace\Modules\Email\Models\Participant', 'PIVOT', 'message_id', 'user_id')->withTimestamps();
}
Therefore, you can't use participants as the pivot because it is one of the tables in the relationship, you need a message_participant pivot table.
Your error is
...from `participants` inner join `participants` ...
You need to provide aliases for each reference, as in
...from `participants` p1 inner join `participants` p2 ...
and then use p1 and p2 in the correct places, for example
...on p1.`id` = p2.`user_id` ...
(I'm guessing on which is p1 and which is p2; you have to make that determination)
Answering this question for anyone who encountered this error much later.
Judging from your table records, the participants table seems to be the pivot table between users and messages. You are referencing the pivot table, leading to the database misbehaving.
The correct way to do this is in your users models:
public function messages(){
return $this->belongsToMany(Message::class, 'participants', 'user_id', 'message_id')->withPivot('last_read');
}
In your messages models:
public function users(){
return $this->belongsToMany(User::class, 'participants', 'message_id', 'user_id')->withPivot('last_read');
}
That way, when you calling messages from users, use this:
$messages = $user->messages()->get();
And when you checking the users for the message,
$user = $message->users()->get()
And last read
$last_read = $message->pivot->last_read;