hasManyThrough query without laravel_through_key - laravel

With a Laravel hasManyThrough relationship how can I prevent extra keys added to the select? Is there a withoutPivot or simiar?
I'm trying to perform a union join and need to the columns to be the same.
SQLSTATE[21000]: Cardinality violation: 1222 The used SELECT statements have a different number of columns
SELECT
count(*) AS aggregate
FROM ((
SELECT
`order_payments`.`id`,
`order_payments`.*,
`orders`.`user_id` AS `laravel_through_key`
FROM
`order_payments`
INNER JOIN `orders` ON `orders`.`id` = `order_payments`.`order_id`
WHERE
`orders`.`user_id` = 1)
UNION (
SELECT
`orders`.`id`
FROM
`orders`
WHERE
`orders`.`user_id` = 1
AND `orders`.`user_id` IS NOT NULL)) AS `temp_table`
I'm unable to use the makeHidden as suggested in another question
$payments = auth()->user()->orderPayments()->select('order_payments.id');
$orders = auth()->user()->orders()->select('orders.id');
$payments->union($orders)->paginate(50);
I guess I could do it without Eloquent manually but just wondering if there was another way?
class User extends Authenticatable implements UserContract {
/**
* Orders for this User
*/
public function orders()
{
return $this->hasMany(Order::class);
}
/**
* Order Payments for this User
*/
public function orderPayments()
{
return $this->hasManyThrough(OrderPayment::class, Order::class);
}
}

Related

Laravel BelongsToMany relationship with WhereHas condition

I have some relationship in my DB:
public function managers()
{
return $this->belongsToMany(User::class, 'manager_location', 'location_id', 'manager_id')
}
This relation works well - return all users which were synced with some location.
I'm using spatie/laravel-permissions and my users have different roles. When I'm trying to return users with some role (add the whereHas to the relation) it continues to returns all records:
public function managers()
{
return $this->belongsToMany(User::class, 'manager_location', 'location_id', 'manager_id')->whereHas('roles', function ($q) {
$q->where('name', '=','manager');
});
}
Where I went wrong?
P.S.
Mysql log query:
select * from `users` inner join `manager_location` on `users`.`id` = `manager_location`.`manager_id` where `manager_location`.`location_id` is null and exists (select * from `roles` inner join `model_has_roles` on `roles`.`id` = `model_has_roles`.`role_id` where `users`.`id` = `model_has_roles`.`model_id` and `model_has_roles`.`model_type` = ? and `name` = ?)
Dump of QueryBuilder: https://pastebin.com/j6ik35mK

Eloquent whereHas Relationship with constraint on particular related Entity

Consider having two models User, and Book the last one has a status column that can obtain different string values active, inactive, deleted, so the user can have multiple books and the book belongs to the user.
how could I get only users that have their last book status = 'inactive'?
The SQL Query for the behavior is given below:
SELECT
*
FROM
`users`
WHERE EXISTS
(
SELECT
*
FROM
`books`
WHERE
`books`.`user_id` = `users`.`id` AND `books`.`status` = 'inactive' AND `books`.`id` =(
SELECT
nested.`id`
FROM
`books` AS nested
WHERE
nested.`user_id` = `users`.`id`
ORDER BY
nested.`created_at` DESC
LIMIT 1
)
)
I'm using Laravel 5.6
Create additional relationship in User model that returns wanted result. Basically you need 1-1 relationship for this.
/**
* #return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function inactiveBookStillLatestPerUser()
{
return $this->hasOne(Book::class)->where(['status' => 'inactive', 'id' => function (\Illuminate\Database\Query\Builder $nested) {
$nested->from('books as nested')
->selectRaw('max(id)')
->whereRaw('nested.user_id = books.user_id');
}]);
}
Then in somewhere in code (i.e. controller) you call it with
$users = User::has('inactiveBookStillLatestPerUser')->get();
// or if books are needed too
// $users = User::has('inactiveBookStillLatestPerUser')->with(['inactiveBookStillLatestPerUser'])->get();
I used id latest order [max(id)] in subquery to avoid unwanted result if one user made multiple books batch insert at same point of time and when all those books would have same time of insert so latest per created_at wouldn't be most accurate, maybe. But you can do that similarly, instead:
/**
* #return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function inactiveBookStillLatestPerUser()
{
return $this->hasOne(Book::class)->where(['status' => 'inactive', 'created_at' => function (\Illuminate\Database\Query\Builder $nested) {
$nested->from('books as nested')
->selectRaw('max(created_at)')
->whereRaw('nested.user_id = books.user_id');
}]);
}
Maybe second example is ok, but first example with id would work fine, though.
User::where('your-conditions')
->whereHas('books', function($query) {
$query->where('books.status', '=', 'inactive')
->orderBy('id', 'desc')
->first();
})->get();

How to retrieve information through table?

I have SQL query that does that I need:
SELECT users.FirstName, users.Id FROM `orders`
INNER JOIN orderrecipients ON orderrecipients.Orders_Id = orders.Id
INNER JOIN users ON users.Id = orderrecipients.Users_Id;
I try to use Eloquent model to build query above using hasManyThrough:
public function OrderRecipients()
{
return $this->hasManyThrough('App\OrderRecipient', 'App\User', 'Id', 'Orders_Id');
}
And using is:
$recipients = OrderModel::with("OrderRecipients")->where("User_Id", $userId)->get();
dd($recipients->OrderRecipients()->count());
How to build this relation and get data through table?
HasManyThrough is not the best choice here. Use a BelongsToMany relationship instead:
public function OrderRecipients()
{
return $this->belongsToMany('App\User', 'orderrecipients', 'Orders_Id', 'Users_Id');
}

belongsToMany multiple foreign keys, laravel

Good morning
I currently have a table that is related to 3 tables.
It has occurred to me to do "belongsToMany", but I do not know how to do it with 3 relationships
Tables:
schools_series (relation)
id
cycle_id
school_id
serie_id
cycle
id
active (true / false)
** more columns **
school
id
** more columns **
serie
id
** more columns **
Principal Model:
School
class School extends Model implements Presentable
{
use PresentableTrait;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'network_id',
'fantasy_name',
'social_name',
'email',
'sim_web_code',
'state_registration',
'cnpj',
'status',
'start_date',
];
public function series()
{
return $this->belongsToMany(Serie::class, 'schools_series')->where('id_cycle', 'xxxx');
}
I need to be able to take out the "school series" depending on the "cycle_id" that has "active" in cycle
example SQL:
SELECT ss.* FROM schools as s
INNER JOIN schools_series as ss
ON ss.school_id = s.id
INNER JOIN cycle as c
ON ss.cycle_id = c.id AND c.active = 1
WHERE s.id = 115
Solution:
public function series()
{
return $this->belongsToMany(Serie::class, 'schools_series')->withPivot('cycle_id')->join('cycle', 'cycle.id', '=', 'schools_series.cycle_id')->where('cycle.active', '=', 1);
}

Doctrine select all users that manage other users via column

Maybe I am missing something completely, but I cannot get it working. I only want to select User objects that are linked to User objects.
User:
class User implements AdvancedUserInterface, \Serializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// other fields ...
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
*/
private $firstManager;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
*/
private $secondManager;
}
I want to select those who are firstManager or secondManager for a User. Sounds simple eh?
I thought, this would do:
public function findAllManagers()
{
$qb = $this->createQueryBuilder('user')
->join('user.firstManager', 'first_manager')
->join('user.secondManager', 'second_manager')
->orWhere('first_manager = user')
->orWhere('second_manager = user');
$qb = $qb->getQuery();
return $qb->getResult();
}
But only got one result, not all three I needed. I think this is valid SQL?
For what i see your sql query should look like:
SELECT user.* FROM user_table user
INNER JOIN user_table first_manager ON first_manager.id = user.first_manager_id
INNER JOIN user_table second_manager ON second_manager.id = user.second_manager_id
WHERE first_manager.id = user.id
OR second_manager.id = user.id
The result of this would be users who are their own first or second manager.
I think what you are looking for is this:
public function findAllManagers()
{
$qb = $this->createQueryBuilder('m')
->leftJoin('u1', 'AppBundle\Entity\User', 'WITH', m = u1.firstManager)
->leftJoin('u2', 'AppBundle\Entity\User', 'WITH', m = u2.secondManager)
->where('u1.firstManager IS NOT NULL')
->orWhere('u2.secondManager IS NOT NULL')
->getQuery()
;
return $qb->getResult();
}
The equivalent SQL query should be:
SELECT m.* FROM user_table m
LEFT JOIN user_table u1 ON u1.first_manager_id = m.id
LEFT JOIN user_table u2 ON u2.second_manager_id = m.id
WHERE u1.first_manager_id IS NOT NULL
OR u2.second_manager_id IS NOT NULL
->join() results in an INNER JOIN. As you can read here, this will only give results, that are "in the center". If you have multiple JOINs, this will only give results of all those joined tables. In your case, this means users that are firstManager and secondManager.
If you change to ->leftJoin(), this will give you all users (with additional info), so it is a good place to start. Then you can filter those out that are neither firstManager or secondManager.
Something like this should work (untested)
$result = $this->createQueryBuilder('user')
->leftJoin('user.firstManager', 'fm')
->leftJoin('user.secondManager', 'sm')
->where('fm.id IS NOT NULL')
->orWhere('sm.id IS NOT NULL')
->getQuery()
->getResult()
;

Resources